보안/android

[앱][리버싱] 2,3주차 정리+과제 ; 안드로이드 파일시스템 & 데이터 구조 / 디컴파일

y00&z1 2021. 9. 10. 14:15

2주차 

안드로이드 파일시스템과 데이터 구조

 

<왜 알아하는지?!>
앱 취약점 분석 시 - 앱을 디버깅/수정/설치할 때 리눅스 시스템 안에서 작업
-> 터미널 창에서 접속을 해서 리눅스랑 똑같이 명령어 입력해서 작업함

 

앱이 어떤 데이터를 저장을 한다 -> 유출 위험성을 확인하고 싶다! 
앱 안에서 사진을 찍고 -> 폰 안에 있는 사진첩 안에 저장 / 앱 안에 저장하게 되면 앱 삭제하면 데이터도 삭제됨

==> 분석하는 과정에서 파일 시스템을 봐야 함, 어디에 저장되는지를 알아야 함! 

 

 

 

1. 안드로이드 파일시스템

1-1) 파일 시스템 ; 파일을 저장하기 위해 관리하는 체계

->운영체제마다 다르다. 

  • 윈도우 계열: FAT(File Allocation Table), NTFS(NT File System)
  • 리눅스 계열: 확장 파일 시스템(ext, ext2, ext3, ext4), ZFS, ResierFS, XFS

 

안드로이드는 리눅스 기반 - 리눅스 계열의 파일 시스템을 사용 + 추가적으로 안드로이드에 필요한 파일 시스템 포함

 

<OS (ios, android, window)>

boot.img (어떤 OS를 구동시킬지에 대한 정보)

system.img

  • <Linux> etc, home
  • <Window> AppData, ProgramFiles(x86), ProgramFiles

 

 

 

 

1-2) 파일시스템의 사본

boot 부팅이미지. 커널 이미지와 ramdisk를 포함
system Android 프레임워크
recovery OTA(On The Air) 프로세스 중에 부팅된 복구 이미지
cache 임시 데이터
misc 복구에 사용되는 파티션
!!userdata 맞춤 설정 데이터 + 사용자가 설치한 애플리케이션과 데이터
metadata 기기 암호화 + 16MB 이상인 경우에만 사용됨
vendor Android 오픈소스 프로젝트(AOSP)에 배포할 수 없는 바이너리 포함
; 기본 시스템 앱 말고 추가로 설치하는 앱
radio 무선 이미지(무선 관련 소프트웨어 있는 기기만)
tos Trusty OS의 바이너리 이미지 저장

 

 

 

2. 부팅 순서

 

1. 부트로더 로드

2. 부트로더가 메모리를 초기화

3. A/B업데이트를 사용하는 경우 - 부팅할 현재 슬롯을 결정 

     ㄴ> 이미지 업데이트할 때 매번 직접 usb 디바이스 쓰기/읽기 할 수 없음 -> OTA 기술(On The Air) ; 특정 서버로부터 부트 이미지를 받아서 업데이트!


온라인에서 업데이트를 할 때는 악성 이미지를 받을 수 있는 위험성 있음 -> 이를 방지하고자 A, B 업데이트 파일 복사를 해서 업데이트 

 

4. 복구 모드를 대신 부팅해야 하는지 확인 

5. 부트로더 - 커널과 RAM 디스크가 포함된 이미지를 로드

6. 부트로더 -  자체적으로 실행할 수 있는 압축된 바이너리로 커널을 메모리에 로드 시작

<메모리로 로드해서 압출을 풀고 메모리로 실행>

7. 커널 - 자체적으로 압축을 풀고 메모리로 실행 시작

8. 해당 시점에서 오래된 기기는 RAM 디스크에서 init을 로드하고, 새로 나온 기기는 /system 파티션에서 이를 로드

9. /system에서 init이 실행되며 /vendor, /oem, /odm 등 다른 모든 파티션이 마운트 되기 시작하고, 이후에는 코드 실행이 시작되어 기기가 시작

       ㄴ> init 프로세스가 실행되면서 os 프로세스 실행 
            oem / odm - 다른 회사에서 만든 앱 

 

 

3. 안드로이드 데이터 구조

3-1) 데이터구조의 사본

데이터 구조를 특히 집중적으로! - 사진 앱으로 사진 찍으면 다 data 폴더 안으로 들어감

패키지 이름 = 앱 이름

/data/data/[패키지이름]/shared_prefs 어플리케이션의 공유 설정 파일이 저장되는 영역
/data/data/[패키지이름]/cache 어플리케션이 필요한 임시파일이 저장되는 영역
/data/data/[패키지이름]/database 어플리케이션이 필요한 데이터베이스 파일이 저장되는 영역
/data/data/[패키지이름]/files (데이터베이스, 캐시 제외) 어플리케이션에서 사용하는 일반 파일이 저장되는 영역
/data/data/[패키지이름]/lib 어플리케이션이 필요한 라이브러리 파일이 저장되는 영역

만약에 카카오톡 리버싱하고 싶으면? -> apk 뜯어서 -> 데이터구조 보면 됨
+) 전체 디렉토리를 보기 위해서는 root 권한 필요!  - ls 해도 극히 일부밖에 안 나옴

 

 


3주차

안드로이드 앱 디컴파일, 리패키징 수행하기

 

1. 디컴파일(Decomplie)

1-1) 디컴파일?

compile의 반대 ; decomplie : new.apk -> file1, file2 ...

ㄴ compile : file1, file2 ... -> new.apk

 

 

 

1-2) 디컴파일을 통해 할 수 있는 것? ; 디컴파일을 왜?
-앱의 로직 분석 가능 (소스를 볼 수 있으니까 - 상세한 내부 로직 알 수 있음)
-앱의 이미지 수정 - apk 파일 만들 때 리소스 파일에 저장
-앱의 기능 수정 (마찬가지로 소스를 볼 수 있으니까 but 쉽지는 않음.  완전히 동일하게 디컴파일되는 것이 아님)

 

 


1-3) 디컴파일 단계

1. apk 내부 파일 추출 (apk == zip 파일 -> apk 압축 툴로 압축 풀면 그나마 읽을 수 있는 형태로 풀림)
2. apk 파일을 java class 파일로 변환
3. jar 파일 올리면 디컴파일 수행됨!

=> 애플리케이션 공개 후 디컴파일 가능하기 때문에 취약함! -> 난독화(변수 이름 바꾸기, 순서 바꾸기)

 

 

 

[실습]

분석 apk 찾기 - https://apkpure.com/kr/ 

 

1. APK 내부 파일 추출 - APKtool 설치

APKtool (https://ibotpeaches.github.io/Apktool/install/)

 

decode 명령어 -> $ apktool d testapp.apk

 

2. APK 파일을 java class 파일로 변환 - 상세한 분석을 위해서 dex2jar 도구 이용

오류 나면 dex2jar 2.1 버전으로 설치! 

 

-> $ d2j-dex2jar.bat testapp.apk 

-> $ ./d2j-dex2jar.sh testapp.apk

 

smali 코드 - 어셈블리어(기계어코드)
아직은 java 수준은 안돼서 한번 더 변환하는 작업 필요
수정은 가능하지만 이해는 어려움

dex -> (smali) -> java 
: Dex2jar(단계 건너뛰고 한번에 jar 파일로)

dex 
^
smali - class.dex
^
jar(java 코드들을 합친 거)

 

 

3. 디컴파일 수행; 변환된 파일을 자바 디컴파일러(Bytecode-Viewer) 사용하여 디컴파일 후 내부 소스코드 분석

Bytecode-Viewer (https://bytecodeviewer.com/)

 

*javaDecomliler - jd gui
android.support
androidx
-> 내정된 부분! 

 

===

+)

*build / generate 차이?

generate :  서명+build(서명이 되어야만 제대로 설치가 됨 - 앱의 무결성/앱 개발자를 위해서)
키는 아무나 만들 수 있음(ios는 키 만들기 어려움)

*debug / release 차이?
dubug - debug 로그 메세지 전부 포함
release - 위 내용 전부 제외 (컴파일 옵션이 다름) (나중에 gdb 명령어가 안 먹히는 상태)
-> 앱에 대한 디테일한 정보 유무 차이

*동적(나중에 따로 연결!)/정적 라이브러리(포함하는거)


 

 

 

2. 리패키징(Repackaging)

; 재포장! - 디컴파일하고, 추출한 소스/이미지를 수정해서 재빌드하는 것 

new.apk(ZIP) -decompile-> files -repackaging-> ZIP

2-1) 리패키징 단계 

  1. APK decompile
  2. APK decompile을 통해 추출한 소스 코드 수정 ; smali 코드 단계에서 해야 함 (java 소스코드와 비교하면서!)수정을 힘들게 하는 게 난독화의 역할 
  3. APK 재빌드
  4. 서명

 

 

[실습]

1, 2번은 위 실습 참고

3. APK 재빌드

// apktool b [패키징 할 폴더명] -o [apk파일명 지정]

apk 빌드 명령어 실행 후 -> dist 디렉토리가 생성 + 해당 디렉토리 내 apk 파일이 생성됨

 

4. 서명

 

안드로이드에서는 앱의 무결성을 검증을 위해 앱을 빌드할 때 서명을 하도록 되어있음.

-> 변조된 앱에 신뢰할 수 있는 서명이 없는 경우 - 설치 실패 문구를 발생시켜 설치하지 못하게 함

 

변조된 앱은 디컴파일 하는 순간 무결성이 깨져 서명이 깨짐 -> 이를 다시 설치 가능한 apk로 만들기 위해서

=> 서명 파일을 생성하여 리패키징한 앱에 재서명 작업 필요! 

 

서명을 하려면 키 파일을 만들고 주입을 해야 함
(키 파일을 pem 형식으로 넣어야 됨. 이미 만들어진 키 파일을 넣는다고 가정!)

 

$ java -jar signapk.jar testkey.x509.pem testkey.pk8 [서명할 APK 이름] [서명 후의 APK 명] 

->?!

 

 

 

 

 

 

[과제]

✔️ Mission1: 디컴파일을 수행하고 앱 로직을 분석해서 password 값 알아내기

1. APK 파일을 java class 파일로 변환하기 

 

성공적으로 변환 완료!

 

2. 디컴파일 수행

jd-gui 자바 디컴파일러로 jar 파일을 열어 디컴파일 수행

-> 여러 폴더를 열어 살펴보던 중..  로그인 관련 소스코드 파일 발견!  

 

 

 

mainactivity에서 login 함수 호출하는 부분  

login(String paramString1, String paramString2, String paramString3, Context paramContext)

paramString1 -> username(사용자 입력 부분)

paramString2 -> password(사용자 입력 부분)

paramString3 -> str (변수)

String str = MainActivity.this.getString(2131755116);

 

 

return byteArrayToHex(arrayOfByte1).equals(paramString3);

->  해당 구절에서 .equals(paramString3) 을 보아  paramString3 (== str == 2131755116 )을 이용해 패스워드를 찾아야하는것 같다

 

2131755116 -> hex 값으로 변환; 0x7f10006c

 

 

 

3 . APK 내부 파일 추출 

 

 

\res\values 위치에서 public.xml 파일에서 위 hex 값 이용해 id 찾기

 

 

strings.xml 에서 비밀번호 찾기

203d233e382c1e215a6a7c6c7e725c6b

 

 

 

[비밀번호 찾기]

 

비밀번호 입력 값이 login 함수를 통해 어떤 값이 됨.

어떤 값 == 203d233e382c1e215a6a7c6c7e725c6b

-> 입력 값(패스워드)을 찾기 위해서는 login 함수 알고리즘을 역으로 적용하여 패스워드를 찾아야한다! 

 

byteArrayToHex(arrayOfByte1) ; 10진수 -> 16진수 변환 함수 

==> 16진수 -> 10진수 (+ stringBuilder.append(String.format("%02x" ~  ; 2자리 16진수로 변환)

20 -> 32

3d -> 61

23 -> 35

3e -> 62

38 -> 56

2c -> 44

1e -> 30

21 ->  33

5a -> 90

6a -> 106

7c -> 124

6c -> 108

7e -> 126

72 -> 114

5c -> 92

6b -> 107

[32, 61, 35, 62, 56, 44, 30, 33, 90, 106,124, 108, 126, 114, 92, 107] ; arrayOfByte1

arrayOfByte1 = arrayOfByte2 + arrayOfByte3 (X)

 

+) ?? 왜 2 3 순서가 바뀜 ? 

arrayOfByte1 = arrayOfByte3 + arrayOfByte2 (O)

arrayOfByte3 - 0 ~ i/2

arrayOfByte2 - i/2 ~ i

근데  arrayCopy는 arrayOfByte2 부터! 

 

 

arrayOfByte2 = [32, 61, 35, 62, 56, 44, 30, 33, 

arrayOfByte3  = 90, 106,124, 108, 126, 114, 92, 107]

 

 

for 문에서 arrayOfByte4에 담긴 값들과 xor 연산 -> xor 연산을 두 번 해주면 원래 값이 된다는 것 이용!

arrayOfByte4 배열과 한번 더 xor 하는 코드 작성

arrayOfByte2 = [48, 56, 49, 51, 50, 54, 35, 36 

arrayOfByte3  = 74, 111, 110, 97, 116, 104, 97, 110]

48, 56, 49, 51, 50, 54, 35, 36 

74, 111, 110, 97, 116, 104, 97, 110


이제 아스키로 변환해주면, 비밀번호가 나타난다! 

081326#$Jonathan 

안됨

혹시 몰라서 순서 바꿔서 입력해봤더니 된다!!!

Jonathan081326#$

ㅠㅠ

감격

 

 

 

✔️ Mission2: otp 인증 단계 통과하기

로그인 성공하면 위와 같은 otp 인증 단계 화면이 나온다. 

 

 

다시 MainActivity 확인해보면 로그인 성공 시 SubActivity.class 로 전환됨. 

 

 

SubActivity 확인하기