An Empirical Study of Cryptographic Misuse in Android Applications

정보

Keywords

  • 안드로이드
  • 암호화
  • 오용

배경

  • 개발자들은 안드로이드에서 비밀번호 및 개인 정보를 기기에서 보호하기 위하여 암호화 API를 사용함
  • 암호학자들은 제대로 된 사용 방법이 강력한 보안을 보장해 주며 잘못된 방법이 문제로 이어질 수 있음을 알고 있음
  • 하지만 개발자는 올바른 방식으로 암호화 API를 사용하는 방법을 알고 있는지 모르겠음

목표

  • 개발자들이 암호화 API를 제대로 사용하는지를 확인해 보자.
  • 특히 잘 알려진 2개의 보안 표준에 중점을 둠
    1. chosen plaintext attacks (IND-CPA)
    2. cracking resistance
  • 점검 플랫폼은 안드로이드
    1. 다양한 개인정보이 위치하고 있음
    2. 안정적인 Java 암호화 API를 사용함
    3. 점검할 수 있는 모수가 큼

방법론

  • Google Play 마켓에 올라가 있는 프로그램을 자동으로 확인하는 프로그램 분석 기법을 개발함
  • CryptoLint, Androguard Android 프로그램 분석 프레임워크 기반의 정적 분석 도구
    1. Androguard가 Dalvik byte-code를 disassemble해주면, 이를 IR로 바꿈
    2. 이 IR을 비슷한 그룹군으로 구분하고, 프로시저 간 분석을 통해 sCFG(Control Flow Graph)를 만듦
      • 모든 메소드들은 SSA(single static assignment)로 변환됨
      • 모든 클래스들의 계층 구조를 추출함
      • 이를 기반으로 sCFG를 생성함
    3. slicing 알고리즘으로 특정 지점의 변수를 기준으로 순방향 또는 역방향으로 탐색함
    4. 보안 속성들을 평가함

결과

  • 암호화 API를 사용하는 대략 88%의 어플리케이션에서 하나 이상의 실수를 하는 것을 발견함

결론

  • 안드로이드 어플리케이션에서 전반적인 암호화 보안을 향상시키는 방향으로 분석 기반의 구체적인 수정 사항을 제안해 줌.

아까 설명했듯이 엑티비티가 실행되면

처음에 onCreate 메소드가 호출됩니다.

 

onCreate 메소드가 길어서 일부만 가져왔습니다.

코드를 보면

저장된 인스턴스 상태를 가져오며

엑티비티 메인으로 값을 설정하는 등의 초기 설정을 합니다.

 


그다음으로는 onStart 메소드가 호출되어야 합니다.

하지만 onStart 메소드는 보이지 않습니다.

 

아마도 super 클래스의 메소드를 오버라이드 하지 않고

그냥 상속받아서 사용하는 것 같다고 추측하고 있지만,

정확히는 잘 모르겠습니다.

 

이런건 개발을 해보면서 배워야 하는데,

안 해봐서 잘 모르겠습니다.

 

이럴 경우에는 다시 onStart 메소드로 돌아가서,

onCreate 메소드에서 어떤 함수들을 호출하는지 확인해봐야 합니다.

 


onCreate 메소드의 마지막 부분을 확인해 보면,

com/kozhevin/rootchecks/util/CheckTask 엑티비티에서

init 메소드를 호출하고 execute 메소드를 호출합니다.

 


CheckTask 엑티비티에서 init 메소드부터 확인해 보면

중간에 asyncTask를 호출하는 부분이 있습니다.

 

네, 비동기로 뭔가를 하는 것 같습니다.

어플리케이션에서는 엑티비티간의 전환은 보이지 않았으므로

백그라운드로 작업을 하는 것 같습니다.

 


같은 엑티비티 내에서

백그라운드 작업을 하는

doInBackground 메소드입니다.

 

쭉 읽다가 보면

결과값이 비어있는 경우

MeatGrinder 엑티비티의 getInstance 메소드와

isLibraryLoaded 메소드를 호출합니다.

 


MeatGrinder의 isLibraryLoaded 메소드에서는

native-lib이라는 이름의 라이브러리를

로드하는 것을 확인할 수 있으며,

여러 메소드를 native 하게 로드하는 것을 확인할 수 있습니다.

 


해당 라이브러리를 ida로 열어보면,

어플리케이션에서 루팅을 확인하는

키워드들이 보이는 것을 확인할 수 있습니다.

 


자 이제부터가 드디어 본 주제였던

루팅을 탐지하는 방법에 대하여 이야기해보겠습니다.

 

안드로이드에서 루팅을 하게 된다면, 크게

- 루팅 하는 과정에서
- 루팅을 활용하는 과정에서

흔적이 발생합니다.

 

루팅을 탐지할 때는

기본적으로 이러한 흔적으로 루팅을 탐지합니다.

 

예를 들면 매번 루트의 권한을 획득하는 건 귀찮으니깐,

이 과정을 생략하기 위해 su 바이너리를 이용합니다.

하지만 이 바이너리의 존재로 루팅이 탐지될 수도 있습니다.

 

또한 루트 권한을 활용하는

다른 어플리케이션의 흔적으로

루팅이 되었는지 확인할 수 있습니다.

 


가장 처음으로는 build.prop 파일로

루팅이 되었는지를 확인할 수 있습니다.

 

이 build.prop 파일은 제조사에서 제품을 출시할 때

제품에 대한 정보나 build 정보 등을 포함하여 출시합니다.

 

그중에서 ro.build.tags 는

출시 당시에 'release-key'로 되어 있지만,

루팅 과정에서 test-keys 나 dev-keys 같은

다른 키로 변경되는 경우가 있으며,

이 키가 변경되었는지 확인을 통해

루팅을 탐지할 수 있습니다.

 


그 외에도

ro.debuggable, service.adb.root,

ro.secure, sys.initd, ro.build.selinux 등을

확인하여 루팅이 되어 있는지를

확인하기도 합니다.

 


아까 예를 들었던 su 바이너리를 통해서

루팅을 탐지할 수 있다는 것을 기억할 것입니다.

 

근데 혹시 이 su 바이너리가

정확히 뭐하는 바이너리인지 아시나요?

 

su는 switch user 즉 사용자를 변경하는 바이너리로,

인자 없이 단독으로 su가 오게 된다면

기본적으로 root로 인식하여

root로 사용자를 변경할 수 있습니다.

 

따라서 이 바이너리의 존재로 루팅을 탐지하기도 합니다.

 


그다음으로

또 su 바이너리를 찾는 것입니다.

 

오른쪽에 보이는 폴더들의 리스트에서 su 바이너리가 있는지 찾습니다.

 


아까 which su를 통해서도 su 바이너리를 찾았고,

방금 전에는 특정 폴더들에서 su 바이너리가 있는지 찾았습니다.

 

이 둘의 차이점은 무엇일까요?

 


차이점은 환경변수에서

PATH 변수에 등록된 주소에서 차이점이 있습니다.

 

which 명령어의 경우에는

이 PATH 변수에 등록된 환경변수에서

인자로 들어온 이름의 실행파일을 찾습니다.

 

즉 이 PATH 변수에 등록이 안된 경로에 대해서는,

which 명령어로 su 바이너리가 존재하는지 확인할 수 없습니다.

 

따라서 su 바이너리가 자주 있거나,

있을 가능성이 높은 폴더들에 대하여

추가로 확인을 하는 것입니다.

 


그다음으로는 Superuser.apk 어플리케이션의

유무를 확인합니다.

 

이 superuser.apk 는

다른 어플리케이션에서 su를 이용할 수 있는 권한을

관리해주는 어플리케이션으로,

이 어필리케이션에 접근할 수 있는지 확인하는 방식으로,

루팅을 탐지합니다.

 


그다음으로는 busybox의 존재로

루팅을 탐지할 수 있습니다.

 

안드로이드의 경우 리눅스를 기반으로 하여,

그 커널 위에서 동작하는 것은 다 들어보셨을 겁니다.

 

이렇게 리눅스 기반이기는 하지만 완전한 리눅스가 아니기에,

기존에 사용하던 여러 리눅스 명령어를 사용하지 못합니다.

 

따라서 이 busybox를 설치하여

여러 명령어를 사용할 수 있게 합니다.

 

busybox는 여러 스크립트 들을 포함하는 실행파일로,

이 busybox로 여러 명령어들을 대체하여

명령어들을 사용할 수 있는 것처럼 해줍니다.

 


다음은 xposed 관련 파일들로

루팅을 탐지할 수 있습니다.

 

xposed는 일종의 프레임워크로

여러 모듈을 적용하여

시스템이나 다른 응용프로그램의 동작을

변경할 수 있게 해 준다고 합니다.

 

저는 써본 적이 없어서 잘은 모르겠습니다.

 


앞에서 언급한 파일들 외에도

magisk 같은 루팅을 이용하는 파일들이

존재하는지 확인합니다.

 


또한 폴더의 권한으로

루팅 여부를 판단할 수 있습니다.

 

system 폴더를 예로 들어 설명하겠습니다.

system 폴더의 경우 기본적으로 읽기 전용 폴더로,

이는 root 권한으로도 수정할 수 없습니다.

 

이 안에 있는 파일들을 수정하기 위해서는

system 폴더를 remount 하여야 하는데,

이러한 폴더 권한의 변경으로

루팅을 탐지하는 경우도 있습니다.

 


마지막으로 후킹을 탐지하는 방법입니다.

 

후킹이란 프로그램에서 함수, 이벤트, 메시지 등이 호출될 때,

이를 중간에서 낚아채가는 것을 말합니다.

 

이 안드로이드에서 후킹 하는 것에 관련된 부분은

주성이가 더 잘 설명해 줄 것이라 믿고,

저는 후킹으로 뭘 할 수 있는지

정도만 보여드리겠습니다.

 


* 해당 슬라이드의 사진 부분은 공개할 수 없습니다.

 

이것은 어느 메신저에서

자체 프로토콜을 빌드하는 부분의

결과 값을 후킹 한 것입니다.

 

전에 상섭이형 발표에서는

치트엔진으로 하셨다고 들은 것 같은데,

이렇게 프리다로 후킹을 해서도

그 내용을 확인할 수도 있습니다.

 


과제라고는 하는데 그냥 선택사항입니다.

 

첫 번째 과제는 오늘 native root checker에서

루팅을 탐지하는 방법을 소개했는데,

이를 참고하여 루팅 탐지를

우회하는 스크립트를 작성하는 것입니다.

 

두 번째 과제는

오늘 소개한 루팅 탐지 방법 외에도

여러 창의적인 방법들이 존재하는데

그중 하나를 찾아 정리해 보고,

그것은 어떤 식으로 탐지를 하는 건지

공부해 보는 것입니다.

 

할 사람들은 기한 없이 하시면서 질문 주시면 되고,

제출은 onsoim@gmail.com으로 보내주시고 알려주시면 됩니다.

 


그리고 안드로이드 쪽 공부를 한다면 도움이 될 자료들입니다.

 

첫 번째는 아시는 분은 아는 그 발표자료입니다.

물론 내용은 좀 삭제되어 있지만

난독화 쪽으로 흥미로운 내용을

많이 포함하고 있습니다.
한번 쭉 보시고, 나중에 더 공부하고 다시 보셔도 좋을 것 같습니다.

 

두 번째는 아까 간략하게 설명한 엑티비티 라이프사이클에 관한 자료입니다.

정리가 아주 잘 되어 있습니다.

 

이상으로 끝내겠습니다. 감사합니다.

원래는 nox로 환경을 맞춰서 같이 해보려고 했지만,,,

 

애플의 보안 정책으로 nox를 사용할 수가 없어서,

제가 해온 것으로 대체하여 진행하겠습니다. ㅜ

 


분석을 하려면 제일 먼저 apk부터 구해야 합니다.

 

apk를 구하는 방법으로는 playstore에서 설치 후 추출하거나,

apkpure.com에서 검색을 해서 받는 방법이 있습니다.

 


playstore을 통해 설치한 apk를 추출하는 방법입니다.

 

제일 먼저 해야 할건 뭘까요?

바로 어플리케이션을 설치하는 것입니다.

 

그다음으로 해야 할 것들이 추출을 위하여

노트북과 안드로이드 장치를 adb로 연결하고,

추출할 패키지의 이름을 확인한 뒤

해당 패키지를 추출하면 끝납니다.

 


adb 로 안드로이드 장치에 연결하는 방법입니다.

에뮬레이터의 경우 각 에뮬레이터 별로 사용하는 adb 포트로 접속을 하고,
실제 기기의 경우에는 개발자 옵션 중 하나인 usb 디버깅을 허용해주면 됩니다.

 

[*] nox : 62001

 


패키지의 목록은 pm list packages -f로 확인할 수 있으며,

설치된 패키지가 많을 경우에는 찾기가 힘들기에,

적당히 grep으로 추려서 확인하시면 됩니다.

 


추출할 패키지의 이름을 확인했다면,

pull 옵션으로 해당 apk를 추출할 수 있습니다.

 

반대로 adb로 apk를 설치할 수도 있습니다.

adb의 옵션으로 install을 주면 apk를 설치할 수 있으며,

-r 옵션을 추가로 주는 경우에는 이미 존재하는 패키지를 재설치할 수 있습니다.

 

하지만 기존에 설치된 서명과 설치하고자 하는 서명이 다른 경우에는

다른 패키지로 인식하여 재설치할 수 없습니다.

그런 경우에는 기존에 충돌되는 패키지를 삭제하고, 설치를 진행해야 합니다.

 


지금까지 사용한 adb 명령어들입니다.

 


apkpure.com 에서 apk를 구하는 방법은 간단합니다.

apk 이름만 검색하면 쉽게 다운받을 수 있습니다. 참 쉽쥬 ~?

 


자 이제 apk 를 구했으니 본격적으로 분석을 시작해 봅니다.

apk의 디코드와 빌드는 apktool을 이용할 수 있습니다.

 

apktool의 옵션으로

d를 주면 디코드 되며,

b를 주면 빌드되어 dist 폴더에서 확인할 수 있습니다.

 


루팅 탐지 루틴을 분석하기 위하여

안드로이드 어플리케이션을 분석할 수 있는 방법을 2가지 정도 소개하겠습니다.

 

물론 정해진 분석 방법은 없으며,

맞는 방법이 아닐 수도 있습니다.

본인이 편한 방법으로 분석을 진행하면 됩니다.

 

방법은 크게 Top-down 방식과 Bottom-up 방식이 있습니다.

쉽게 이야기하자면 위에서부터 아래로, 아래에서부터 위로 분석하는 방식입니다.

 

예제를 들며 설명을 하면 이해하기 편할 것 같아서,

native root checker를 대상으로 분석하면서 설명하겠습니다.

 

우선 이 어플을 선택한 이유는

native 하게 루팅을 탐지해서

조금 뒤에서 루팅 탐지 방법을 설명할 때,

익숙하지 않은 달빅 바이트코드보다는

cpp 코드가 더 이해하기 쉬울 것 같아서 골랐습니다.

 


Top에서 내려가는 방식보다

Bottom에서 올라가는 것이 더 간단하기에 이것부터 설명하겠습니다.

 

Bottom-up 분석 방식은 끝에서부터 원하는 분석 부분까지 올라가는 방식입니다.

 


분석을 하기에 가장 먼저 찾아야 할 것은 시작점을 찾는 것입니다.

여러 방법 중에서 가장 쉽다고 생각하는 방법을 하나 소개하겠습니다.

다시 한번 말하지만 분석 방법에 있어 항상 정답은 없습니다.

 

어플리케이션을 실행시켜 보면 각 항목 별로 탐지 결과를 보여주는데,

이 항목들은 문자열로 처리를 했을 것이다라고 가정을 하고 시작합니다.

 

그래서 확인 차 TEST KEYS를 검색해 보니,

strings.xml 파일에서 확인을 할 수 있습니다.

 


strings.xml 은 다국적의 언어를 처리하기 쉽게 하기 위한 용도의 파일로,

사용자가 선택한 언어에 따라 문자열을 동일한 name으로 처리할 수 있습니다.

 


이전에 strings.xml 에서 찾은 name을 검색해 보면,

public.xml에서도 사용된 것을 확인할 수 있습니다.

 

strings.xml 이 변수명과 그 값을 저장한 파일이라면,

public.xml 은 변수명과 변수의 주소를 저장한 파일입니다.

 


마지막으로 이 주소를 검색해 보면,

달빅 코드 내부에서는 이 주소를 이용하는 것을 확인할 수 있습니다.

 

아직 분석은 안 했지만 뭔가 감이 오는 switch 문이 보입니다.

이런 식으로 문자열을 기준으로 하여,

분석 지점을 쉽게 파악할 수 있습니다.

 


하지만 몇 번 말했듯이 이 방법도 정답이 아닐 수도 있습니다.

이것처럼 잘못된 시작은 삽질의 시작이 될 수도 있습니다.

 

루팅 탐지의 결과를 알려주는 맨 하단부의 초록색 창이

문자열일 것이라고 생각하고 진행을 한다면,

그 문자열은 찾지 못할 것입니다.

왜냐하면 이 문자열은 사진(png)으로 처리되어 있기 때문입니다.

 

그래서 저 같은 경우에는 이렇게 몇 개 해보고 안되면,

Top-down 방식으로 다시 분석을 합니다.

 


그다음으로는 Top-down 분석법입니다.

 

위에서부터 아래로, 큰 범주에서 목표를 향해 분석하는 방식입니다.

 


마찬가지로 Top-down 분석을 시작하려면 분석을 할 시작점부터 찾아야 합니다.

 

apk의 경우 그 시작점은 메인 엑티비티로,

메인 엑티비티는 apk 파일 내의

안드로이드 메니페스트 파일에서 확인할 수 있습니다.

 


이 사진은 메인 엑티비티에 해당하는 엑티비티입니다.

 

그다음은 어디를 봐야 할까요?

그것을 알려면 안드로이드 라이프사이클에 대하여 알아야 합니다.

 


안드로이드 라이프 사이클이라고 부르는 관계도입니다.

정확히 말하면 엑티비티 라이프 사이클입니다.

안드로이드 어플리케이션에는 여러 엑티비티가 있고,

이들 중 하나의 엑티비티가 실행되고 소멸하기까지의 관계도를 도식화한 것이 엑티비티 라이프 사이클입니다.

 

그렇다면 이 엑티비티 라이프사이클은 왜 중요할까요?

안드로이드의 경우 PC 와는 다르게 제한된 환경으로,

정해진 양의 자원을 여러 어플리케이션에서 나눠서 사용해야 합니다.

따라서 각 어플리케이션 별로 사용하는 자원을 효율적으로 관리하기 위하여 나온 것이 라이프 사이클입니다.

어디서 어떻게 사용할지 알아야,

자원을 같이 사용할 수 있는 거죠.

 

처음에 엑티비티가 호출되면 시스템에서 가장 먼저 호출하는 메소드가

onCreate( ) 메소드입니다.


onCreate( ) 메소드는 엑티비티가 처음 생성될 당시 한 번만 호출되며,

데이터를 초기화하는 단계입니다.
파라미터로 넘어온 데이터를 바인딩하거나

이전에 저장된 instance의 값 등을 불러오는 역할을 합니다.

 

onStart( ) 메소드에서는

본격적으로 엑티비티와 사용자가 상호작용 할 수 있도록 준비하는 단계입니다.


예를 들면 UI를 관리하는 코드를 초기화하여,

엑티비티를 포그라운드로 보내 상호작용을 할 수 있도록 합니다.

 

onResume( ) 메소드에서는 엑티비티를 화면에 보여주면서,

사용자와 상호작용을 하는 단계입니다.


사용자와 상호작용을 하며 엑티비티가 실행되며,

별도의 이벤트가 있기 전까지 이 상태에 머무릅니다.

 

onPause( ) 메소드는 다른 엑티비티가 호출되어,

잠시 멈출 때 호출됩니다.


화면에서 뒤에 흐리게 되어 존재는 하지만,

상호작용을 할 수 없는 상태입니다.


예를 들면 게임에서 일시정지 버튼을 눌러,

게임 관련 엑티비티가 멈춘 상태입니다.

 

onStop( ) 메소드는 엑티비티가 화면에서 사라질 때 호출되며,

필수로 저장해야 하는 데이터 등을 저장합니다.


예를 들면 게임을 하고 있는데 전화가 온다면,

화면이 변경됨과 동시에

시스템에서 게임의 엑티비티에 onStop( ) 메소드를 콜백 합니다.

 

그 후 게임으로 다시 돌아가게 된다면,

onRestart( ) 메소드를 호출하여

엑티비티의 onStart( ) 메소드를 호출할 수 있게 준비합니다.

 

마지막으로 onDestroy( ) 메소드에서는 onCreate( ) 메소드에서 할당했던 변수 등을 해제하며,

엑티비티를 정리합니다.

 

짝을 지어 보자면

onCreate( ) 메소드와 onDestory( ) 메소드가 대치되며,

nStart( ) 메소드와 onStop( ) 메소드와 대치됩니다.

 

마지막으로 정리해보면

처음 엑티비티가 호출되면,

onCreate -> onStart -> onResume 메소드를 거치며 엑티비티가 실행되며,

사용자와 상호작용하다가

특별한 이벤트가 발생하면 onPause 메소드가 로 호출되어 일시 정지되며,

사용자의 중지 요청 등으로 onStop 메소드가 호출되며,

onDestroy 메소드를 거쳐 엑티비티가 종료합니다.

 

네 그러면 native root check 어플리케이션을 이 라이프 사이클에 맞춰서 분석해 보겠습니다.

오늘 제가 발표할 주제는 안드로이드 101입니다.

원래는 안드로이드 어플리케이션에서 어떻게 루팅을 탐지하는지에 대해서만 발표하려고 하였으나,
처음 안드로이드 공부할 때 많이 막막했던 것이 생각나기도 하고,
처음 하는 분들에게 조금이라도 도움을 드릴수 있고자 안드로이드에 대한 기본적인 내용을 추가하고,
루팅 탐지 부분의 비중을 낮춰서 안드로이드 101으로 발표 주제를 정했습니다.

 


목차는 크게 다음과 같습니다.

 - APK에 대한 기본적인 지식들

 - APK 분석 방법

 - 루팅 탐지 방법

 


아시다시피 요즘 날의 모바일 어플리케이션 생태계는 크게 안드로이드와 iOS로 나눌 수 있습니다.

그중에서 non-native code 인 달빅 바이트 코드를 사용하며,
상대적으로 디버깅하기 쉬운 안드로이드 어플리케이션에 대하여 이야기해보려고 합니다.

 


앞에서 non-native code에 대하여 언급했는데,
조금은 헷갈릴 수 있는 개념이라 간단하게 정리하고 넘어가겠습니다.
정확하게 까지는 아니더라도 대충 native 한 게 어떤 느낌인지 정도만 아셔도 괜찮을 것 같습니다.

 

우선은 managed code입니다.

 

이 managed code는 microsoft에서 2003년에 비주얼 스튜디오 2003을 출시하면서 발표한 개념입니다.
마이크로소프트 사의 C#이나 VB .NET 이 managed code에 포함되며,
그리고 많이들 사용하는 Python, Java 등도 여기에 포함됩니다.

 

소스코드가 실행되기까지의 과정에 대하여 간단하게 이야기해보면,
사용자가 작성한 소스코드는 IL 즉 중간 언어로 컴파일되는데,
이 IL 은 어셈블리어 정도로 생각하시면 될 것 같습니다.
그 후 IL 파일이 정상인지 확인한 후 runtime에 사용되는 메소드를 호출합니다.

 

이 지터에서 runtime 시에 사용되는 메모리나 예외 등을 처리해주기 때문에,
파이썬처럼 변수를 따로 할당하지 않아도 알아서 메모리를 할당해 주고 해제해줍니다.

 

다음으로는 unmanaged code입니다.

 

managed code에 반대되는 개념으로 예전부터 있던 전통적인 C 등이 여기에 포함됩니다.

 

소스코드는 각 환경에 맞게 바로 컴파일되어 기계어로 된 프로그램의 형식으로 실행되며,
C언어를 하면서 많이들 느꼈겠지만 필요한 메모리를 직접 할당(malloc) 및 해제를 해줘야 합니다.

 

참고로 알고리즘 할 때 많이 사용되는 C++ 은 managed code 가 될 수도, unmanaged code 가 될 수도 있습니다.

 

마지막으로 좀 모호하다고 생각되는 native code입니다.

 

보통은 native code를 unmanaged code의 동의어의 일종으로 생각해서,
크게 managed code와 native code로 나누기도 합니다.
이전 슬라이드에서 말한 것처럼 native code와 non-native code로 나눈 것처럼 말이죠.


하지만 native code는 unmanaged code 일 수도,
managed code의 산출물인 machine code 인 기계어도 될 수 있습니다.

 

이 부분에 대해서는 pwnwiz 님이 더 잘 아시니,
잘 모르겠다 싶은 것은 pwnwiz 님께 질문하면 잘 알려주실 겁니다.

 


apk 해킹? 이 맞는 말인지 모르겠지만,
apk 해킹에서 제가 가장 중요하다고 생각하는 것은

리패키징 방지 우회 및 루팅 탐지를 우회하는 것이라고 생각합니다.

 

왜냐하면 리패키징 방지와 루팅 탐지 2개에서 자유롭다면 분석의 난이도는 다르지만,
그래도 코드를 변조해서 무엇인가는 할 수 있기 때문입니다.

 


안드로이드 어플리케이션인 apk의 경우 iOS 어플리케이션인 ipa에 비하여 단순한 zip 구조로 구성되어 있으며,
별다른 절차(루팅) 없이도 개발자 도구 중 하나인 usb 디버깅 모드로 apk의 설치 및 삭제가 가능합니다.

 

그래서 사용자는 설치된 apk를 추출하거나 외부에서 apk를 구하여,
수정한 후 리패키징하여 다시 설치할 수 있습니다.


이렇게 되면 악의적인 사용자는 apk를 수정하여하고 싶은 것을 할 수 있게 됩니다.

 


apk의 구조를 조금 더 자세히 보자면,

소스코드는 dex 파일로 컴파일되며
여기에 apk에서 사용할 여러 리소스 파일들과 네이티브 코드들을 압축하여

apk를 생성합니다.

 

이 apk를 설치하면,

반대로 압축을 풀면서 설치가 되고 달빅이가 바이트코드를 해석하고 실행이 됩니다.

 


앞에서 말한 바와 같이 apk 해킹하는데 첫 번째로 중요하다고 생각하는 리패키징 방지입니다.

 

리패키징 과정은 apk 만드는 과정을 거꾸로 포함하고 있습니다.


정상적이라면 소스코드에서 컴파일을 한 후,
패키징 과정을 거친 후에 서명을 하고 배포를 합니다.


하지만 리패키징 과정은 apk를 언패킹 해서,
디컴파일을 거쳐 소스코드를 획득하고,
수정을 한 후에 다시 apk를 만듭니다.

 

따라서 이렇게 리패키징을 통해 소스코드를 수정할 가능성이 있기 때문에,
apk 가 리패키징되지 못하도록 방지해야 합니다.

 


이름은 밝힐 수 없는 한 게임이 있습니다.
물론 이 게임에서도 여러 보안 방식들로 자사의 어플리케이션을 보호하고 있습니다.


하지만 이렇게도 할 수 있다는 것을 보면서,
리패키징 방지의 중요성을 한번 더 생각해 봤으면 좋겠습니다.

 

우선 이러한 짓을 한 개요는 다음과 같습니다.
게임을 플레이하는 게 귀찮아서

“한 가지 색상으로만 나온다면 가만히 있어도 편하게 게임을 끝낼 수 있지 않을까?"

라는 생각으로,
파란색 캔디만 생성되도록 바꿔 보았습니다.
하지만 당연히 끝날 거라 생각했던 게임은 끝나지가 않았습니다,,

 


그다음으로 두 번째로 중요하다고 생각하는 루팅 탐지입니다.

 

여기서 루팅이란 정확히 뭘 말하는 것일까요?
루팅이란 최고 권한인 root를 획득하는 것을 말하며,
안드로이드 운영체제에서는 최고 권한인 superuser 권한을 획득하는 것을 말합니다.


이 권한으로는 일반적으로 할 수 없는 것들을 할 수 있게 해 줍니다.

예를 들면 접근이 불가능한 시스템 폴더 같은 곳에 접근해서 삭제 불가능한 좀비 어플을 삭제하거나,
어플리케이션이 사용하는 메모리에 접근해서 수정하는 것이 가능합니다.

 


초등학교, 중학교 때 게임을 많이 해보신 분들은 한 번은 써봤을 러키패처와 치트엔진입니다.


일반적으로 안드로이드에서는 각 어플리케이션별로 uid를 생성하고 각 uid 별로 관리되지만,
이 어플리케이션들은 루팅을 이용하여 다른 프로세스에서 사용하는 메모리를 수정할 수 있게 해 줍니다.

 

따라서 메모리 무결성 등이 중요한 금융이나 게임 어플리케이션의 경우 루팅이 되어 있는지를 확인해야 합니다.

 


앞에서 소개한 루팅을 탐지하는 것과 리패키징을 방지하는 것 외에도 여러 보호 기법들이 있습니다.


이 중에서 제가 가장 싫어하는 보호 기법을 하나 보여 드리면서 넘어가도록 하겠습니다.

 


이 어플리케이션은 제가 분석하면서 제일 싫었던 어플리케이션 이였습니다.

 

중간에 보면 메소드들이 a.a.a으로 되어 있는데 다시는 만나고 싶지 않은 친구입니다.

 

그러면 오늘의 진짜 주제였던 안드로이드에서 루팅 탐지 방법에 대하여 소개하겠습니다.

문제 소개

상용 안드로이드 어플리케이션을 개발했지만, 불행히도 개발은 멈췄고 출시를 하지 못하게 되었습니다. 하지만 유효한 키를 찾아서 프리미엄 기능을 열어달라고 합니다.

 

APK 분석

apk 를 제공해 주므로, apktool 을 이용하여 디컴파일을 진행합니다.

.
├── THC
│   ├── AndroidManifest.xml
│   ├── apktool.yml
│   ├── original
│   ├── res
│   └── smali
└── THC.apk

apk 를 디컴파일해보면, 여러 구성 요소들이 보입니다.

 

AndroidManifest.xml 파일을 확인해 보면, com.thc.bestpig.serial.MainActivity 가 메인 엑티비티 인 것을 알 수 있습니다.

 

MainActiviy->onCreate() 부터 살펴 봅니다. onCreate() 에서 하는 게 많지만, 핵심은 마지막 부분에 위치합니다.

Id가 0x7f070023 View 를 가져와서 버튼인지 확인을 하고, MainActivity$3의 <init> 함수를 호출하고 버튼에 Click Listener 을 설정합니다.

 

<public type="id" name="buttonActivate" id="0x7f070023" />

Id 가 0x7f070023 인 것을 확인해 보면, buttonActivate 라는 이름을 확인할 수 있습니다.

 

더 확인해보면, 텍스트로 "Activate Software" 를 가진 버튼임을 확인할 수 있습니다.

 

다시 MainActivity$3 으로 돌아와서, 버튼을 click 하게 되면 위의 method 가 호출됩니다.

입력한 값을 가져와서 문자열로 바꾸고, 이 문자열을 MainActivity의 checkPassword() 메소드의 인자로 하여 호출합니다.

 

checkPassword 메소드에서는 이 문자열을 다시 validateSerial 메소드의 인자로 하여 호출을 하고, 결과값이 1이 아니면 인증 실패 루틴으로 이동하게 됩니다. 따라서 validateSerial 메소드에서 1을 반환할 조건을 분석해 봅니다.

 

validateSerial 메소드에서는 v0 의 값을 return 하게 되는데, 이 값이 1이 되는 경우는 아래에 더 존재하는 모든 조건을 충족하는 경우만 있습니다. 따라서 이 조건들을 정리해 보면 아래와 같습니다.

 

.line 18    len(flag) == 0x13
.line 20    flag[0x04] == flag[0x09] == flag[0x0e] == 0x2d
.line 22    flag[0x05] == flag[0x06] + 0x01
.line 25    flag[0x05] == flag[0x12]
.line 28    flag[0x01] == (flag[0x12] % 0x04) * 0x16
.line 30    flag[0x0a] == flag[0x03] * flag[0x0f] / flag[0x11] - 0x01
.line 32    flag[0x01] == flag[0x0a]
.line 34    flag[0x0d] == flag[0x0a] + 0x05
.line 36    flag[0x0a] == flag[0x05] - 0x09
.line 38    0x5a0 == (flag[0x00] % flag[0x07]) * flag[0x0b]
.line 40    flag[0x02] - flag[0x08] + flag[0x0c] == flag[0x0a] - 0x09
.line 42    (flag[0x03] + flag[0x0c]) / 0x02 == flag[0x10]
.line 44    flag[0x00] - flag[0x02] + flag[0x03] == flag[0x0c] + 0x0f
.line 46    flag[0x03] == flag[0x0d]
.line 48    flag[0x10] == flag[0x00]
.line 50    flag[0x07] + 0x01 == flag[0x02]
.line 52    flag[0x0f] + 0x01 == flag[0x0b]
.line 54    flag[0x0b] + 0x03 == flag[0x11]
.line 56    flag[0x07] + 0x14 == flag[0x06]

이 조건들로 시리얼을 직접 구할 수도 있지만, SMT solver 중 하나인 Z3 를 이용하여 solver 를 만듭니다.

 

Serial 구하기

위에서 찾은 식을 조건에 추가하고, 조건을 충족하는 결과 값을 출력합니다.

 

위에서 구한 serial 값을 입력하면, 활성화에 성공한 것을 확인할 수 있습니다.

'0x00 Reversing > 0x02 CTF' 카테고리의 다른 글

InCTF 2019 :: cliche_crackme  (0) 2019.10.05

Intro

[*] 안드로이드 카카오톡을 대상으로 분석하였기에, 아래에 나오는 카카오톡은 안드로이드 버전의 카카오톡을 지칭합니다.
프로젝트를 진행하면서, 재미있어 보였지만 끝까지 하지는 못한 카카오톡 DB의 복호화에 관한 글을 적어 봅니다.
프로젝트 당시에는 많은 제약 조건들로 인하여 복호화 쪽은 다른 것들에 밀려 끝까지 분석해 보지 못했었습니다. 따라서 예전 분석한 것을 바탕으로 이번 기회에 시간을 더 투자하여 다시 한번 복호화를 진행하여 보았습니다. 결과부터 말하자면, 안드로이드 KakaoTalk 의 최신 버전인 v8.5.4 에서 진행하였으며 성공적으로 복호화 하였습니다.
 

메시지 원문

복호화할 대상을 생성하기 위하여 카카오톡 버전에 관한 정보와 DB 파일등을 주고 받았습니다.
카카오톡에서 메시지를 주고 받으면 위와 같이 평문으로 확인을 할 수 있습니다. 여기서 조금 추측을 해보자면 카카오톡이 네트워크에 연결되어 있지 않아도 채팅창에서 평문의 메시지를 확인할 수 있습니다. 따라서 서버에서 키와 관련된 정보는 받아오지 않는 것을 추측해 볼 수 있을 것 같습니다.
 

암호화된 DB

위는 /data/data/com.kakao.talk/databases/KakaoTalk.db 에서 추출한 카카오톡 메시지 관련 DB 입니다.
PC 버전 KakaoTalk의 경우에는 DB 자체가 암호화되어 있습니다. 반면에 안드로이드 KakaoTalk 의 경우에는 모바일이라는 특성 때문인지 messsage 부분과 attachment 부분만 암호화되어 있습니다.
즉 사용자가 주고 받은 메시지와 첨부파일에 대한 정보들만을 암호화하고 있습니다.
 

메시지 복호화

위는 DB를 성공적으로 복호화하여 CSV 형식으로 저장한 것입니다.
리버싱을 통해 암호화 루틴을 파악하고, smali로 되어 있는 코드를 java 코드로 구현하여 복호화를 진행하였습니다.
텍스트 메시지의 경우에는 메시지 부분에, 사진과 같은 첨부파일들은 첨부파일 부분에 카카오톡 파일 서버의 URL 과 같은 정보 등이 저장되어 있는 것을 확인할 수 있습니다.
 

Outro

카카오톡은 대칭키를 사용하여 message 와 attachment 부분을 암호화합니다. 따라서 카카오톡 DB 복호화의 핵심 요소는 암호화와 복호화할 때 사용하는 키입니다.
이 키는 서버에서 받아오거나 로컬에서 생성할 수 있을 텐데, 위에서 추측한 바와 같이 네트워크 연결이 없음에도 평문으로 내용을 확인할 수 있기에 로컬에서 생성할 것입니다. 실제로 DB에 저장되어 있는 값과 여러 고정 값들을 이용하여 키를 생성합니다. 이 부분은 내용이 길어지므로 나중에 별도의 글로 작성하겠습니다.
또한 필자와 같이 리버싱하여 사용자들이 카카오톡의 암호화 알고리즘을 파악할 수 있습니다. 이에 대한 변화가 필요하지만 기존에 암호화한 DB 와의 호환성을 위하여 무작정 암호화 알고리즘를 변경하기도 힘들다고 생각합니다. 따라서 카카오톡은 호환성을 유지하며 버전별로 조금씩 변화를 주며 키를 생성 하고 있습니다. 역시 이에 대한 자세한 내용은 길어질 것 같기에 나중에 별도의 글로 작성하겠습니다.

+ Recent posts