1. Uncrackable3
1.1 다운로드
해당 웹사이트에서 다운 받을 수 있으며,
view Raw
버튼을 누르면 다운로드 됩니다.1.2 실습 준비 도구
유형 별로 정리해 두었습니다. 유형 별로 맘에 드는 도구를 선택하여 사용하시면 됩니다.
Apk 분석 툴
- jadx (추천) → 현재 풀이에서 사용
- BytecodeViewer
- jd-gui & dex2jar
리버스 엔지니어링 도구
- IDA → 현재 풀이에서 사용
- Gidra
Frida
- Frida (client, server) 환경 구축 완료 상태
device
- mobile device ( 루팅폰 ) → 현재 풀이에서 사용
- ADV ( 루팅 )
- NOX ( 루팅 )
2. Uncrackable3 풀이
2.1 루팅 탐지 우회 확인
Uncrackable2
와 마찬가지로, 애플리케이션을 실행하면 기기가 루팅이 되었는지 확인하고, Root or tampering detected
라는 문자열을 출력하며 종료됩니다.이는
exit
함수를 후킹하거나, 루팅 탐지 함수를 우회하는 것으로 해결했었습니다.이번에도 기존 방식과 동일하게 탐지를 우회하여 보겠습니다.
루팅 탐지 로직 확인
➡️
MainActivity onCreate()
부분을 보면, RootDetection class
의 method로 루팅을 탐지합니다.➡️ 해당 클래스(RootDetection)를 보면, 3개의 method가 루팅탐지에 이용됩니다.
- su 확인
- 빌드 태그를 확인
- 루팅에 사용되는 주요 프로그램이 설치된 경로 확인
➡️ Uncrackable2와 같이 해당 method를 전부
false
를 return 하도록 재 작성 해보겠습니다.2.2 크래시 확인
➡️ 해당 스크립트를 frida를 이용하여 동작하였지만, 애플리케이션이 중단되는 결과가 발생합니다.
➡️ 원인 분석을 위해 logcat으로 로그를 확인해 보겠습니다.
- 루팅 탐지 로직에서 발견하지 못한 문자열들이 존재합니다.
- 루팅 탐지에 추가 기능이 있거나, 무언가 방어 대책이 존재하는 것 같습니다.
크래시 원인 분석
➡️ 크래시가 나는 이유를 확인하기 위해, 루팅탐지 이전에 동작한
verifyLibs()
를 확인해 보겠습니다.➡️
verifyLibs()
를 확인해보면, 뭔가 라이브러리에 대한 무결성을 확인하는 것 같습니다.- 아까 로그에 보았던
CRC[ ~
형태의 문자열도 보입니다.
- 하지만 앞서 logcat으로 보았던 로그에 찍힌
Tampering detected!
문자열은 보이지 않습니다.
➡️ 해당 문자열이
class
파일 안에 있는지 확인하기 위해 검색하였지만, 존재하지 않습니다.- 따라서 해당 탐지 로직은,
NativeCode
로 작동될 확률이 높다고 판단, 라이브러리를 로드하는 곳을 확인해야 합니다. (아니면backtrace
를 확인하여도 됩니다.)
➡️ JNI를 사용한다고 판단하고, 해당 라이브러리가 Load 되는 곳을 확인하였습니다.
- 해당 라이브러리는 어플리케이션의
/lib/<arch_name>/libfoo
로 저장되게 됩니다.
2.3 Native 코드 분석
➡️ 해당 라이브러리를
IDA
로 분석하여 Tampering detected!
문자열을 찾았습니다.- IDA에서는
F12 + Shift
를 누르면, 문자열들을 볼 수 있습니다.
➡️ 해당 문자열이 참조된 함수를 확인할 수 있습니다.
F5
키를 눌러 디컴파일을 진행합니다.
➡️
start_routine
이라는 함수에서 frida
를 탐지하고 있었습니다.start_routine
함수를 트레이싱 하다보면, 해당 libc가 로드될때 실행됨을 알 수 있습니다.
- 프로세스 중에서 frida와 관련된 문자열이 있는지 확인하고, 있다면 프로세스를 종료시킵니다.
strstr
함수는 서브스트링을 찾는 함수로,args[0]
에서args[1]
문자열을 찾지 못한다면0(Null)
을 반환합니다.
➡️ 따라서 해당
strstr
이 반드시 0을 반환하도록 Intercepter
을 이용하여 후킹해 보겠습니다.- 이후 해당 스크립트를
-f
옵션과 함께 실행하면 루팅 탐지가 우회 되는 것을 알 수 있습니다.
3. Secret value 찾기
3.1 정답 체크 부분 확인
➡️
Code_check
class의 check_code
method 부분에 EditText
에서 가져온 데이터로 SecretValue
를 확인합니다.➡️
check_code
부분을 확인하면, bar
이라는 Native method로 무언가 하는 것을 알 수 있습니다.3.2 NativeCode분석 (libfoo.so)
➡️
CodeCheck_bar
의 코드를 살펴보면 무언가 키를 확인하는 부분이 있습니다. ➡️ 해당 코드를 분석해 보겠습니다.
입력값
을v4
에 저장합니다.
입력값
과v7 ^ dest
(xor) 에 대한 값을1byte
씩 비교하여 일치하는지 확인합니다.
- v7은 길이 40의 배열이며, sub_12C0 함수에 인자로 들어갑니다.
- dest또한 확인이 필요합니다.
- 총 0x18 (24) 만큼 byte를 비교합니다.
➡️ dest를 추적해 보겠습니다.
- dest를 보면, NativeCode로 작성된
Init
에서 참조 되어있는 것을 볼 수 있습니다.
➡️ init을 확인해 보겠습니다.
- 인자로 값을 받아
dest
의 주소에 문자열을strncpy
로 복사 하는 것을 알 수 있습니다.
➡️ 해당 Init이 호출되는
ClassCode
를 확인해 보겠습니다.- init 함수의 호출에 상단에 저장되었던
xorkey
가 들어갑니다.
- 따라서
xorkey
인pizzapizzapizzapizzapizz
가dest
에 복사 됨을 알 수 있습니다.
➡️ 이제
dest
의 값을 알았으니 v7
의 값을 확인하면, secretValue
를 알 수 있습니다. v7
을 분석해 보겠습니다.- v7은 길이 40의 char arr로 선언됩니다.
sub_12C0()
에 버퍼의 시작 주소가 전달됩니다.
➡️ 매우 긴 함수지만, 매개변수를 참조하는 코드를 확인해보면, 마지막 줄에만 해당되는 것을 알 수 있습니다.
- xmmword는
16Byte
를 참조합니다. 따라서v7 배열
에 길이16의 byte
를 쓰는 것을 알 수 있습니다.
- 이후 나머지 8byte는
0x14130817005A0E08
을 넣는 것을 볼 수 있습니다.
➡️ 16byte를 넣는 곳을 확인해보면,
15131D5A1903000D1549170F1311081D
를 넣는 것을 알 수 있습니다. 3.3 SecretValue 추출
v7
에 들어간 16byte
와 8byte
를 합쳐서 xorkey
와 xor
해주면, SecretValue
를 확인할 수 있습니다.- 나머지 8byte는
리틀 엔디안
형식으로 데이터가 삽입되었기 때문에 byte단위로 reverse 해주어야 합니다.
- key는
making owasp great again
임을 알 수 있습니다.
3.4 다른 풀이
일단 기존 풀이는
디바이스의 아키텍쳐가 intell
이였기 때문에 함수의 offset이 12C0
이였습니다. 하지만 저는 arm 아키텍쳐
로 풀이를 진행하기 때문에, offset이 다릅니다. 이점을 유의해야 합니다.- 기존 함수 (12C0)
➡️ sub_10E0은 libfoo가 load된 시점에서 함수의 주소가 10E0만큼 offset이 발생한다는 의미입니다.
- 여기서 주의할 점은, ARM 아키텍쳐여서 함수의 오프셋이
10E0
으로 변동되었습니다.
- 따라서 libfoo의 메모리 로드 시점에서 메모리 주소를 구하고, 10E0만큼 더해주면 해당 함수의 주소를 알 수 있고, 인자로 전달되는 v8이 어떤식으로 값이 바뀌는지 볼 수 있습니다.
➡️ 후킹 코드는 다음과 같습니다.
➡️ 0으로 초기화 되었던 v8이
sub_10E0
이후 값이 변경된 것을 볼 수 있습니다.1D 08 11 13 0F 17 49 15 0D 00 03 19 5A 1D 13 15 08 0e 5a 00 17 08 13 14
로 변경되었습니다.
- 앞서 기존 풀이에서 보았던 24바이트와 일치합니다.
➡️ 기존 secret 키를 구해보겠습니다.
짜잔~
이렇게 Uncrackable level3의 풀이가 끝났습니다.
글이 길어지다보니, 이상한 설명 혹은 부족한 풀이가 있을 수 있습니다. 피드백은 언제나 환영입니다.