mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
200 lines
9.1 KiB
Markdown
200 lines
9.1 KiB
Markdown
# 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}}
|