200 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Smali - 디컴파일/[수정]/컴파일
{{#include ../../banners/hacktricks-training.md}}
때때로 애플리케이션 코드를 수정하여 숨겨진 정보를 얻는 것이 흥미로울 수 있습니다(예: 잘 난독화된 비밀번호나 플래그). 이럴 때 apk를 디컴파일하고 코드를 수정한 뒤 다시 컴파일하는 것이 유용합니다.
**Opcodes reference:** [http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html](http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html)
## 빠른 방법
Visual Studio Code와 [APKLab](https://github.com/APKLab/APKLab) 확장 기능을 사용하면, 어떤 명령도 실행하지 않고도 애플리케이션을 **자동으로 디컴파일**, 수정, **재컴파일**, 서명 및 설치할 수 있습니다.
또한 이 작업을 훨씬 쉽게 해주는 또 다른 **script**는 [**https://github.com/ax/apk.sh**](https://github.com/ax/apk.sh) 입니다.
## APK 디컴파일
APKTool을 사용하면 **smali code and resources**에 접근할 수 있습니다:
```bash
apktool d APP.apk
```
만약 **apktool**이 어떤 오류를 발생시키면, [ installing the **latest version**](https://ibotpeaches.github.io/Apktool/install/ )을 시도해 보세요
살펴봐야 할 몇 가지 **흥미로운 파일들**:
- _res/values/strings.xml_ (and all xmls inside res/values/*)
- _AndroidManifest.xml_
- Any file with extension _.sqlite_ or _.db_
만약 `apktool`이 애플리케이션 디코딩에 **문제가** 있다면 [https://ibotpeaches.github.io/Apktool/documentation/#framework-files](https://ibotpeaches.github.io/Apktool/documentation/#framework-files) 를 확인하거나 인자 **`-r`**(리소스 디코딩 안 함)을 사용해 보세요. 그러면 문제가 소스 코드가 아니라 리소스에 있었다면 해당 문제는 발생하지 않습니다(리소스도 디컴파일하지 않습니다).
## smali 코드 변경
명령(instructions)을 **변경**하거나, 일부 변수의 **값(value)**을 바꾸거나, 새로운 명령을 **추가**할 수 있습니다. 저는 Smali 코드를 [**VS Code**](https://code.visualstudio.com)로 수정합니다. 그 다음 **smalise extension**를 설치하면 에디터가 어떤 **instruction이 잘못되었는지** 알려줍니다.\
몇 가지 **예제**는 다음에서 찾을 수 있습니다:
- [Smali changes examples](smali-changes.md)
- [Google CTF 2018 - Shall We Play a Game?](google-ctf-2018-shall-we-play-a-game.md)
또는 [**아래의 일부 Smali 변경 설명을 확인해 보세요**](smali-changes.md#modifying-smali).
## APK 재컴파일
코드를 수정한 후 다음을 사용해 코드를 **재컴파일**할 수 있습니다:
```bash
apktool b . #In the folder generated when you decompiled the application
```
새 APK를 _**dist**_ 폴더 **내부**에서 **컴파일**합니다.
만약 **apktool**이 **오류**를 발생시키면, [ installing the **latest version**](https://ibotpeaches.github.io/Apktool/install/)을 시도해 보세요
### **새 APK 서명**
그런 다음, **키를 생성**해야 합니다 (비밀번호와 무작위로 채워도 되는 몇 가지 정보를 입력하라는 요청을 받습니다):
```bash
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias <your-alias>
```
마지막으로 새 APK에 **서명**하세요:
```bash
jarsigner -keystore key.jks path/to/dist/* <your-alias>
```
### 새 애플리케이션 최적화
**zipalign**은 Android 애플리케이션(APK) 파일에 중요한 최적화를 제공하는 아카이브 정렬 도구입니다. [자세한 정보는 여기](https://developer.android.com/studio/command-line/zipalign).
```bash
zipalign [-f] [-v] <alignment> infile.apk outfile.apk
zipalign -v 4 infile.apk
```
### **새 APK에 (다시?) 서명하기**
jarsigner 대신 [**apksigner**](https://developer.android.com/studio/command-line/)를 **선호한다면**, **zipaling으로 최적화를 적용한 후** APK에 **서명해야 합니다**. 하지만 jarsigner로(zipalign 이전에) 또는 aspsigner로(zipaling 이후에) **애플리케이션을 한 번만 서명하면 된다는 점에 유의하세요**.
```bash
apksigner sign --ks key.jks ./dist/mycompiled.apk
```
## Smali 수정하기
다음 Hello World Java 코드의 경우:
```java
public static void printHelloWorld() {
System.out.println("Hello World")
}
```
Smali 코드는 다음과 같습니다:
```java
.method public static printHelloWorld()V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello World"
invoke-virtual {v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
```
Smali 명령어 집합은 [here](https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions)에서 확인할 수 있습니다.
### 가벼운 변경
### 함수 내부 변수의 초기값 수정
일부 변수들은 함수 시작 부분에서 opcode _const_를 사용해 정의됩니다. 해당 값을 수정하거나 새 변수를 정의할 수 있습니다:
```bash
#Number
const v9, 0xf4240
const/4 v8, 0x1
#Strings
const-string v5, "wins"
```
### 기본 작업
```bash
#Math
add-int/lit8 v0, v2, 0x1 #v2 + 0x1 and save it in v0
mul-int v0,v2,0x2 #v2*0x2 and save in v0
#Move the value of one object into another
move v1,v2
#Condtions
if-ge #Greater or equals
if-le #Less or equals
if-eq #Equals
#Get/Save attributes of an object
iget v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save this.o inside v0
iput v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save v0 inside this.o
#goto
:goto_6 #Declare this where you want to start a loop
if-ne v0, v9, :goto_6 #If not equals, go to: :goto_6
goto :goto_6 #Always go to: :goto_6
```
### 더 큰 변경사항
### 로깅
```bash
#Log win: <number>
iget v5, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Get this.o inside v5
invoke-static {v5}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; #Transform number to String
move-result-object v1 #Move to v1
const-string v5, "wins" #Save "win" inside v5
invoke-static {v5, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I #Logging "Wins: <num>"
```
권장사항:
- 함수 안에서 선언된 변수를 사용할 예정이라면 (선언된 v0,v1,v2...) 이 줄들을 _.local <number>_와 변수 선언들 (_const v0, 0x1_) 사이에 넣으세요.
- 함수 코드의 중간에 로깅 코드를 넣고 싶다면:
- 선언된 변수 수에 2를 더하세요: Ex: from _.locals 10_ to _.locals 12_
- 새 변수들은 이미 선언된 변수들의 다음 번호여야 합니다 (이 예에서는 _v10_과 _v11_이어야 합니다, v0부터 시작하는 것을 기억하세요).
- 로깅 함수의 코드를 변경하여 _v10_과 _v11_을 _v5_와 _v1_ 대신 사용하세요.
### Toasting
함수 시작 부분에 있는 _.locals_의 숫자에 3을 더하는 것을 잊지 마세요.
이 코드는 **함수의 중간**에 삽입되도록 준비되어 있습니다 (**변경**해야 하는 **변수**의 수를 필요에 따라 조정하세요). 이 코드는 **this.o의 값**을 **변환**하여 **String**으로 만들고, 그 값으로 **toast**를 **만듭니다**.
```bash
const/4 v10, 0x1
const/4 v11, 0x1
const/4 v12, 0x1
iget v10, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I
invoke-static {v10}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v11
invoke-static {p0, v11, v12}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v12
invoke-virtual {v12}, Landroid/widget/Toast;->show()V
```
### 시작 시 네이티브 라이브러리 로드 (System.loadLibrary)
때때로 다른 JNI 라이브러리보다 먼저 초기화되도록 네이티브 라이브러리를 미리 로드해야 할 때가 있습니다(예: 프로세스 로컬 telemetry/logging을 활성화하기 위해). 정적 초기화자(static initializer)나 Application.onCreate() 초기에 System.loadLibrary() 호출을 주입할 수 있습니다. 정적 클래스 초기화자(<clinit>)용 예제 smali:
```smali
.class public Lcom/example/App;
.super Landroid/app/Application;
.method static constructor <clinit>()V
.registers 1
const-string v0, "sotap" # library name without lib...so prefix
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
return-void
.end method
```
또는 동일한 두 지시문을 Application.onCreate()의 시작 부분에 배치하여 라이브러리가 가능한 한 빨리 로드되도록 하세요:
```smali
.method public onCreate()V
.locals 1
const-string v0, "sotap"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
invoke-super {p0}, Landroid/app/Application;->onCreate()V
return-void
.end method
```
참고:
- 라이브러리의 올바른 ABI 변형이 lib/<abi>/ (예: arm64-v8a/armeabi-v7a) 아래에 존재하는지 확인하여 UnsatisfiedLinkError를 방지하세요.
- 매우 일찍 로드(class static initializer)하면 native logger가 이후의 JNI 활동을 관찰할 수 있습니다.
## 참고
- SoTap: 경량의 앱 내 JNI (.so) 동작 로거 [github.com/RezaArbabBot/SoTap](https://github.com/RezaArbabBot/SoTap)
{{#include ../../banners/hacktricks-training.md}}