JSP를 사용하여 간단하게 게시판을 만들고 취약점을 찾아 진단, 조치까지 해보기로 하였습니다.

게시판을 만드는 과정은 소스코드를 첨부 :  jspBoardProject.zip하는 것으로 생략하고

취약점을 진단하고 조치하는 과정에 초점을 맞추어 블로깅을 해보도록 하겠습니다.

급박하게 개발하느라 발로 짠부분도 있고, 후에 취약점 진단을 하기위해 일부러 취약하게 짠점도 있는데 

양해부탁드립니다 ㅠㅠ


개발 환경은 아래와 같습니다.

개발환경 

1. IDE : STS 3.8.3 / Dynamic Web Project

2. DB: mysql


간단하게 게시판의 형태를 살펴보도록 하겠습니다.

게시판의 메인화면입니다.



분석해볼 게시판의 기능은 크게 

1. 일반/비밀 게시글 작성

2. 파일첨부 기능


3. 게시글 정렬 기능


4. 게시글 검색 기능

5. 게시글 삭제 기능


6. 게시글 수정 기능

일반적으로 게시판이 가지는 기능을 가지고 있습니다.

블로깅은 취약점 진단/해당 취약점 조치 식으로 진행 해보도록하겠습니다.

'Study > WEB' 카테고리의 다른 글

jspBoardProject#5(File Download)  (0) 2017.02.08
jspBoardProject#4(SQL Injection / Blind SQL Injection)  (0) 2017.02.07
jspBoardProject#3(파라미터 변조)  (0) 2017.02.06
jspBoardProject#2(XSS)  (0) 2017.02.06
jspBoardProject#1  (0) 2017.02.06

1. 서론

apk 파일은 Zip파일에서 확장된 형태로 Zip파일과 동일한 구조를 같습니다. apk의 구조를 알아보기 위해 Zip파일 구조를 알아보았습니다. 


2. 본론

Zip 파일은 여러 개의 파일이 압축(혹은 압축되지 않은)되어 묶여 있는 파일이며 각 파일에 대한 내용이 담긴 헤더(Local file header)가 있고 이 헤더를 위한 각각의 헤더(Central Directory header)가 있으며 이 헤더들을 위한 끝판왕 헤더(End of Central Directory header)로 살펴볼 수 있습니다. 말로 풀어 이해가 잘 안되지만 그림으로 살펴보면 아래와 같습니다.

   

zip file 개요


2.1 ) End of Central Directory

End of Central Directory는 파일의 맨 마지막에 위치하고 있으며, 위에서 언급했 듯 Central Directory의 정보(시작 offset, 전체 Central Directory사이즈 등)를 가지고 있습니다. 즉, Zip파일 구조를 뜯어보려면 제일 먼저 End of Central Directory를 찾으면서 시작해야합니다. 하지만 End of Central Directory의 크기는 file comment때문에 가변적이고, 시작점을 단번에 찾기는 쉽지 않습니다. 

file comment는 End of Central Directory의 맨 마지막에 위치하고, file comment를 제외한 상위 바이트들의 사이즈는 22바이트입니다. 따라서 file comment가 존재하지않는 경우 파일의 끝에서부터 22바이트 떨어진 지점 부터 End of Central Directory가 시작됩니다. End of Central Directory가 시작되는지 확인하기 위해서는 4바이트를 읽어 signature(0x06054B50)와 일치하는지 확인해여 확인 할수 있습니다. 

종합해보면 End of Central Directory를 찾는 과정은 다음 처럼 생각해 볼 수 있습니다.

1) 파일 맨 끝에서부터 22바이트 떨어진 지점으로 파일포인터를 이동한다.

2) 해당 지점에서 4바이트를 읽는다.

3) signature와 일치하는지 확인한다.

4) 일치하지 않는 경우 파일의 상위로 파일포인터를 감아가면서 2) ~ 3)의 과정을 반복한다.


      end of central dir signature    4 bytes  (0x06054b50)
      number of this disk             2 bytes
      number of the disk with the
      start of the central directory  2 bytes
      total number of entries in the
      central directory on this disk  2 bytes
      total number of entries in
      the central directory           2 bytes
      size of the central directory   4 bytes
      offset of start of central
      directory with respect to
      the starting disk number        4 bytes
      .ZIP file comment length        2 bytes
      .ZIP file comment       (variable size)

End of Central Directory 구조


End of Central Directory의 시작점을 찾았다면 위에 서술된 구조를 참조하여, 차례대로 읽어드리면됩니다.

참고 : http://stackoverflow.com/questions/8593904/how-to-find-the-position-of-central-directory-in-a-zip-file

End of Central Directory에서 Central Directory들의 첫 시작 offset을 읽어 파일포인터를 이동시킴으로써 Central Directory를 읽을 수 있습니다.


2.2) Central Directory

Central Directory는 zip파일에 포함된 파일들의 정보를 가지고 있는 Local header의 정보(Local header의 offset, 파일명, 압축사이즈 등)를 가지고있습니다. End of Central Directory에서 얻은 offset을 통해 Central Directory들의 첫 시작 주소를 찾았다면, 이 또한 역시 4바이트를 읽어 signature(0x02014B50)와 비교하여 검증을 해볼 수 있습니다.  일치 하지 않는다면, 위에서 offset을 잘못찾은 것입니다. 

Central Directory들은 연속되어 존재합니다. 

ex) 첫 번째 파일의 Central Directory -> 두 번째 파일의 Central Directory -> ....... -> N 번째 파일의 Central Directory

Central Directory의 구조는 아래와 같습니다.

 central file header signature          4 bytes  (0x02014b50)
        version made by                 2 bytes
        version needed to extract       2 bytes
        general purpose bit flag        2 bytes
        compression method              2 bytes
        last mod file time              2 bytes
        last mod file date              2 bytes
        crc-32                          4 bytes
        compressed size                 4 bytes
        uncompressed size               4 bytes
        file name length                2 bytes
        extra field length              2 bytes
        file comment length             2 bytes
        disk number start               2 bytes
        internal file attributes        2 bytes
        external file attributes        4 bytes
        relative offset of local header 4 bytes

        file name (variable size)
        extra field (variable size)
        file comment (variable size)

Central Directory 구조


Central Directory역시 파일명, extra field, file comment등의 가변 크기의 변수를 갖지만 사이즈를 가지고 있으므로, 읽는데 어려움은 없습니다. file comment의 끝에 바로 다음 Central Directory가 시작 되기 때문에 다음 Central Directory의 offset이 따로 존재 하지 않는 것으로 추측됩니다.

Central Directory에서 local header offset을 얻었으면 비로소 실제 파일에 접근 할 수 있는 local header에 도달할 수 있습니다.


2.3) Local file header

Local file header는 파일의 0번부터 시작하며, 순차적으로 N번째 파일까지 이어집니다.

Local file header에는 Zip파일 내부에 존재하는 각 파일에 대한 정보들(파일 명, 압축 메서드 정보, crc-32 등)을 가지고 있습니다.

Local file header뒤에는 압축에 암호화가 되어있는 경우와 암호화가 되어있지 않은 경우로 뒤에 위치하는 데이터가 다릅니다.

암호화가 되어있는 경우에는 encryption header가 뒤에 온 후 file data(압축 되어있거나 압축되지 않은)

암호화가 되어 있지 않은 경우에는 바로 file data가 오게 됩니다.

      local file header signature     4 bytes  (0x04034b50)
      version needed to extract       2 bytes
      general purpose bit flag        2 bytes
      compression method              2 bytes
      last mod file time              2 bytes
      last mod file date              2 bytes
      crc-32                          4 bytes
      compressed size                 4 bytes
      uncompressed size               4 bytes
      file name length                2 bytes
      extra field length              2 bytes

      file name (variable size)
      extra field (variable size)

Local file header 구조


3. 결론

Zip파일 구조를 파악하기 위해 가장 중요한 세 가지 헤더를 알아보았는데, 아직 부족한 점이 많습니다.

encryption 여부를 파악하여 실제 file data를 찾아내는 방법, file data뒤에 이어지는 file description파트, zip64에 따른 내용 등

Zip파일 전체 구조를 확실히 파악하기 위해서는 조금 더 연구를 해보아야 할 것 같습니다.

개인적으로는 한글 자료가 상세하게 되어있는 것이 없어서 짦은 영어끈으로 머리 싸매가며 연구해본 첫 경험이라 매우 뜻깊었습니다.


참고

1) 위에 분석 방법을 C로 개발한 프로젝트를 git hub에 올려두었습니다.

링크 : https://github.com/uisooshin/ZipAnalyzer

2) 위 내용은 아래의 링크에 상세 내용이 있습니다.

링크 : https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT



'Study > ETC' 카테고리의 다른 글

2017년도 블로그 결산!  (0) 2018.01.09
Zip file구조  (2) 2016.10.27
Google I/O 2016 Extended Seoul 정리 및 후기  (4) 2016.06.19
Arpspoofing  (0) 2015.10.16
실전 악성코드와 멀웨어 분석 Lab03_03.exe  (0) 2014.09.01
실전 악성코드와 멀웨어 분석 Lab03-02.dll  (0) 2014.08.29
  1. 가산킴 2018.01.09 11:36

    End of Central Directory의 시작점을 찾았다면 위에 서술된 구조를 참조하여, 차례대로 읽어드리면됩니다.

    End of Central Directory의 시작점을 찾았다면 위에 서술된 구조를 참조하여, 차례대로 읽어들이면됩니다.

2016년 6월 19일 세종대학교 광개토관 컨벤션홀에서 열린 Google I/O 2016 Extended Seoul에 다녀온 후 개인적으로 정리 / 후기를 남겨봅니다!

이 행사는 13:00 ~ 18:00 동안 50분 컨퍼런스 10분 휴식방식으로 5번 3개의 트랙이 진행되었습니다. 저는 안드로이드에 타겟을 맞추어 세션을 왔다 갔다하며 참여하였습니다! 꼼꼼히 적는다고 적었으나, 아는만큼 보인다고.. 알아들은 것 위주로 개인적으로 중요하다 생각했던 것 위주로 적어서 두서없이 정리가 되었네요ㅠ 정리한 내용은 아래와 같습니다.

1. 안드로이드 N을 준비하는 개발자를 위한 안내서 - 이승찬 (Track A)  

1-1) Android N 진행 현황

Android N은 6월 15일에 공개된 API 즉, API24가 최종으로 배포될 예정이고 큰 결함이 없는 이상 수정없이 배포될 예정이라고 합니다. 또한 이전 배포한 마쉬멜로 이하 버전들은 항상 버그가 발견되거나 수정사항이 생겨서 일정이 조금씩 밀려서 10월 정도에 릴리즈가 되곤했다고 하는데, 이번 N버전은 일정대로 순항 중이고 현재 큰 버그가 없다면 8월 중에는 N버전 사용가능 단말(Nexsus, xperia)에 한해 릴리즈 유저가 생길 전망이라고 합니다!

 1-2) Android N for Developer

Android N에 대처하는 개발자들에게 3가지 정도를 이야기했습니다.

1-2-1) Supproting Multiple Screens

기존에 많이 알려져있듯이 Android N 버전에서의 가장 큰 변화는 Supproting Multiple Screens 입니다. 단말의 화면을 분할하여 2개의 앱을 실행하는 기능인데요, 나뉜 화면 간 Drag and Drop까지 가능하다고 합니다. 

Multiple Screens는 기본적으로 한 화면에 두 개의 Activity가 실행되지만 기존의 Activity생명주기라던지, Orientation과 같은 View 메커니즘 상의 변화는 없다고 합니다. 다만 유의할 점이 몇 가지 생겼습니다. 

유의점 

- 앱에서 화면을 landscape 또는 portrait로 제한하고 있는 경우 멀티스크린에서 view가 제대로 동작하지 않을 수 있으니 유의해야합니다.

-  Screen Zoom이라는 기능은 안드로이드 접근성을 고려해 생긴 기능으로 말그대로 화면을 Zoom을 할 수 있는 기능입니다. 기존에 High performance device를 타겟으로 한 앱이라면 320dp의 리소스가 누락되어있을 수 있는데, 이런 경우에는 resource not found exception -> app crash 가능성이 있습니다.

- Chrome Book은 아직 국내에서 많은 인기를 얻고 있지는 못하지만 미국에서 진행된 Google I/O 2016에서는 상당한 관심을 보였던 분야라고 합니다. Chrome Book에서도 Multiple Screens를 고려한 개발이 이루어져야 한다고 합니다.

제가 유의점을 들으면서 종합적으로 이해한 내용은 기존의 버전에서 앱의 화면은 비교적 정적으로 지정되어있었다면 N버전부터는 내 앱이 어떤 화면에서 어떤 해상도로 어떻게 실행 될지 알수 없는 상황이 되고 많은 UI의 가능성을 고려하지 않으면 안된다는 내용이라고 이해했습니다.

물론 멀티스크린을 무조건 지원해야하는 것은 아닙니다. 멀티스크린을 지원하지 않게 지정하려면 엑티비티 설정 중 resizeableActivity를 false 값으로 셋팅하여 지원하지 않도록 설정 할 수 있습니다. 다만 이 resizeableActivity의 default값은 true이고, Activity_task의 root에 설정된 값을 나머지 task들이 모두 같은 값을 갖게 됩니다.

구체적인 방법은 아래와 같습니다.

if I can't support Multi-Window

1. targetSdk < 24, screenOrientation = portrait | landscape

2. targetSdk = 24

   a. resizeableActivity = false

      launchMode = singleInstance | singleTask

   b. resizeableActivity = fase

      documentLaunchMode = always


1-2-2) Battery Optimization

더 나은 앱을 위해서 모든 개발자들이 고민하는 요소 중의 하나인 베터리 최적화에 대한 내용을 이야기했습니다.

구글이 생각하는 Battery Optimization을 위해 Background Activity 3가지 원칙이 있었는데요,

 1. Reduce

    - 하지말자

 2. Defet

    - 해야되면 충전 할 때 하자

 3. Coalesce

    - 여러가지 작업을 한번에 몰아서 하자

이런 원칙을 지키기 위해서 Marshmallow버전부터 있었던 Doze mode라는 것을 N에 더 강화시켰다고합니다. 안드로이드 개발을 하고있다고 하면서도 처음듣는 내용이라서 매우 부끄러웠습니당ㅠㅠ

Marshmallow에서 Doze mode는 간단히 말해서 단말이 일정시간동안 움직이지 않고 스크린이 비활성화 되어있다면 네트워크 작업, 알람 작업 등의 백그라운드 작업이 멈추게됩니다. 이 기능이 Android N에서는 한층 더 강화되어서 이동 중일때에도 스크린이 비활성화 된 경우 Doze mode가 활성화 된다고하네요.

이런 Doze mode에 대한 앱 테스트를 진행 할 수 있는 방법은 아래의 adb 커맨드로 환경을 설정해서 테스트를 진행 할 수 있습니다.

adb shell dumpsys deviceidle step (light) 

또한 베터리 관련해서 Battery Historian이라는 것을 이용하면 app의 베터리 사용량을 자세히 모니터링 할 수 있다고 하니 참고하시기바랍니다.


1-2-3) Memory Optimization

안드로이드에서 발생하는 브로드캐스트 Intent에 대해 앱이 반응하도록 만들어진 경우가 많습니다. 예를 들어  NEW_PICTURE를 이용하여 사진 촬영이 이루어지면 저장소에 전송하여 동기화를 시키는 일을 예로 들수 있습니다. 이 때 만약 NEW_PICTURE 브로드캐스트 Intent를 사용하는 앱과 서비스의 개수가 많다면 한번에 사용하는 메모리량이 많아지고, 이에 따라 불안정한 상태가 될수 있습니다. 이번 컨퍼런스에서 발표된 내용에 의하면 CONNECTIVITY_CHANGE, NEW_PICTURE, NEW_VIDEO 등의 Intent가 사라진다고 하네요.
또한 개인적으로 충격적이였던 부분은 Background Service를 점진적으로 제한해서 궁극적으로는 없앨 예정이라고합니다.(지금 당장 N버전에서부터 없어지는 것은 아닙니다.)
앱에서 백그라운드 서비스가 없을 경우의 테스트를 진행 하려면 아래와 같은 adb 커맨드를 이용하여 테스트하면됩니다.
adb shell cmd appops set <package_name> RUN_INBACKGROUND ignore  //background service     비활성화
adb shell cmd appops set <package_name> RUN_INBACKGROUND allow    //background service     활성화


1-3) Android`s New Features
앞으로 추구되는 Android의 방향에대해 요약해주셨습니다. 
1. 다양한 화면 크기와 오리엔테이션을 지원
2. 반복적으로 수행되어야하는 작업은 베터리를 최적화 할 수 있도록 지능적으로...
3. 서비스/브로드 캐스트 리시버 등 백그라운드 작업은 꼭 필요한 경우에만 사용  

첫 번째로 들었던 세션에서는 Android N뿐만아니라 앞으로 진행될 Android방향 전반에대한 내용도 들을 수 있어서 좋았던 것 같습니다. 다 듣고 난 후에 들었던 생각은 예전의(Marshmallow 이전) 안드로이드는 개방되어있고 핸들링할 수 있는 요소가 많은 장점을 가졌었다면 앞으로 나아갈 Android의 방향은 폐쇄적이지만 안정적이고, 여러 디바이스 환경에 대처 가능한 유연함을 추구하고 있는 것으로 느껴졌습니다.


2, 3. 두,세 번째 세션 
두,세 번째 세션이라고 항목을 넣기도 애매한데요,,  
두 번째 세션에서 What`s next for the web? (B track)을 들었습니다. 웹앱에 대한 내용을 기대하고 들었는데 웹앱이 크롬 어플리케이션에 대한 내용이였습니다ㅠㅠ JavaScript도 잘 모르는 상황이라 절반 정도까지 이해안되는 세션을 억지로 듣다가 Espresso에 대한 세션(B track)으로 넘어가서 어중간해졌네요
그래도 두 번째 세션에서 promise라던지 Espresso 등 중요 키워드를 들었으니 앞으로 살펴볼 요소라고 생각합니다.
특히 Espresso같은 경우에는 Android Studio에 탑재되어있고, 코드를 통해 UI QA 테스트를 진행하는 예시를 보니 굉장히 흥미로웠습니다. 잘만 활용하면 업무 상에도 적용가능할 수있을 것 같다고 생각했습니다.
세 번째 세션에서는 딥러닝에 대해서 Tensorflow를 이용하여 파이썬으로 MINIST를 핸들링하는 내용에 대해서 들었는데,
세션 진행하시는 분께서 최대한 쉽게 풀어주셨지만 기초지식이 있는 상태에서 들었어야 하는 내용이고 애초에 50분내에 다룰 수 있는 내용이 아니라는 생각이 내내 들었습니다. 아는만큼 보인다고ㅠㅠ 머신러닝에 대해 "그냥 그렇구나" 하고 멍하니 듣기만해서 적을 수 있는게 많이 없네요ㅠ   

4. Android Studio 2.2(Preview 3 기준) - 김태호 (Track A)

새롭게 나올 Android Studio 2.2에 대해서 현재까지 나온 Preview 3을 기준으로 잘 설명해주셨습니다. 개인적으로 이 세션을 듣고나서 Android Studio 2.2에 대한 기대감이 엄청나게 올라갔습니다!

4-1) Design

4-1-1) 기존에 있었던 Visual Editor가 더욱 심플하고 파워풀해졌습니다. 

몇 가지 눈에 띄는 것을 꼽아보자면 레이아웃 리소스 외 menu와 Preferences를 지원하고 에디터 내에서 스크롤 뷰의 스크롤 지원하며 속성 탭 개편을 통해 주요 속성화면과 전체 속성 화면간 전환이 이루어져서 굉장히 편리하게 개선되었습니다!

4-1-2) Constraint Layout

새롭게 추가된 Constraint Layout은 Visual Editor와 맞물려서 앱의 Layout과 View를 굉장히 간편하게 만들수 있게 되었습니다. XCode와 견주어도 더 좋으면 좋았지 떨어질 것 같지 않은 성능인 것 같았습니다. 세션 진행자 분께서 시연영상을 보여 주셨는데 기존에는 거의 xml로 작업했다면 제 기준으로 1~2시간은 족히 했을 작업을 10분도 안되서 뚝딱 하는 걸 보고 굉장히 놀랐습니다. 거의 프리젠테이션에서 도형그리기 수준(?)으로 보였습니다. 또한 기존의 레이아웃은 Relative Layout안에 Linear Layout이 배치되고, 또 안에 Layout이 배치되거나 View가 배치되는 구조가 일반적이였는데 Constraint Layout은 이러한 계층 구조를 가지지 않고 Constraint Layout안에 한번에 다 배치가 되는 구조라 계층구조가 굉장히 단순해지고 쉬워졌습니다! 

4-1-3) Layout inspector

현재 표시되고 있는 단말기 (혹은 에뮬레이터)의 뷰 구조를 확인할 때 사용하는 툴인데요, 기존에 DDMS 내에 포함되어 있던 기능과 거의 유사하며 뷰 디버깅에 용이하다고 합니다. Android Monitor Pane > Layout Inspector를 통해 사용할 수 있다고하는데, 사실 아직 개발하면서 UI 디버깅을 많이 해본 적이 없어서 그런지 Constraint Layout에 비해 크게 기대되는 기능은 아니였습니다. 역시 아는만큼 보이는 거겠지요.ㅠㅠ

4-2) Develop

코드 분석과 리소스 관리측면에 대해서 향상되었습니다. 

4-2-1) Find & Remove Unused Resources기능은 앱내에서 참조하지 않고 있는 리소스를 찾아서 제거해주는 기능이라고 합니다. Android N에서 제공하는 멀티스크린에서는 리소스의 량이 크게 증가할 거라는 생각이 들었는데, 이것과 맞물려서 유용할 것 같습니다.

4-2-2) 또한 추가된 Annotations 22.2에서 Threading Annotations를 통해서 @UIThread, @MainThread, @WokerThread, @BinderThread 등으로 지정하고 구현을 해서 런타임 에러를 많이 줄일 수 있게 될거 같습니다.

또한 기존에 타겟 sdk를 지정하고 그와 맞지 않는 api를 사용하여 개발을 하면 노란 밑줄이 가면서 잠재적인 에러를 내포하는 코드가 되곤했는데 @RequiresApi를 통해서 호출하는 단말의 api레벨에 따라 알아서 판단해서 분기해서 호출하게 됩니다.  

@Dimension, @Px 어노테이션은 px과 dp 값을 표시하게 되어 좀더 깔끔한 UI처리를 할 수 있게 도와줍니다. 

@Keep은 난독화 관련 어노테이션은 Proguard 돌릴 때 난독화를 돌리지말아라 하는 어노테이션으로 알아두면 굉장히 유용할 것 같습니다. 단, Keep 어노테이션은 Gradle plugin 2.2+를 사용 해야합니다.

4-2-3) firebase plugin이 추가되었는데 GCM등의 서비스를 제공하기 편하도록 해주는 플러그인이라고 합니다.

4-2-4) 현재 개발 중 모르는 api는 f2키를 눌러 제공되는 document를 보고 확인 할 수 있습니다. 앞으로는 sample code 또한 document와 같이 제공되어 더욱 쉽게 이해 할 수 있게 도와준다고합니다. 


4-3) Build
빌드에 대해 크게 추가되는 점들이 있습니다.
4-3-1) Jack Compiler가 향상되었습니다. Java 8의 일부 기능이 사용가능한데, 대표적으로 람다표현식을 들 수 있습니다.
4-3-2)정식버전 플러그인에서  CMake and ndk-build가 지원됩니다. externalNativeBuild{} 사용하여 적용가능하며 CMake를 사용한 예제 저장소가 공식홈페이지에 게시되어있다고하네요

4-4) TEST
4-4-1) APK Analyzer
APK의 클래스/메서드 수, 각 부분 별 차지하는 용량, 다양한 환경에 적용될 리소스 등  다양한 정보를 확인 할수 있게 되었습니다. 특히 자신이 작성하여 export한 apk뿐만 아니라 보유하고있는 apk파일을 지정하면 난독화된 부분을 제외하고 모두 볼 수있다고합니다. 개발자분들뿐만 아니라 분석가분들 입장에서도 역시 흥미롭게 볼만한 기능인 것 같습니다. 본 기능은 Build > Analyze APK...에서 사용 할 수 있습니다.

4-4-2) Espresso Test Recoder
두 번째 세션에서 제가 놓쳤던 부분에 대해 간략하게 나마 더 설명을 들을 수 있었습니다. 주된 내용은 Espresso를 이용하여 테스트 진행이 가능하지만 테스트를 위한 코드를 작성하는 일 또한 자칫 노가다가 될 수 있는데, 테스트할 동작을 에뮬레이터에서 진행하고 recoding을 하면 테스트코드를 작성해준다는 놀라운 기능이였습니다.

4번째 세션을 듣고 난 생각은 현재 업무에서 Eclipse 비중이 훨씬 더 높고, 고객사들 역시 대부분이 Eclipse를 사용하고 있기 때문에 지금 당장 Android Studio로 모든 것을 사용하기는 어렵지만 새로 개발하는 건에 대해서는 Android Studio를 사용하고 기존에 있는 코드 역시 컨버팅을 해나가는 준비가 필요하다는 생각이 들었습니다.

5. Introduce Android TV and new features from Google I/O 2016 - 이승재 (Track A)
마지막 세션이여서 그런지 코드를 비롯한 기술적인 내용보다는 Android TV에 대한 기능 및 릴리즈 현황과 앞으로의 전망에 대해 편안하게 들을 수 있는 세션이였습니다.
인상 적인 부분은 Android TV가 발매된 시점 부터 증가 추이를 그래프로 표현해준 슬라이드를 보았을 때, 6개월 단위로 Linear로 증가하는 것이 아니라 exponential적으로 증가되고 있는 점이 있었습니다. 더불어 현재 스마트폰을 타깃으로 나오는 App 시장은 이제 레드오션으로 볼 수 있고, 앞으로 Android TV시장은 블루오션일 수 있다 라는 말에 공감은 되었으나 같이 참여한 사촌형님의 개인 개발자의 입장으로써는 진입장벽이 너무나 높을 것 같다는 말 또한 이해가 되었습니다.
굉장히 의외라고 느꼈던 부분은 Android TV를 생산하고있는 회사에 삼성/LG가 빠져있는 점이 있었습니다. 자체 개발 Smart TV를 생산하기 때문인것 같기는 하지만, 샤오미 + Android와 대적 가능한 Smart TV를 만들 수 있는지에 대해서는 개인적으로 의문이 들었습니다.


끝으로 Google I/O 2016 Extended Seoul에 참석한 이 후 느낀 점을 몇자 적어보자면, 각 세션을 들으면서 생소한 단어, 기술들이 많이 있었고, 앞에서 계속 언급했듯이 아는만큼 보인다는 말을 몸으로 느낀 좋은 경험을 한 것 같습니다. 
개인적으로 많은 자극이 되었고 더욱 열심히 해야 겠다는 동기부여가 된 것 같습니다.
이상으로 긴 글 읽어주셔서 감사합니다 :)


'Study > ETC' 카테고리의 다른 글

2017년도 블로그 결산!  (0) 2018.01.09
Zip file구조  (2) 2016.10.27
Google I/O 2016 Extended Seoul 정리 및 후기  (4) 2016.06.19
Arpspoofing  (0) 2015.10.16
실전 악성코드와 멀웨어 분석 Lab03_03.exe  (0) 2014.09.01
실전 악성코드와 멀웨어 분석 Lab03-02.dll  (0) 2014.08.29
  1. 1466357712 2016.06.20 02:35

    알찬 정보 좋네요~

  2. 1466386554 2016.06.20 10:35

    잘보고가요~

  3. 1466868526 2016.06.26 00:28

    제 블로그도 놀러오세요~

  4. 1467597050 2016.07.04 10:50

    좋은글 감사

서론

탈옥된 단말에는 파일을 전송하고, 그 파일을 실행시킬 수 있다.

2장 iOS해킹 기초 에서는 간단하게 hello world를 출력하는 프로그램을 탈옥단말에서 실행시키는 것을 실습하게 되어있다. 책에 있는 방법을 따라가면서 실습을 진행하려고 하였으나, 책이 12년도에 출판된 책이라서 OS환경이 달라 똑같이 진행하는데는 무리가 있었고, 다른 방법을 찾아가면서 실습을 마칠 수 있었다.


본론

탈옥 단말에서 hellow world를 출력하는 프로그램을 실행하기 위한 순서는 아래와 같다.

1. hellow world를 출력하는 소스 코드 작성

2. 컴파일

3. 파일전송을 통해 탈옥단말로 파일 전송

4. 파일 실행


1. hellow world를 출력하는 .c 파일 작성

처음에 소스코드가 간단하기 때문에 vi로 작성하였다.


2. 컴파일

gcc로 컴파일 하여 실행파일을 만들어냈다.


3. sftp를 이용하여 파일전송

책에서는 scp를 이용한 방법으로 써져있었지만, FileZilla로 sftp를 이용하여 파일을 전송하였다.


4. 파일실행

파일을 실행했더니, 아래와 같은 에러 문구가 나오고 실행이 되지 않았다.

이유를 곰곰히 생각해보니, 파일이 arm cpu에서 실행될 수 없는 파일이라는 것을 깨닫게 되었고, 직접확인해본 결과

file의 타겟 cpu타입이 다르다는 것을 확인할 수 있었다.

file 명령어를 통해 확인해 본 결과로 컴파일된 test라는 프로그램은 Mach-O 64-bit바이너리이고, 타겟 cpu는 x86_64였다.


문제 해결을 위해 arm cpu를 타깃으로 컴파일할 수 있는 방법에 대해서 여러가지 찾아보았으나, 책에서 제시하고 있는 llvm-gcc는 OS버전이 올라가면서 경로에서 찾을 수 없었다. arm cpu를 타깃으로 컴파일할 수 있는 arm-eabi-gcc를 설치하고 컴파일 해보니 arm타깃으로 컴파일이 되기는 하나 ELF파일 형식을 가지고 있어서 실행이 될 것같지는 않았다.

한 가지 들었던 생각은 어찌됐건 XCode에서 앱을 만들면 ipa안에 실행 바이너리가 들어있으니까, llvm-gcc가 XCode안에 들어있지 않을까 하고 생각하기는 했지만 찾을 수는 없었고, 조언을 통해 XCode로 컴파일을 하는 방법을 시도해보았다.

 

iOS 앱 프로젝트를 하나 생성한 후 Supporting Files안에 있는 main.m을 위의 사진과 같이 만들어서 실행 바이너리를 얻을 수 있었다.


한 가지 유의할 점은 앱을 빌드할 때 타깃이 시뮬레이터인경우 생성되는 바이너리는 타깃 cpu가 x86_64로 생성된다. 생각해보면 시뮬레이터는 컴퓨터에서 돌아가는 환경이니 당연한 결과인 것 같고, 원하는 arm 타깃 바이너리를 만들기 위해서는 실 디바이스를 연결해서 빌드하는 과정이 필요했다.


이렇게 생성한 바이너리를 동일한 3, 4과정을 거쳐 실행을 한 결과 아주 잘 동작하는 것을 확인할 수 있었다!


결과

정리하고 나니 삽질의 흔적이 많이 보이지는 않지만 저 mach-o arm 실행 바이너리를 얻기위해 많은 것을 시도했다. 이 과정을 통해서 컴파일러들에 대해서 조금은 더 알게되었고 막연했던 무언가가 윤곽이 보이는 것 같은 느낌이든다. 

지금은 간단하게 hellow world를 출력하는 코드를 삽입한 것일 뿐이지만, 특정 위치의 파일을 읽어 온다거나 어떤 메모리의 덤프를 뜬다거나 하는 코드 또한 동작시킬 수 있을 것 같고, 나중에 꼭 쓰게될 기술일거라고 생각한다.

다음 블로깅에는 삽입한 코드를 특정 이벤트 발생 시 실행 시킬수 있는 plist를 작성해서 실습을 진행해볼 예정이다. 


'Study > iOS' 카테고리의 다른 글

iOS Hooking#2(Frida)  (0) 2017.04.19
iOS Hooking#1(Logos)  (1) 2017.04.18
2장 iOS 해킹 기초 (1)  (0) 2016.06.18
자주쓰는 데이터형 변환  (0) 2016.06.01
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13
[iOS] MD5해시 생성 / SHA256해시 생성  (0) 2016.05.09

서론

iOS에서 개발을 하다보면, C <-> Objective C 간 데이터 형변환을 하게되는 경우가 빈번한 것 같다. 예를 들어 C에서의 문자열은 Char * / Char[] 데이터형을 사용하지만, Objective C에서는 NSString객체를 사용한다. 또한 NSString 객체를 Char * / Char[]의 형태로 역변환해서 사용하는 경우도 빈번하기 때문에 경우의 수도 많고, 외워서 쓰기에는 불편함이 있어서 정리해서 모아보는 글을 작성해보기로 하였다.


본론

1 ) unsigned char * ( C ) 와 NSData (Objective C) 

1-1) unsigned char * to NSData

1
2
3
unsigned char * byteString = "byteString";
int byteStringLen = strlen(byteString);
NSData * data = [NSData dataWithBytes:byteString length:byteStringLen];


byteString이 바이트 data라고 가정할 때, 위와 같이 변환 할 수 있다. 만약, 문자열을 바이트 데이터로 바꾸고 싶다면 base64디코딩 과정을 거쳐야 한다.


1-2) NSData to unsigned char *

1
2
NSData * data;
unsigned char* bytesForData = [data bytes];


예시에서는 값이 들어 있지 않지만, NSData형의 data라는 변수에 값이 있을 때 위와 같이 변환 할수 있다.


2 ) char * ( C ) 와 NSString (Objective C)

1-1) char * to NSString

1
2
NSString * nsString = @"Hello Bach!";
char * cString = [nsString UTF8String];


NSString에서 제공하는 UTF8String 메서드는 NSString문자열을 char * 데이터형을 반환해준다.


1-2) NSString to char *

1
2
char * cString = "Hello Bach!";
NSString *nsString = [NSString stringWithUTF8String:cString];


NSString 문자열 변수 또한 NSString에서 제공하는 stringWithUTF8String 메서드를 통해서 char * 데이터형으로 변환할 수 있다.


결론

개인적으로 Objective C에서 프로그래밍을 하면서 느낀점은 C와 같이 혼용해서 사용할 수 있기 때문에 편한 점도 있지만 불편한 점 또한 있고, 그 불편한 점 중에 한가지가 데이터형태가 일치하지 않기 때문에 변환 과정이 필요하다는 것이다. 간단하게 가장 많이 사용하는(본인이) 형변환 두 가지만을 다루었는데, 다른 데이터 형 변환 중 자주 쓰이는 변환이 생긴다면 더 추가할 생각이다.

'Study > iOS' 카테고리의 다른 글

iOS Hooking#2(Frida)  (0) 2017.04.19
iOS Hooking#1(Logos)  (1) 2017.04.18
2장 iOS 해킹 기초 (1)  (0) 2016.06.18
자주쓰는 데이터형 변환  (0) 2016.06.01
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13
[iOS] MD5해시 생성 / SHA256해시 생성  (0) 2016.05.09
서론

 iOS, C/C++에서 main함수보다 먼저 실행하고 싶은 코드가 있을 수 있다. 가령, iOS앱 시작 전에 디바이스 정보를 먼저 얻어온 후 앱을 시작하는 경우나, 사용 Class의 초기화 등의 경우가 이에 해당한다.

 이런 상황에서 __attribute__((constructor)) 옵션을 지정한 함수를 선언하여 해결할 수 있다.


본론

 1. __attribute__((constructor)) 란?

 서론부에서 설명한 것 처럼 GCC에서 제공하는 옵션이다. 이 옵션을 지정한 함수는 main함수 실행 이전에 호출되어 함수의 로직을 수행하게 된다.

 1-1) __attribute__((constructor))을 지정한 함수 선언 방법 예시 

1
2
3
4
void __attribute__((constructor)) before_main( void )
{
/* Things to do before main function */
}


위와 같이 간단한 형태로 정의 할 수 있다.


 1-2) __attribute__((constructor))가 여러개인 경우의 처리

만약 메인 이전에 수행하고싶은 함수의 개수가 두 개 이상인 경우의 처리는 어떻게 하면 될까? 

이러한 경우에는 함수의 Priority를 지정할 수 있는 방법이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int my_constructor(void) __attribute__((constructor));
int my_constructor2(void) __attribute__((constructor(101)));
int my_constructor3(void) __attribute__((constructor(102)));
int my_constructor(void/* This is the 3rd constructor */
{                        /* function to be called */
    ...
    return 0;
}
int my_constructor2(void/* This is the 1st constructor */
{                         /* function to be called */
    ...
    return 0;
}
int my_constructor3(void/* This is the 2nd constructor */
{                         /* function to be called */
    ...
    return 0;
}


위의 예시 코드에서 볼 수 있듯이 우선순위를 지정하지 않고 default로 선언을 한 경우에는 가장 후 순위로 지정되는 것을 알 수 있으며, 여러 개의 constructor함수가 default선언이 된 경우, 스택 방식으로 먼저 발견되는 함수가 나중에 호출되는 순서를 가진다.


결론

 GCC옵션 중 __attribute__((constructor))를 이용하여 메인 함수 이전에 실행되는 함수를 선언하는 방법에 대해서 알아보았다.

이 옵션과 반대의 옵션인  __attribute__((desstructor)) 옵션을 주면 메인 함수 이후에 실행되는 함수 또한 선언하여 사용 할 수 있고, 이 두 함수의 조합으로 메인 함수 이전과 이후의 처리가 필요한 경우 자유롭게 코드를 더 할 수 있게 된다. 

'Study > iOS' 카테고리의 다른 글

iOS Hooking#2(Frida)  (0) 2017.04.19
iOS Hooking#1(Logos)  (1) 2017.04.18
2장 iOS 해킹 기초 (1)  (0) 2016.06.18
자주쓰는 데이터형 변환  (0) 2016.06.01
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13
[iOS] MD5해시 생성 / SHA256해시 생성  (0) 2016.05.09

서론

 MD5와 SHA256 해시 함수들은 주로 파일의 무결성을 검증하는데 사용되어지고 있는 해시 함수들이며, 이 둘은 단방향 암호화 함수이기 때문에 현재까지는 출력 값을 복호화하여 입력 값을 알아내기는 매우 어렵다.

 이 둘의 원리와 구현방법을 다룬다면 정말 좋겠지만, 이 글에서는 구체적인 알고리즘이나 구현 방법에 대해서는 다루지 않고, 둘의 전반적인 개요와 iOS에서 제공하고 있는 구조체와 함수들을 이용하여 파일과 메모리에 대해서 MD5 및 SHA256 해시 값을 얻어내는 방법에 대해서 알아볼 것이다.


1. MD5 해시란?

 MD5(Message-Digest algorithm 5)는 128비트 암호화 해시 함수이다.  주로 프로그램이나 파일이 원본 그대로인지를 확인하는 무결성 검사 등에 사용된다. 1996년에 MD5의 설계상 결함이 발견되었고, 2004년에는 더욱 심한 암호화 결함이 발견되었고. 2006년에는 노트북 컴퓨터 한 대의 계산 능력으로 1분 내에 해시 충돌을 찾을 정도로 빠른 알고리즘이 발표되기도 하였다. 현재는 MD5 알고리즘을 보안 관련 용도로 쓰는 것은 권장하지 않으며, 심각한 보안 문제를 야기할 수도 있다. 2008년 12월에는 MD5의 결함을 이용해 SSL인증서를 변조하는 것이 가능하다는 것이 발표되었다.


2. SHA256 해시란?

 SHA(Secure Hash Algorithm, 안전한 해시 알고리즘) 함수들은 서로 관련된 암호학적 해시함수들의 모음이다. SHA 함수군에 속하는 최초의 함수는 공식적으로 SHA라고 불리지만, 나중에 설계된 함수들과 구별하기 위하여 SHA-0이라고도 불린다. 2년 후 SHA-0의 변형인 SHA-1이 발표되었으며, 그 후에 4종류의 변형, 즉 SHA-224, SHA-256, SHA-384, SHA-512가 더 발표되었다. 이들을 통칭해서 SHA-2라고 하기도 한다. SHA-256은 256 비트이다.


 간단하게 종합적으로 요약해보면 두 함수 모두 단방향 암호화 해시함수이며, MD5보다는 SHA-2 즉 SHA-256이 더 안전하다고 보인다.


본론

1. 대상 통째로 해시 생성

위의 코드들은 XCode상에서 사용할 수 있는 CommonDigest.h 헤더파일의 일부이다. 위의 함수와 구조체를 이용해서 해시값을 구할 것이다.


결론 부터 이야기 하자면 CC_MD5함수를 이용하여 해시 값을 구할 수 있다.

unsigned char *CC_MD5(const void *source, CC_LONG len, unsigned char *dest)

CC_MD5함수와 CC_SHA256함수는 unsigned char * 를 리턴하고 있으나, 일반적으로 아래 예시처럼 리턴 값을 사용하고 있지 않지만 리턴 값은 md파라미터를 통해 전달된 포인터를 반환한다.

첫 번째 인자 : 해싱 할 데이터의 포인터

두 번째 인자 : 해싱 할 데이터의 사이즈

세 번째 인자 : 해싱 한 데이터를 저장 할 수 있는 포인터

실제 MD5 해싱소스 코드를 보면 아래와 같다. (아래 내용의 MD5를 SHA256으로 치환하고, result의 배열인덱스를 0~ 31 까지 받아내면 SHA256소스가 된다.)

펌 1)

#import <CommonCrypto/CommonDigest.h>

NSString *md5(NSString *str) {
    const char *cStr = [str UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5( cStr, strlen(cStr), result );
    return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
        result[0], result[1],
        result[2], result[3],
        result[4], result[5],
        result[6], result[7],
        result[8], result[9],
        result[10], result[11],
        result[12], result[13],
        result[14], result[15]
    ];
}

NSString *digest = md5(@"test");
NSLog(@"MD5 TEST %@", digest);


2. 버퍼를 이용하여 해시 생성

 예를 들어 크기가 큰 파일의 해시 값을 구한다고 가정할 때, 위의 방법을 이용하면 파일 전체를 메모리에 올려야 하기때문에 저사양 단말에서는 큰 부담을 가지게 될 수 있다. 따라서 파일의 일부를 버퍼를 이용하여 해시 값을 업데이트함으로써, 메모리사용량을 줄 일 수 있다.


이 때 사용할 구조체 및 함수는

CC_MD5_CTX / CC_SHA256_CTX

CC_MD5_Init() / CC_SHA256_Init()

CC_MD5_Update() / CC_SHA256_Update()

CC_MD5_Final() / CC_SHA256_Final() 를 사용할 것이고, 개념적인 순서로 나열해보면, 

1) CC_MD5_CTX / CC_SHA256_CTX 구조체를 선언 하여 컨텍스트를 선언한다.

2) CC_MD5_Init() / CC_SHA256_Init() 컨텍스트 변수를 초기화한다.

3) CC_MD5_Update() / CC_SHA256_Update()를 이용하여 버퍼의 내용만큼 해시를 업데이트한다.

4) CC_MD5_Final() / CC_SHA256_Final() 모든 과정을 마무리하여 해싱작업을 마친다.


위의 과정을 이용한 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static int md5useUpdate(char* data, long len, char** outData)
{
    unsigned char hashBuf[BUF_LEN] = {0,};       //업데이트에 사용할 버퍼
    long curSize = 0;                            //현재 까지 읽은 데이터 사이즈
    long remainSize = len;                       //남아있는 데이터 사이즈
    
    //md5 컨텍스트 선언 및 초기화
    CC_MD5_CTX md5;
    CC_MD5_Init(&md5);
    
    //더 읽어야할 데이터가 남아있다면
    while(curSize < len){
        
        if(remainSize < BUF_LEN){       //남은 사이즈가 버퍼보다 작을 경우, 남아있는 사이즈만큼만 메모리 복사 및 해시 업데이트
            memcpy(hashBuf, (char *)data, remainSize);
            CC_MD5_Update(&md5, hashBuf, (CC_LONG)remainSize);
        }
        
        else{                           //일반적인 경우 버퍼사이즈(1024)만큼 메모리 복사 및 해시 업데이트
            memcpy(hashBuf, (char *)data, BUF_LEN);
            CC_MD5_Update(&md5, hashBuf, (CC_LONG)BUF_LEN);
        }
        
        data += BUF_LEN;                //메모리 읽은 후 포인터 이동
        curSize += BUF_LEN;             //현재까지 읽은 사이즈 계산
        remainSize -= BUF_LEN;          //남아있는 사이즈 계산
    }
    
    unsigned char digest[CC_MD5_DIGEST_LENGTH] = {0,};
    CC_MD5_Final (digest, &md5);
    char *tmpOut = *outData;
   
    
    for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
    {
        sprintf(tmpOut, "%02x", digest[i]);
        tmpOut += 2;
    }
 
    return 0;
}


 정의한 함수 md5useUpdate는 해싱할 데이터와 사이즈, 해싱한 문자열을 기록할 데이터의 포인터를 입력받는다. 자세한 내용은 주석으로 설명을 대체하였다. 위의 코드 역시 약간의 수정으로 쉽게 SHA256 해싱이 가능 하도록 변환할 수 있다.


결론

 XCode내에서 제공하는 함수를 이용하여 MD5 및 SHA256해시를 생성하는 방법에 대해서 알아보았다. 내부적인 알고리즘까지 깊게 다루지는 못하였지만, 주어진 API를 이용하여 해시를 생성할 수 있었고, 이를 응용할 수 있는 방법은 사용자의 비밀번호를 저장하는 방법에 평문으로 저장하기 보다는 해시 값을 이용하여 저장하고, 파일의 해시 값을 비교하여 파일의 무결성을 검증하는 등 무궁무진할 것 같다. 



'Study > iOS' 카테고리의 다른 글

iOS Hooking#2(Frida)  (0) 2017.04.19
iOS Hooking#1(Logos)  (1) 2017.04.18
2장 iOS 해킹 기초 (1)  (0) 2016.06.18
자주쓰는 데이터형 변환  (0) 2016.06.01
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13
[iOS] MD5해시 생성 / SHA256해시 생성  (0) 2016.05.09

1. 애플리케이션 인증서란?

 1) 어플리케이션 인증서는 개발자가 어플리케이션 마켓에 등록할 때 어플리케이션의 신뢰성을 나타내기 위해 사용하는 것이다.   모든 어플리케이션은 설치되기 전에 인증서와 함께 서명되어야 한다.

 2) 안드로이드 어플리케이션 서명은 Jar서명을 수정한 것이며 Jar서명은 암호 해시 함수를 어플리케이션의 구성요소에 적용하는 방식으로 동작한다. 이때 정해진 해시들은 인증서와 함께 배포된다. 인증서는 개발자의 개인키를 이용해서 암호화가 되고, 이것은 서명된 인증서라는 것을 의미한다.


2. apk 압축해제 및 구성요소 확인

 1) apk를 zip으로 확장자를 변경한 후 압축을 해제한다.

 

  2) 공개키 인증서와 서명파일이 존재하는 META-INF폴더로 이동한다.

META-INF 폴더는 어플리케이션 무결성을 성립하는 데 도움이 되기 때문에 매우 중요한 리소스이다. 

MANIFEST.MF :  CERT.SF파일과 매우 유사한 리소스를 정의하고있다. 

CERT.SF : 이 파일은 어플리케이션 서명으로 처리되는 어플리케이션의 모든 리소스를 포함하고 있다. 추가로 Jar암호 서명 공간을 가진다.

펌-1 )

MANIFEST.MF / CERT.SF 의 차이점은 MANIFEST.MF는 각 엔트리(실제 파일)에 대한 SHA1-Digest의  Base64로 인코딩된 값을  나타내고,

CERT.SF는 MANIFEST.MF에 기입되어 있는 각 요소들 예를들어 아래와 같이 "\r\n"를 포함한다.

-----------------------------------------------------------------------------------
Name: res/drawable-xhdpi/ic_launcher.png\r\n
SHA1-Digest: 8nhRYA3Pn75x/54RW/GQ97miQkk=\r\n

\r\n

----------------------------------------------------------------------------------- 
의 Digest의  Base64로 인코딩된 값을  나타낸다.
추가로, CERT.SF의 최상단에 기입되어 있는 SHA1-Digest-Manifest는 MANIFEST.MF의 SHA1-Digest의 Base64로 인코딩된 값을 나타낸다.

///////////////////////////////////////////////

CERT.RSA : X.509 v3 인증서이고, 내부 정보는 keytool에 의해 구조화된다.

- Owner : 공개키 소유자를 정의, 소유자 관련 국가/조직에 대한 기본 정보 포함

- Issuer : 소유의 공개키와 관련된 X.509 인증서의 발급자를 정의

- Serial Number : 발행된 인증서의 식별자

- Valid from ... until : 인증서 관련 속성이 검증되는 기간

- Certificate fingerprints: 인증서의 다이제스트 합, 인증서가 변조되지 않았다는 것을 확인하는데 사용


CERT.RSA파일을 확인하려면 openssl이 설치 되어 있어야한다.

openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_cert -text

내용은 위와 같다.


3. 재서명할 키스토어 생성

윈도우와 유닉스/리눅스에서 새로운 키스토어를 생성하려면 다음 명령어를 수행한다.

keytool -genkey -v -keystore [name of keystore] -alias [your_keyalias] -keyalg RSA -keysize 2048 -validity [number of days]

keytool은 키스토어에 암호를 설정하도록 도와주며 입력사항은 잊지 않아야한다. 

인증서 생성 중 위와 같은 질의사항이 있고, 과정이 끝나면 키스토어가 생성된다.


4. 생성한 키스토어로 서명

서명전에 재서명할 apk파일에서 위에서 처럼 압축 해제한 후 META-INF를 삭제하고 다시 압축, 확장자 apk로 변경의 과정을 거친다.

jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore [name of your keysotre] [your .apk file] [your key alias]


5. 재서명한 내역 확인

재서명이 제대로 이루어졌는지 확인하기 위해서 apk를 압축해제한 후 openssl로 CERT.RSA파일을 확인한다.


추가 ) jarsigner: unable to sign jar: java.util.zip.ZipException: invalid entry compressed size (expected 766 but got 770 bytes) 등의 에러 메시지가 발생 하는 경우 apk안의 META-INF폴더를 삭제한 후 서명을 시도한다.



펌-1 )의 출처 : http://shloves.tistory.com/entry/CERTSF-MANIFESTMF-%ED%8C%8C%EC%9D%BC%EC%9D%98-%EB%8B%A4%EB%A5%B8%EC%A0%90

  1. 2017.08.24 09:26

    비밀댓글입니다

 우선 안드로이드 APK 파일은 JAR 와 호환되는 형태로 되어있다. 즉, 코드를 빌드를 하여 나오는 APK  파일은 zip 포맷과 유사한 jar 와도 비슷하다. 특히 서명에 대한 방식은 JAR 와 APK 와 매우 비슷하다. 실제로 서명을 사용해 빌드된 APK 파일 확장명을  zip 으로 변경하고 압축을 풀어보면 META-INF 디렉토리 내부에 이와 관련한 파일이 생성된 것을 볼 수 있다. 

  이 파일은 MANIFEST.MF, CERT.SF, CERT.RSA 파일이다. 


  MANIFEST.MF 파일은 텍스트로 되어있으며 META-INF 디렉토리 내부의 파일을 제외한 APK 패키지 내부 모든 파일(Entry)에 대한 리스트를 보여준다. 각 리스트의 항목에는 파일 경로와 각 파일의 SHA1 해쉬 값(파일을 읽어서 만든다.)을 Base64 로 인코딩 하여 나타내고 있다. 



  CERT.SF 도 마찬가지로 텍스트로 되어있으며, MANIFEST.MF 파일과 구조가 비슷하다. 다만  MANIFEST.MF 에 등록된 각 파일에 대한 해쉬 값은 개인키(APK 빌드할 때 사용되는 인증서, 패스워드와 관련되어 있다고 생각하면 된다) 를 이용하여 SHA1 RSA 로 암호화 한 뒤에 Base64 로 인코딩 되어 있다. 또,  MANIFEST.MF 파일 자체의 위변조를 막기 위하여 MANIFEST.MF 파일의 값도 포함되어 있다. 개인키로 암호화된 값들은 공개키로 복호화할 수 있다. 




 CERT.RSA 는 공개키 인증서이며 소유자, 발행자, 일련번호, 유효기간, 인증서 지문,  알고리즘등이 표시되어 있다.  keeytool 을 이용하여 값을 확인할 수 있다.






   안드로이드 APK 패키지 파일의 서명은 기존 JAVA 에서 제공하는 JAR 파일의 서명과 동일한 방식을 사용한다. 마찬가지로 APK 파일 설치시 JAVA 에서 기본적으로 제공하는 jar 서명 검증 관련 API 를 이용하여 APK 의 무결성을 확인한다. 실제 예를 들자면 Android 내부의  PackageParser 클래스에서도 JarFile 클래스를 이용하여 APK 파일을 열고 내부의 Entry(파일) 리스트를 받아와서 InputStream  을 통하여 파일을 읽는다. 이 과정에서 내부적으로 JarVerifier 클래스가 서명 정보를 확인한다. 서명 정보를 확인할 때, CERT.SF 내부의 암호화된 값들을 공개키를 이용하여 복호화 하고 MANIFEST.MF 의 파일 해쉬 값들과 비교한다. 또, 실제 파일의 SHA1 값들과 MANIFEST.MF 내부의 값들이 일치하는지 검사한다.(순서는 확인하지 못 하였다.) 

   

  이런 과정 덕분에 안드로이드 앱은 빌드시에 사용되는 인증서가 있지 않는한 위변조된 APK 파일은 원본과 구분될 수 있다. 즉, 원본 앱을 업데이트 하는 방식으로 위변조 APK 를 설치할 수 없다. (물론 인증서를 탈취하면 이야기가 달라진다.) 그렇다고 해서 악의적으로 위변조된 APK 파일에 대한 위협이 사라지는 것은 아니다. 만약 원본과 거의 비슷한 모양새로 만들어 빌드하고 다른 인증서로 배포하면 사용자 입장에서는 속아 넘어가서 설치하고 스미싱을 당하거나, 서버와 통신하는 클라이언트 앱의 경우 서버 공격에 대한 빌미를 줄 수 있다. 


  앱이 위변조 되었는지 확인하기 위하여 불가피하게 서버와의 통신이 필요하다. (이 내용은 지금 포스팅과 시리즈로 엮어서 다음 포스팅에서 다루겠다.) 서버로 APK 파일에 대한 고유 키 값을 보내줘야할 때, 인증서 정보(인증서 지문) 만큼 확실한 값은 없다. 


  우선 APK 파일을 압축을 풀면 나오는 META_INF/CERT.RSA  파일에 대한 인증 정보를 확인하는 방법은 다음과 같다.


 keytool -printcert -file  CERT.RSA 

  

  이 명령어를 실행하면 인증서 정보(인증서 지문, Certificate Fingerprint)를 얻을 수 있다.

   

안드로이드 코드상에서 얻는 방법은 다음과 같다. 

Context context = getApplicationContext();
PackageManager pm = context.getPackageManager();
String packageName = context.getPackageName();
String cert = null;
try {
    PackageInfo packageInfo = packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
    Signature certSignature =  packageInfo.signatures[0];
    MessageDigest msgDigest = MessageDigest.getInstance("SHA1");
    msgDigest.update(certSignature.toByteArray());
    cert = Base64.encodeToString(msgDigest.digest(), Base64.DEFAULT);
} catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
}

  

     

  위 코드에서 cert  변수에 이 앱을 빌드할 때 사용된 인증서의 정보 값이 들어간다. 이 값과 APK 파일의 CERT.RSA 파일로부터 'keytool -printcert -file CERT.RSA' 으로 얻은 값을 Base64 로 인코딩 하여 비교하면 동일함을 알 수 있다.  물론 해쉬 알고리즘을 SHA-256 또는 MD5 를 이용하여 요약해도 마찬가지다.  


  하지만, 서버나 외부에서 APK 파일의 인증서 정보 값을 얻을 때는 안드로이드 내부에서 얻는 코드를 이용할 수 없다. 

  그래서 다음과 같은 클래스를 만들어 보았다.   APK 파일의 인증을 검증하는 과정도 포함되어 있으며 잘못된 인증 값을 갖고 있을 경우 예외를 발생시킨다.

  테스트 완료 하였으며, 아마도 안드로이드 내부에서 다른 APK 파일 인증 정보를 읽어오는 것도 가능할 것이다. 


  (코드가 다소 길다. 읽기 쉽도록 소스 파일도 함께 첨부한다.  )

 APKCertExtractor.java



APKCertExtractor:

import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.xml.bind.DatatypeConverter; import kr.re.dev.server.APKCertExtractor.APKCertExtractionException.ErrType; /** * APKCertExtractor * * - APK 파일내의 인증 정보를 SHA1, base64 로 만들어 반환한다. * - APK 파일의 유효성을 검사한다. * - 2개 이상의 서명을 사용하는 APK 파일에 대하여 사용할 수 없다. * 하지만, Play store 에서도 2개 이상의 서명을 갖고 있는 APK 를 허용하지 않는다. * * ::사용법 * - APKCertExtractor.execute(apk 파일 경로); * - 인증 검증 실패시 APKCertExtractionException 예외 발생. * * http://dev.re.kr */ public class APKCertExtractor { public static void main(String[] args) { String cert = ""; try { cert = APKCertExtractor.execute( "test.apk"); } catch (APKCertExtractionException e) { e.printStackTrace(); cert = e.getMessage(); } System.out.println(cert); } /** * 인증서 지문을 반환한다. * @param apkFilePath APK 파일의 경로. * @return * @throws APKCertExtractionException */ @SuppressWarnings("resource") public static String execute(String apkFilePath) throws APKCertExtractionException { try { // JarFile 클래스는 java.util.zip.ZipFile 를 상속받아 구현되었다. // jar 파일에 대한 설명은 다음 블로그 페이지에 자세히 소개되어 있다. // http//www.yunsobi.com/blog/62 // 이 클래스는 jar(zip) 의 엔트리(파일 정보) 및 파일을 읽기 위하여 사용된다. JarFile jarFile = new JarFile(apkFilePath); // APK 파일 내의 AndroidManifest.xml 의 엔트리와 인증 정보를 읽어온다. JarEntry manifestEntry = jarFile.getJarEntry("AndroidManifest.xml"); if(manifestEntry == null) throw APKCertExtractionException.newInstance(ErrType.WrongAPKFormat, apkFilePath, null); Certificate[] certs = loadCertificates(jarFile, manifestEntry); if(certs == null || certs.length == 0) throw APKCertExtractionException.newInstance(ErrType.WrongCert, manifestEntry.getName(), null); Certificate cert= certs[0]; // APK 파일 내의 모든 엔트리의 인증을 검증한다. verifCertificates(jarFile, cert); // 바이트 배열 타입의 인증 정보를 SHA1 Base64로 변환. String hash; try { hash = certToSHA1(cert); } catch (CertificateEncodingException e) { e.printStackTrace(); // 인증서에 문제 있을 때 발생. throw APKCertExtractionException.newInstance(ErrType.WrongCert, apkFilePath, e); } return hash; } // 파일 경로등에 문제 있을 때 발생한다. catch (IOException e) { throw APKCertExtractionException.newInstance(ErrType.ReadFail, apkFilePath, e); } } /** * APK 내부 파일의 인증을 확인한다. * @param jarFile * @param cert */ private static void verifCertificates(JarFile jarFile,Certificate cert) throws APKCertExtractionException { Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry jarEntry = (JarEntry) entries.nextElement(); // 서명되지 않는 디렉토리인 META-INF 는 건너뛴다. if (jarEntry.isDirectory() || jarEntry.getName().startsWith("META-INF/") || jarEntry.getName().contains(".DS_Store")) { continue; } Certificate[] certs = null; certs = loadCertificates(jarFile, jarEntry); if(certs == null || certs.length == 0) throw APKCertExtractionException.newInstance(ErrType.WrongCert, jarEntry.getName(), null); Certificate localCert = certs[0]; // 인증 정보가 없는(서명이 되지 않은) 파일 발견. // APK 압축을 풀고 임의의 파일을 넣거나 제거하여 다시 APK 파일로 압축했을 때 발생한다. if (localCert == null) { try { jarFile.close();} catch (IOException e) { e.printStackTrace(); } throw APKCertExtractionException.newInstance(ErrType.ForgeryAPK, jarEntry.getName(), null); } // 인증 정보가 서로 다른 엔트리 발견. // 물론 설치는 안 되겠지만, 위변조 시도되는 앱으로 의심된다. else if (!cert.equals(localCert)) { try { jarFile.close();} catch (IOException e) { e.printStackTrace(); } throw APKCertExtractionException.newInstance(ErrType.ForgeryAPK, jarEntry.getName(), null); } } } /** * Certificates 객체를 반환한다. * @throws APKCertExtractionException */ private static java.security.cert.Certificate[] loadCertificates(JarFile jarFile, JarEntry jarEntry) throws APKCertExtractionException { if(jarEntry == null || jarFile == null) return null; try { // JarEntry 로부터 Certificate 객체를 얻기 위해서는 JarEntry 를 검증하기 위하여 끝까지 다 읽어야 한다. // 이 과정에서 내부적으로 JarVerifier 클래스를 통하여 인증에 대한 검증이 이뤄어진다. byte[] buffer = new byte[1024]; InputStream is = jarFile.getInputStream(jarEntry); try { while (is.read(buffer, 0, buffer.length) != -1) {} // 테스트 결과 MATA-INF 폴더의 파일 내에 해당 엔트리의 인증값들은 있지만, // 실제 파일이 존재하지 않을 경우 아래 예외가 발생한다. // 역시 위변조된 앱일 가능성이 크다. // 물론 이 경우도 일반적인 안드로이드 폰에서는 설치조차 되지 않는다. } catch(SecurityException e) { throw APKCertExtractionException.newInstance(ErrType.ForgeryAPK, jarEntry.getName(), e); } is.close(); buffer = null; return (java.security.cert.Certificate[])jarEntry.getCertificates(); } catch (IOException e) { System.err.println("Exception reading " + jarEntry.getName() + " in " + jarFile.getName() + " " + e); } return null; } /** * 서명 깂을 SHA1 해쉬로 변경하여 Base64 로 만든 String 값으로 반환한다. * @param cert * @return * @throws CertificateEncodingException */ private static String certToSHA1(Certificate cert) throws CertificateEncodingException { byte[] certWith = null; // X509 인증 정보를 ASN.1 DER 구조의 byte 배열로 반환한다. certWith = cert.getEncoded(); MessageDigest md = null; try { // 인증 정보를 SHA1 해쉬로 변경. md = MessageDigest.getInstance("SHA1"); md.update(certWith); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return DatatypeConverter.printBase64Binary(md.digest()); } /** * 인증서 추출 예외상황. * @author ice3x2 */ public static class APKCertExtractionException extends Exception { private static final long serialVersionUID = 6796836839897143903L; private ErrType mError; protected static APKCertExtractionException newInstance(ErrType errType, String path, Throwable throwable) { String message = ErrType.toMessage(errType) + " (" + path + ")"; APKCertExtractionException apkCertExtractionException = (throwable == null)?new APKCertExtractionException(message):new APKCertExtractionException(message, throwable); apkCertExtractionException.mError = errType; return apkCertExtractionException; } private APKCertExtractionException(String message, Throwable throwable) { super(message, throwable); } private APKCertExtractionException(String message) { super(message); } /** * 에러 타입을 반환한다. * @return */ public ErrType getError() { return mError; } public static enum ErrType { /** * 잘못된 APK 파일 포맷. */ WrongAPKFormat("Wrong APK file format."), /** * APK 파일을 읽을 수 없음. */ ReadFail("APK file read failed."), /** * 잘못된 인증서. 파일에 대한 인증 정보가 존재하지 않는다. */ WrongCert("Wrong certificate. Certificate verified failed."), /** * 위변조가 의심되는 APK. 인증 정보 검증에 문제가 있다. */ ForgeryAPK("Wrong certificate. This package is suspected with forgery apk."); private String message; private ErrType(String value) { message = value; } protected static String toMessage(ErrType errType) { return errType.message; } } } }



출처 : Dev.re.kr (http://dev.re.kr/70)

안드로이드 최신 버전의 SDK에서 pm명령어 모음입니다. adb shell로 접근하여 얻을 수 있는 명령어입니다. adb shell 접근 방법은 아래에서 설명하겠습니다.

명령어를 통해 앱을 설치하거나, 삭제하고, 사용자를 추가하고, 삭제 할 수 있는 명령어도 있습니다. 현재 기본값으로 셋팅 된 설치 경로를 가져오는 get-install-location 도 있습니다. 일반적으로 [0/auto]가 설정되어 있습니다

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
usage: pm list packages [-f] [-d] [-e] [-s] [-e] [-u] [FILTER]
       pm list permission-groups
       pm list permissions [-g] [-f] [-d] [-u] [GROUP]
       pm list instrumentation [-f] [TARGET-PACKAGE]
       pm list features
       pm list libraries
       pm path PACKAGE
       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH
       pm uninstall [-k] PACKAGE
       pm clear PACKAGE
       pm enable PACKAGE_OR_COMPONENT
       pm disable PACKAGE_OR_COMPONENT
       pm disable-user PACKAGE_OR_COMPONENT
       pm set-install-location [0/auto] [1/internal] [2/external]
       pm get-install-location
       pm createUser USER_NAME
       pm removeUser USER_ID
  
pm list packages: prints all packages, optionally only
  those whose package name contains the text in FILTER.  Options:
    -f: see their associated file.
    -d: filter to only show disbled packages.
    -e: filter to only show enabled packages.
    -s: filter to only show system packages.
    -3: filter to only show third party packages.
    -u: also include uninstalled packages.
  
pm list permission-groups: prints all known permission groups.
  
pm list permissions: prints all known permissions, optionally only
  those in GROUP.  Options:
    -g: organize by group.
    -f: print all information.
    -s: short summary.
    -d: only list dangerous permissions.
    -u: list only the permissions users will see.
  
pm list instrumentation: use to list all test packages; optionally
  supply <target-package> to list the test packages for a particular
  application.  Options:
    -f: list the .apk file for the test package.
  
pm list features: prints all features of the system.
  
pm path: print the path to the .apk of the given PACKAGE.
  
pm install: installs a package to the system.  Options:
    -l: install the package with FORWARD_LOCK.
    -r: reinstall an exisiting app, keeping its data.
    -t: allow test .apks to be installed.
    -i: specify the installer package name.
    -s: install package on sdcard.
    -f: install package on internal flash.
  
pm uninstall: removes a package from the system. Options:
    -k: keep the data and cache directories around after package removal.
  
pm clear: deletes all data associated with a package.
  
pm enable, disable, disable-user: these commands change the enabled state
  of a given package or component (written as "package/class").
  
pm get-install-location: returns the current install location.
    0 [auto]: Let system decide the best location
    1 [internal]: Install on internal device storage
    2 [external]: Install on external media
  
pm set-install-location: changes the default install location.
  NOTE: this is only intended for debugging; using this can cause
  applications to break and other undersireable behavior.
    0 [auto]: Let system decide the best location
    1 [internal]: Install on internal device storage
    2 [external]: Install on external media
</target-package>

 

 

이 중 가장 많이 사용하는 명령어가 앱을 강제로 SD카드로 이동하는 명령어 일 겁니다. 해당 명령어도 아래와 같이 변경되었습니다.

설치 경로의 기본 값을 가져오는 명령어와 설정하는 명령어입니다.

 

pm get-install-location //기본값으로 지정된 설치 경로를 불러옵니다.
pm set-install-location 0/1/2 //강제로 경로를 설정합니다.

 

 

[adb를 이용한 apk 파일 추출 및 인스톨]

 

c:\> adb shell


# pm list packages -f

...

package:/system/app/SettingsProvider.apk=com.android.providers.settings
package:/system/app/TtsService.apk=android.tts
package:/system/app/Mms.apk=com.android.mms << 이걸 가져오고 싶으면...
package:/system/app/MediaProvider.apk=com.android.providers.media
package:/system/app/CertInstaller.apk=com.android.certinstaller
package:/system/app/DownloadProvider.apk=com.android.providers.downloads

...

 

# exit

 

c:\>adb pull /system/app/Mms.apk .

adb pull (가져올 파일 경로/파일명.apk) (받을 경로/파일명.apk)




출처 : 코드 클리핑 (http://www.dreamy.pe.kr/zbxe/CodeClip/163974)

+ Recent posts