本文由 Luzhuo 编写,转发请保留该信息.
原文: https://blog.csdn.net/Rozol/article/details/88755130
另外还需要一些基础知识: smali语法、arm指令、NDK开发
反编译:
使用该工具最好是去Github下去源码, 放到Eclipse里执行, 一旦反编译报错可以立即修改源码解决问题.
1.下载源码: https://github.com/iBotPeaches/Apktool
2.Eclipse安装Gradle插件
buildship
-> 搜索 -> Install -> 重启Eclipse3.导入项目
4.运行apktool
在brut.apktool包下的Main类下的main入口函数手动构建一个命令, 然后运行即可.
args = new String[]{"d", "C:\\apk\\xxx.apk", "-o", "C:\\apk\\xxx"};
回编译:
1.进行回编译:
args = new String[]{"b", "C:\\apk\\xxx1", "-o", "C:\\apk\\xxx_build.apk"};
2.签名:
jarsigner -verbose -keystore "C:\apk\case.key.jks" -signedjar "C:\apk\xxx_build_ok.apk" "C:\apk\xxx_build.apk" key0
-signedjar
参数:
3.安装
想要调试应用, 打开调试开关这是破解步骤的第一步.
不反编译的情况下:
Android配置信息放在 /system/build.prop
文件里
C:\Users\LZLuz>adb shell
shell@S658t:/ $ su
root@S658t:/ # cat /system/build.prop |grep ro
# begin build properties
ro.build.id=KOT49H
ro.build.display.id=VIBEUI_V2.0_1512_7.17.1_ST_S658t
ro.build.version.incremental=VIBEUI_V2.0_1512_7.17.1_ST_S658t
ro.custom.build.version=VIBEUI_V2.0_1512_7.17.1_ST_S658t
ro.build.version.sdk=19
ro.build.version.codename=REL
ro.build.version.release=4.4.2
ro.build.date=Mon Feb 9 21:38:28 CST 2015
ro开头的属性表示只读, 不可修改
可以通过命令单独获取某值
root@S658t:/ # getprop ro.product.model
Lenovo S658t
默认配置信息
root@S658t:/ # cat default.prop
#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.secure=1
ro.allow.mock.location=0
persist.mtk.aee.aed=on
ro.debuggable=0
ro.adb.secure=1
这里的ro.debuggable=0
为0表示判断应用中的AndroidManifest.xml文件中manifest标签是否有android:debuggable="true"
属性, 有则可以调试; 若为1, 表示所有程序均可调试.
接下来我们想办法修改这个值, 改为1:
我们使用ptrace注入到init进程, 修改内存中的属性值, 只要该进程不重启就会一直有效, 即使重启了, 再操作一遍即可.
1.下载mprop工具
2.拷贝到设备中
adb push C:\Code\mprop /sdcard/
3.执行命令
C:\Users\LZLuz>adb shell
shell@S658t:/ $ su
root@S658t:/ # cd sdcard/
1|root@S658t:/sdcard # ./mprop2 ro.debuggable 1
properties map area: b6f7e000-b6f9e000
00000000 84 e1 00 00 00 89 00 00 50 52 4f 50 ab d0 6e fc .�......PROP��n�
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
4.查看是够修改成功
root@S658t:/sdcard # getprop ro.debuggable
1
为1说明修改成功
5.使用DDMS查看可调试列表
AndroidStudio3.2找不到DDMS, 看来只能手动找了:
SDK -> tools -> monitor.bat
接着把adb进程重启下:
C:\Users\LZLuz>adb kill-server
C:\Users\LZLuz>adb start-server
并且可通过jdwp
命令查看可调式进程id:
C:\Users\LZLuz>adb jdwp
2215
22916
26277
27256
通过dumpsys package
命令查看包信息:
adb shell dumpsys package me.luzhuo.hacker
反编译的情况下:
1.反编译
2.manifest标签添加 android:debuggable="true"
属性
3.回编译
4.签名
5.安装
以上步骤的具体执行, 见apktool介绍.
编写一个apk, 然后通过签名导出.
以下是用jadx-gui看到apk源码
package me.luzhuo.smalidemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
/* Access modifiers changed, original: protected */
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView((int) R.layout.activity_main);
((TextView) findViewById(R.id.tv_hello)).setText(getString("hello"));
}
private String getString(String str) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append("world!");
return stringBuilder.toString();
}
}
我们的目的是针对getString(), 打印传入的参数, 打印运算后的结果, 返回我们期望的结果.
1.反编译apk生成smali代码
使用apktool进行反编译, 生成smali源码
.method private getString(Ljava/lang/String;)Ljava/lang/String;
.locals 1
.line 19
new-instance v0, Ljava/lang/StringBuilder;
invoke-direct {v0}, Ljava/lang/StringBuilder;->()V
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
const-string p1, "world!"
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object p1
return-object p1
.end method
2.修改smali代码
修改后的代码:
扩展了2个非参数寄存器, 用来存放"MyLog"和"AndroidSmali!",
然后打印了 参数p1 和 结果p1 的值,
最后定义了"AndroidSmali!"字符串给返回.
.method private getString(Ljava/lang/String;)Ljava/lang/String;
.locals 3
const-string v3, "MyLog"
# print param
invoke-static {v3, p1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 19
new-instance v0, Ljava/lang/StringBuilder;
invoke-direct {v0}, Ljava/lang/StringBuilder;->()V
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
const-string p1, "world!"
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object p1
# print result
invoke-static {v3, p1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
# change result
const-string v2, "AndroidSmali!"
return-object v2
.end method
3.回编译, 签名, 安装
如果安装时被告知是非法包, 并且用jadx-gui查看APK signature时, 包空指针, 这说明你的包本身就不能被安装, 你得通过签名导出的方式获得, 或者通过app市场下载的方式获得.
4.查看日志输出
使用命令adb logcat -s MyLog
即可.
C:\Users\LZLuz>adb logcat -s MyLog
--------- beginning of crash
--------- beginning of system
--------- beginning of main
03-09 19:35:41.829 5622 5622 I MyLog : hello
03-09 19:35:41.829 5622 5622 I MyLog : helloworld!
smalidea地址
通过AndroidStudio3.2动态调试smali代码, 使用baksmali
插件.
1.安装插件
目前最新的插件是smalidea-0.05.zip
, 20170331更新的.
去Browse Repositories搜了下, 发现没有这个插件, 直接去官方下载然后安装即可.
2.打开调试
adb shell
su
cd sdcard/
./mprop2 ro.debuggable 1
getprop ro.debuggable
3.安装应用
安装apk, 不需要做任何修改
4.获得smali
用apk反编译apk, 得到smali代码, AndroidStudio中打开现有AndroidStudio项目(File -> Open -> 选择反编译后文件夹 -> New window)
右击smali目录, 把Mark Directory As
改成 Sources Root
5.配置调试
配置远程调试: Run -> Edit Configurations -> + -> Remote (如果8700被占, 可换成任意端口)
Host: localhost
Port: 8700
获取目标进程
C:\Users\LZLuz>adb shell ps | findstr "com.example.simpleencryption"
u0_a121 8938 140 558708 28800 ffffffff 00000000 S com.example.simpleencryption
可见目标进程为8938
启动应用并将JDWP服务转发到localhost (注:8700被占了, 我换成了8701)
adb forward tcp:8701 jdwp:8938
6.调试应用
打调试断点 -> 调试
调试结束清除端口绑定
adb forward --remove-all
动态调试比静态调试的好处是, 静态需要添加Log然后打包签名安装才能看到运算值, 而动态调试不需要修改apk直接运行调试就能看到值, 比静态调试简便了许多.
IDA的基本使用的文章很早之前写过一篇
打开IDA -> new -> 选择.so文件打开
.text:000000000000059C ; jstring Java_me_luzhuo_idaso_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz)
.text:000000000000059C EXPORT Java_me_luzhuo_idaso_MainActivity_stringFromJNI
.text:000000000000059C Java_me_luzhuo_idaso_MainActivity_stringFromJNI
.text:000000000000059C ; DATA XREF: LOAD:0000000000000330↑o
.text:000000000000059C
.text:000000000000059C var_18 = -0x18
.text:000000000000059C var_10 = -0x10
.text:000000000000059C env = -8
.text:000000000000059C var_s0 = 0
.text:000000000000059C
.text:000000000000059C ; __unwind {
.text:000000000000059C SUB SP, SP, #0x30
.text:00000000000005A0 STP X29, X30, [SP,#0x20+var_s0]
.text:00000000000005A4 ADD X29, SP, #0x20
.text:00000000000005A8 ADRP X8, #aHelloFromC@PAGE ; "Hello from C!"
.text:00000000000005AC ADD X8, X8, #aHelloFromC@PAGEOFF ; "Hello from C!"
.text:00000000000005B0 STUR X0, [X29,#env]
.text:00000000000005B4 STR X1, [SP,#0x20+var_10]
.text:00000000000005B8 STR X8, [SP,#0x20+var_18]
.text:00000000000005BC LDUR X8, [X29,#env]
.text:00000000000005C0 LDR X8, [X8]
.text:00000000000005C4 LDR X8, [X8,#0x538]
.text:00000000000005C8 LDUR X0, [X29,#env]
.text:00000000000005CC LDR X1, [SP,#0x20+var_18]
.text:00000000000005D0 BLR X8
.text:00000000000005D4 LDP X29, X30, [SP,#0x20+var_s0]
.text:00000000000005D8 ADD SP, SP, #0x30
.text:00000000000005DC RET
.text:00000000000005DC ; } // starts at 59C
.text:00000000000005DC ; End of function Java_me_luzhuo_idaso_MainActivity_stringFromJNI
按F5就成将其转成伪C代码, 可见其对jni.h的支持还是相当不错的
__int64 __fastcall Java_me_luzhuo_idaso_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz)
{
return ((__int64 (__fastcall *)(JNIEnv *, const char *))(*env)->NewStringUTF)(env, "Hello from C!");
}
而原来的C代码是这样子的
jstring Java_me_luzhuo_idaso_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz){
char* cstr = "Hello from C!";
return (*env)->NewStringUTF(env, cstr);
}
目的: 通过调试获取方法的输入和输出内容.
1.打开调试
adb shell
su
cd sdcard/
./mprop2 ro.debuggable 1
getprop ro.debuggable
2.获取端口号
a.安装apk
b.运行apk
c.查看端口 (port:8700)(SDK -> tools -> monitor.bat)
3.IDA配置
a.把 IDA安装目录/dbgsrv/
下的android_server
文件拷贝手机/sdcard
目录下
adb push "C:\DevelopSoftware\IDA 7.0\dbgsrv\android_server" /sdcard/
b.运行./android_server
adb shell
su
cd sdcard
./android_server
c. 连接端口
adb forward tcp:23946 tcp:23946
(这里的端口是固定的)
调试结束清除端口绑定
adb forward --remove-all
d.打开IDA -> Go -> Debugger -> Attach -> Remote ARMLinux/Android debugger -> 填写信息 -> OK
Hostname: 127.0.0.1
Port: 23946
e.然后会弹出一个Choose process to attach to
对话框, android_server会帮你把所有进程信息给列出来, 按Ctrl+F进行搜索, 然后OK
4.开始调试
a.查找.so文件的基地址:
按Ctrl+S弹出choose segment to jump
, 然后按Ctrl+F搜索so文件名, 可见基地址为:5C035000.
b.获取函数地址:
再开一个IDA, 为了方便区分, 我们就叫他IDA2吧(之前那个就叫IDA), 选择new, 并打开.so文件.
找到要分析的函数Java_me_luzhuo_idaso_MainActivity_stringFromJNI
, 可以看到函数相对地址为:778
c.得到函数的绝对地址为:5C035778, 在IDA使用G键快速跳到这个绝对地址, 然后点击左边的小绿点下断点(F2), 再然后点击左上角的绿色三角形按钮运行(F9)
d.触发native代码运行, 按F8单步调试, 可以看到寄存器里的内容.
可以点寄存器地址后面的跳转按钮, 跳到该数据所在的内存地址.
输出的结果为:ifmmp!gspn!kbwb"
当你用IDA调试时, 打了断点, 然后点击绿色小三角准备开始调试, 结果应用却退出了, 这说明该应用被加了反调试.
这是未被调试前, TracerPid: 0
C:\Users\LZLuz>adb shell
shell@S658t:/ $ ps | grep com.yao
u0_a220 13387 140 560248 27988 ffffffff 00000000 S com.yaotong.crackme
shell@S658t:/ $ cat /proc/13387/status
Name: yaotong.crackme
State: S (sleeping)
Tgid: 13387
Pid: 13387
PPid: 140
TracerPid: 0
Uid: 10220 10220 10220 10220
Gid: 10220 10220 10220 10220
这是被调试后, TracerPid: 11868
shell@S658t:/ $ cat /proc/13387/status
Name: yaotong.crackme
State: t (tracing stop)
Tgid: 13387
Pid: 13387
PPid: 140
TracerPid: 11868
Uid: 10220 10220 10220 10220
Gid: 10220 10220 10220 10220
可见反调试就是不断的监测自己应用进程的状态值(/proc/[pid]/status
), 如果TracerPid
非0则说明被调试了, 就结束应用.
我们要解决这个问题就要从以下两个方法找解决方案:
.init_array
: so最先加载段信息, 一般用来so解密JNI_OnLoad
: System.loadLibrary调用时执行, 没.init_array
早1.打开调试
2.debug启动应用
用am的debug方式启动应用 (D要大写), 这样就不会执行static方法
adb shell am start -D -n com.yaotong.crackme/com.yaotong.crackme.MainActivity
3.设置Debugger Option
启动IDA(Go), 然后进行连接(前面连过很多次了, 这里就不细说了)
需要使它暂停在load前, 通过 Debugger -> Debugger Option 设置
4.运行IDA
a.点绿色小三角运行(F9)
b.运行attach命令
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
如果8700出现无法附加到目标 VM。
, 那么就选8700前面的那个端口号
5.下断点
a.在JNI_OnLoad方法下断点
按Ctrl+S获得libcrackme.so的地址为:5F707000.
JNI_OnLoad的相对地址为:00001B9C
JNI_OnLoad的绝对地址为:5F708B9C
按G跳到该位置, 并打上断点.
b.在libc的fopen方法下断点
按Ctrl+S找到libc.so, 打开它,
按Alt+T搜索fopen
关键字, 然后下断点 (会自动转为代码形式)
6.调试
进行单步调试, 我们发现BLX(执行) R7寄存器创建了一个线程,
这个线程执行的函数体是unk_5F62A6A4.
这里的unk_5F62A30C被重复执行, 这很可能就是反调试代码.
然后按F9运行到fopen处, LR是5F62A420, 我们点进去看看
不断的点上一层, 最后你会发现就是unk_5F62A30C调的他, 现在可以大概率认定它就是反调试代码.
然后继续单步执行, 看看fopen打开的是哪个文件
现在不用怀疑了, 他就是反调试代码.
7.修改so
我们发现BLX R7创建的线程调用unk_5F62A6A4函数就是用来反调试的, 那么只需不让它执行就行了.
5F708C58 BLX R7
的相对地址为: 5F708C58 - 5F707000 = 1C58,
打开IDA2, 按G跳到1C58.
切换为Hex View窗口, 将代码NOP, 也就是把1C58位置的值 37 FF 2F E1 改为 00 00 A0 E1
, 然后右键applay change
改完之后 edit -> patch program -> apply patches to input file…, 最后覆盖so文件重新打包签名运行即可.
加固分为 apk加固 和 so加固.
apk被加固后, 代码在jadx-gui里都看不到了, 只有一个继承Application的壳, 虽然代码看不到了, 但是AndroidManifest.xml清单里的申明都还在.
解决思路: 不管源码被如何加固, 最终都需要被加载到内存中运行, 只需要找到dex在内存中的位置, dump出来即可.
1.打开调试
2.获取libdvm.so文件
libdvm.so文件在/system/lib
目录下
adb pull /system/lib/libdvm.so C:\apk\libdvm.so
3.debug启动应用
adb shell am start -D -n com.ali.tg.testapp/com.ali.tg.testapp.MainActivity
4.设置Debugger Option
启动IDA(Go), 然后连接, 再去设置Debugger Option
5.运行IDA
a.点绿色小三角运行(F9)
b.运行attach命令
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
6.下断点
a.打开IDA2获取libdvm.so的dvmDexFileOpenPartial()相对地址.
dvmDexFileOpenPartial(void const*, int, DvmDex **)
第1个参数为dex内存起始地址, 第2个参数为dex大小.
dvmDexFileOpenPartial的相对地址为: 00044F1C
地址为: 417CF000 + 00044F1C = 41813F1C
b.IDA按G, 输入地址打上断点
7.调试
按F9运行到断点出, 然后单步执行PUSH指令.
dump脚本代码块:
static main(void){
auto fp, dex_addr, end_addr;
fp = fopen("C:\\apk\\dump.dex", "wb");
end_addr = r0 + r1;
for ( dex_addr = r0; dex_addr < end_addr; dex_addr++ )
fputc(Byte(dex_addr), fp);
}
按Shift+F2调出脚本, 把上面dump代码粘贴进去, run之后, 指定目录下生成dump.dex
文件, 直接用jadx-gui打开就能看到源码了.
也可以使用baksmali工具生成smali代码
java -jar baksmali-2.2.6.jar disassemble -o C:\apk\dexout\ dump.dex
主要断点处 | 作用 | 位于so库 |
---|---|---|
JNI_OnLoad | 反调试 | xxx.so |
fopen | 反调试 | libc.so |
fgets | 反调试 | libc.so |
dvmDexFileOpenPartial | apk脱壳 | libdvm.so |
提供练习的资源文件在这: https://download.csdn.net/download/rozol/11051587