먼저 안드로이드 아키텍처 구조에 대해 이해해야 한다.
가장 하위에 리눅스 커널이 있고, 그 위에 달빅 가상머신이 있는데 이 달빅 가상머신이 dex 포맷의 바이트코드를 해석하여 안드로이드 어플리케이션을 실행하게 된다.
이 때의 dex 바이트 코드를 사람이 읽을 수 있도록 변환한 것이 바로 Smali이다.(≒어셈블리 언어)
smali 코드를 얻기 위해서는 dex 바이트코드를 baksmali시켜야 하는데, 간단히 apk 파일을 apktool을 이용해 디컴파일시키는 것으로 smali를 확인할 수 있다.
#apktool d test.apk
또한, 이렇게 얻은 smali 코드를 수정하여 apktool로 recompile시켜 apk를 생성할 수 있다.
#apktool b test.apk
참고로 윈도우에서는 버그로 인한 에러가 발생하여 리눅스 환경에서 컴파일해야 한다.
https://github.com/iBotPeaches/Apktool/issues/1623
: 윈도우 apktool 버그
https://ibotpeaches.github.io/Apktool/install/
: 리눅스 apktool 설치
TGHACK2020의 리버싱 문제인 apk파일의 일부를 보면서 몇 가지 문법을 익혀보겠다.
java
this.waves = 1; ... ... ... if (this.waves == 1000) {
waves
라는 변수가 1000인지를 검사하는 if
문이다.
smali
.line 265 iget p1, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->waves:I const/16 v0, 0x3e8 if-ne p1, v0, :cond_f
iget
명령으로 waves
변수 값이 p1
에 저장되고
const
명령으로 v0
에 0x3e8이 저장되며
if-ne
가 이 두 가지를 비교하게 된다.
같지 않을 경우 cond_f
로 분기한다.
conf_f
:cond_f iget-wide v2, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->magic:J iget p1, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->waves:I
이제 waves
는 0x3e8이라고 가정하고 if
문 안에서 중요한 코드를 살펴보겠다.
java
this.magic = 4919L; ... ... ... localObject1 = EncryptionKt.decrypt("M6GzLMKMB7TO7qCwYIE6tWjstepv0Fa6B3yyCrRtFbNRb2+VVVCqDuDO6UWY14LEu9Ac3A2sVaKG4Thk1s1j0g==", (Number)Long.valueOf(this.magic), "ClcYYh9brZTGyTFG4kge7w=="); this.canvas.drawText("Congrats!!!", f1, f2, this.paint); this.paint.setTextSize(40.0F); this.canvas.drawText(String.valueOf(localObject1), f1, f2 + 100, this.paint); getHolder().unlockCanvasAndPost(this.canvas); pause();
어떤 암호화된 값을 decrypt
한 뒤에 "Contrats!!!"라는 문자열을 출력해주고 pause()
를 실행한다.
smali
iget-wide v6, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->magic:J invoke-static {v6, v7}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long; move-result-object v0 check-cast v0, Ljava/lang/Number;
magic
의 변수값 가져와서 invoke-static
에서 valueOf()
메서드를 호출할 때 인자로 전달한다.
결과값을 v0
에 가져오고 캐스팅 한다.
const-string v3, "M6GzLMKMB7TO7qCwYIE6tWjstepv0Fa6B3yyCrRtFbNRb2+VVVCqDuDO6UWY14LEu9Ac3A2sVaKG4Thk1s1j0g==" const-string v6, "ClcYYh9brZTGyTFG4kge7w==" invoke-static {v3, v0, v6}, Lno/tghack/gaiainvaders/EncryptionKt;->decrypt(Ljava/lang/String;Ljava/lang/Number;Ljava/lang/String;)Ljava/lang/String; move-result-object v0
decrypt
를 호출하는 부분이다. 결과값이 v0
에 저장된다.
.line 284 iget-object v3, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->canvas:Landroid/graphics/Canvas; iget-object v6, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->paint:Landroid/graphics/Paint; const-string v7, "Congrats!!!" invoke-virtual {v3, v7, p1, v2, v6}, Landroid/graphics/Canvas;->drawText(Ljava/lang/String;FFLandroid/graphics/Paint;)V
drawText()
는 인자로 넘어온 문자열('Contrats')을 화면에 출력해준다.
iget-object v6, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->paint:Landroid/graphics/Paint; invoke-virtual {v3, v0, p1, v2, v6}, Landroid/graphics/Canvas;->drawText(Ljava/lang/String;FFLandroid/graphics/Paint;)V
v0
은 아까 decrypt
에서 복호화한 플래그 값이었다.
이것을 출력해준다.
iget-object v0, p0, Lno/tghack/gaiainvaders/GaiaInvadersView;->canvas:Landroid/graphics/Canvas; invoke-interface {p1, v0}, Landroid/view/SurfaceHolder;->unlockCanvasAndPost(Landroid/graphics/Canvas;)V .line 289 invoke-virtual {p0}, Lno/tghack/gaiainvaders/GaiaInvadersView;->pause()V
마지막으로 pause()
를 호출하며 if
문이 끝난다.
unlockCanvasAndPost()
역시 화면에 출력하는 라이브러리 함수이며 일련의 출력을 위한 과정이라고 이해하고 넘어가면 된다.
https://stackoverrun.com/ko/q/8485879
: smali에 대한 이해
https://github.com/JesusFreke/smali/wiki
: smali github wiki
https://source.android.com/devices/tech/dalvik/dalvik-bytecode
: invoke-XXX
에 대한 문법