Notes-Android-II

NDK汇编(开发)

基础配置

https://www.jianshu.com/p/c546783ad284

idea安装cmake和NDK
1、ubuntu自动加进环境变量
2、windows要自己添加

source ~/.bashrc

1
ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk

IDA远程调试:

1
2
1、把 IDA dbgsrv目录下的文件在手机执行 android_server 
2、用32位IDA Debugger调试程序

elf文件

arm 模式:定长指令集,四个字节

thumb 模式:不定长

T标志位

clang 开发

https://developer.android.com/ndk/guides/other_build_systems?hl=zh-cn

https://www.cnblogs.com/burner/p/clang-fen-si-bu-bian-yimainc.html

clang路径在SDK/NDK下

1
2
C:\Users\admin\AppData\Local\Android\Sdk\ndk\28.0.12674087\toolchains\llvm\prebuilt\windows-x86_64\bin
C:\Users\admin\AppData\Local\Android\Sdk\ndk\28.0.12674087\toolchains\llvm\prebuilt\linux-x86_64\bin

GDB

安装GEF

GDB multiarch 远程调试,原理同IDA远程调试,需上传服务端文件

1
2
3
4
5
6
7
8
Sdk/ndk/23.1.7779620/prebuilt/android-arm/gdbserver
./gdbserver :1234 hello.o

gdb-multiarch
target remote ip:port

gdb-multiarch
gef-remote -p 3929 ip:port #雷电需要用x86_64版本,提示权限不足

ARM 案例

案例一

在 .o文件里用汇编代替原有函数(print、getchar)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#printf
#只有return 0的汇编
main:
.fnstart
@ %bb.0:
.pad #4
sub sp, sp, #4
movw r0, #0
str r0, [sp]
movw r0, #0
add sp, sp, #4
bx lr


main:
.fnstart
push {lr}

ldr r0,[r1,#4]
bl printf

movw r0, #0
pop {lr}
bx lr

案例二

根据汇编还原为C代码

1、IDA打开v7a的 SO文件

2、找到导出 check

3、修改第一个默认参数为 JNIEnv,这样就能关联到内置的函数

4、算法还原

这些R0 、R1指代第一第二个参数吗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
TestDec                                 ; CODE XREF: j_TestDec+8↑j
.text:00001062 ; DATA XREF: LOAD:00000250↑o ...
.text:00001062 ; __unwind {
.text:00001062 PUSH {R4,R5,R7,LR}
.text:00001064 ADD R7, SP, #8
.text:00001066 MOV R4, R0
.text:00001068 BLX strlen
.text:0000106C CMP R0, #2
.text:0000106E BCC loc_108A
.text:00001070 MOVS R5, #0
.text:00001072
.text:00001072 loc_1072 ; CODE XREF: TestDec+26↓j
.text:00001072 ADDS R1, R4, R5
.text:00001074 LDRB R0, [R4,R5]
.text:00001076 LDRB R2, [R1,#0x10]
.text:00001078 STRB R2, [R4,R5]
.text:0000107A ADDS R5, #1
.text:0000107C STRB R0, [R1,#0x10]
.text:0000107E MOV R0, R4 ; s
.text:00001080 BLX strlen
.text:00001084 CMP.W R5, R0,LSR#1
.text:00001088 BCC loc_1072
.text:0000108A
.text:0000108A loc_108A ; CODE XREF: TestDec+C↑j
.text:0000108A LDRB R0, [R4]
.text:0000108C CBZ R0, locret_10B8
.text:0000108E LDRB R1, [R4,#1]
.text:00001090 STRB R1, [R4]
.text:00001092 STRB R0, [R4,#1]
.text:00001094 MOV R0, R4 ; s
.text:00001096 BLX strlen
.text:0000109A CMP R0, #3
.text:0000109C BCC locret_10B8
.text:0000109E MOVS R5, #0
.text:000010A0
.text:000010A0 loc_10A0 ; CODE XREF: TestDec+54↓j
.text:000010A0 ADDS R0, R4, R5
.text:000010A2 LDRB R1, [R0,#2]
.text:000010A4 LDRB R2, [R0,#3]
.text:000010A6 STRB R2, [R0,#2]
.text:000010A8 STRB R1, [R0,#3]
.text:000010AA MOV R0, R4 ; s
.text:000010AC BLX strlen
.text:000010B0 ADDS R1, R5, #4
.text:000010B2 ADDS R5, #2
.text:000010B4 CMP R1, R0
.text:000010B6 BCC loc_10A0
.text:000010B8
.text:000010B8 locret_10B8 ; CODE XREF: TestDec+2A↑j
.text:000010B8 ; TestDec+3A↑j
.text:000010B8 POP {R4,R5,R7,PC}
.text:000010B8 ; End of function TestDec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int TestDec(char *str){
char *r4 = str;
int lenstr = strlen(str);
if(lenstr>=2){
for(int r5=0;r5< lenstr>>1;r5++){
char * r1 = r4+r5;
char r0 = *(r4+r5);
char r2 = *(r1+16);
*(r4+r5) = r2;
*(r1+16) = r0;
}
}
char r0 = *(r4);
if(r0){
char r1 = *(r4+1);
*(r4) = r1;
*(r4+1) = r0;
if(lenstr >= 3){
for(int r5=0; r5+4 < lenstr;r5+=2){
char *rr0 = r4+r5;
char rr1 = *(rr0+2);
char rr2 = *(rr0+3);
*(rr0+2) = rr2;
*(rr0+3) = rr1;
}
}
}
printf("%s\n",r4);
return 0;
}

int main(){
char *s = "9007b55be5bf95adf72c5a365694182a";
int lenstr = strlen(s);
char *arg = (char*)malloc(lenstr+1);
memset(arg,0,lenstr+1);
memcpy(arg,s,lenstr);
TestDec(arg);
return 0;
}

可引入IDA 提供的 defs.h 后直接运行伪代码

https://www.52pojie.cn/forum.php?mod=viewthread&tid=1584115&highlight=ida

IDA快捷键&常用方法

  • 汇编 & 伪代码的跳转(空格、Tab)
  • 强制当做代码/数据(C、D)
  • 数据 convert
  • 重命名
  • 查找调用链,Xref graph to
  • 插件使用

https://blog.csdn.net/qq_41028985/article/details/119407917

熟悉算法

用于识别魔改算法或分析算法

输入信息/输出信息要hex编码

md5算法

SHA1算法

SHA2/256/512等后续一堆

HMAC(salt)

  • 自行根据字符串计算hmac对照

https://space.bilibili.com/253413704

1
2
3
message:加密消息
key:加密密钥(需要补充)
opad 和 ipad 都是固定

DES

1
2
3
4
5
https://www.bilibili.com/video/BV1134y1Y71j
https://www.bilibili.com/video/BV1QW411B7A4
https://www.bilibili.com/video/BV1KQ4y127AT
https://juejin.cn/post/7084242293816983589
https://cloud.tencent.com/developer/article/1497864

https://www.cnblogs.com/jikexianfeng/p/10192024.html

CLion

分组加密

https://blog.csdn.net/a745233700/article/details/102311776

https://segmentfault.com/a/1190000040964999

https://www.bilibili.com/video/BV1U8411f74f/

AES

https://www.bilibili.com/video/BV1824y1y7gc

12345678,8个字节,64个位

APK未抹去符号表,MD5算法是否魔改分析

1、提取 so,用IDA分析

2、根据原MD5的几个关键步骤,分析汇编。搜索固定的表内容,没被魔改可以搜到

3、Hook SO地址

MD5常见魔改点:

1、明文加盐或填充

2、初始魔数的修改

3、常量表K

4、左移次数

5、F,G,H,I四个非线性变换函数等的逻辑

unidbg

IDA快捷键:H、Y、Tab

IDA 动态调试之反调试

遗留问题:虚拟机debuggable=1

1、getprop ro.debuggable = 0 的话 IDA 动态调试看不到进程名,部分功能被屏蔽

真机用:agiskHide Props Config 修改成功

虚拟机暂未成功

2、查看线程名要用新版的IDA,实测7.0的看不到,7.7的就看得到

1
2
3
4
5
6
1.Attach模式 附加 so动态调试。过掉反调试的方法
2.Spwan模式 过掉反调试的方法:这里讲原理

1. nop掉
2. 挂起线程
3. frida hook

IDA调试tips:

1
2
3
4
5
6
1、真机无网络通过USB调试,需配置端口转发才能IDA远程调试
adb forward tcp:23946 tcp:23946 本机->设备
adb reverse tcp:23946 tcp:23946 设备->本机
adb forward --remove tcp:<port>

2、静态调试和动态调试,IDA自动化分析的注释不同

步骤:

1
2
3
4
1、进入IDA动态调试,点击运行崩溃
2、在Threads窗口,找和包名一样的线程,Suspended 挂起运行正常
3、在Modules窗口,搜索包名/运行关键词/check/security,找到检测/校验模块,双击进入对应代码,下断点
4、点击触发,Tab切换伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var ByPassTracerPid = function () {
var fgetsPtr = Module.findExportByName("libc.so", "fgets");
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
var retval = fgets(buffer, size, fp);
var bufstr = Memory.readUtf8String(buffer);
if (bufstr.indexOf("TracerPid:") > -1) {
Memory.writeUtf8String(buffer, "TracerPid:\t0");
console.log("tracerpid replaced: " + Memory.readUtf8String(buffer));
}
return retval;
}, 'pointer', ['pointer', 'int', 'pointer']));
};
setImmediate(ByPassTracerPid);

参考文章:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Android修改ro.debuggable 的四种方法
https://blog.csdn.net/jinmie0193/article/details/111355867

IDA动态调试破解AliCrackme与反调试对抗
https://blog.csdn.net/weixin_39190897/article/details/120808079

[原创]2015年AliCrackMe第二题的分析之人肉过反调试
https://bbs.pediy.com/thread-258595.htm

常用命令:
adb shell am start -D -n com.yaotong.crackme/.MainActivity
adb shell ps | findstr crackme
jdwp协议端口转发:adb forward tcp:8700 jdwp:2494
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

APP过ROOT检测

安装面具:
https://www.bilibili.com/video/BV1UN4y137Z5/?vd_source=43c2c404de6d798650d44c856ee1e992
https://bbs.kanxue.com/thread-263203.htm

修改固有面具(修改源码)

https://blog.csdn.net/qq_41155858/article/details/127885048

root检测:
1.在代码中检测 -> hook/修改重打包(代码固有API检测是否root?)
2.检测系统属性(检测app安装的系统环境参数)
3.查找字符串(查找root关键词,比如su)

对抗方法:
1.hook
2.最新版magisk(配置排除列表、随机文件名) + shamiko(可有可无)+随机magisk
3.需要重新编译面具 改su名称(这个有点麻烦,最新版gsyh过不去就这个)

1
2
3
4
小米6重装最新版Magisk就可以了,直接过三个(新版的gs只开还是过不了)
adb reboot bootloader
https://magiskcn.com/
https://blog.csdn.net/qq_42663692/article/details/135704736

绕过的具体操作
1、最新版面具
2、修改面具名称
3、用自带的隐藏root模块
4、修改su名称

Tips:
1、面具安装在真机,需要提取真机boot,通过面具修复后生成.img文件,再重新刷回手机
2、面具安装在模拟器,要安装修改的magisk-delta版本,有直接刷在系统选项(https://magisk-delta.com/)

自编译面具

环境:win10

1
2
3
4
5
6
7
8
1、用AS自带的 java11,设置为环境变量
2、全程挂梯子
3、windows 用旧版 python,如python3.8

git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git
python build.py ndk
python build.py clean
python build.py -v all

需要修改成自定义指令的地方

1
2
val isActive = versionCode > 0 追踪逻辑
"su","--mount-master"

Apktoolbox 重打包

windwos坑太多,用ubuntu试试,用ubuntu挂梯子

参考链接:

1
2
https://blog.csdn.net/qq_41155858/article/details/127885048?spm=1001.2014.3001.5506
https://zhuanlan.zhihu.com/p/385255256

frida 反调试

1
2
3
4
5
6
7
8
9
10
11
12
13
参考文章: [翻译]多种特征检测 Frida ->https://bbs.pediy.com/thread-217482.htm

从inlinehook角度检测frida->https://blog.csdn.net/u010559109/article/details/120846740
9
Android逆向——过frida检测+so层算法逆向https://blog.csdn.net/weixin_43889136/article/details/127713563


原理:当被调试器附加之后 不为0:https://wiki.hackjie.com/2728.html

ptrace占坑:
https://bbs.pediy.com/thread-268155.htm#msg_header_h2_16

adb shell dumpsys window w |grep \/ |grep name=

fridacheck app

Process terminated douban strstr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 为什么这样就可以hook,不输入脚本?还是原先有脚本?只是绕过-UF?
frida -U -f xxx
%resume

ps -e | grep xxx
adb shell dumpsys window w | grep \/ | grep name=

# 修改端口、修改文件名(进程名)
./frida-server-pass -l 0.0.0.0:1234
adb forward tcp:1234 tcp:1234
frida -H 手机ip:1234 -F

hook_RegisterNatives.js
https://github.com/zhkl0228/unidbg
# 获取的偏移值可直接在IDA g搜索,此时是加载状态吗,直接hook Native为什么不准确
修改第一个参数为 JNIEnv*

# 线程检测
通过IDA分析所用检测的方法,根据检测逻辑去hook fgets、fopen,为什么直接hook整个检测函数

function hook_readlink() {}
function main(){
hook_readlink()
}
setImmediate(main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// code.js
function hook_strstr() {
// char *strstr(const char *haystack, const char *needle) 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符
var strstr = Module.findExportByName(null, "strstr");
if (null !== strstr) {
Interceptor.attach(strstr, {
onEnter: function (args) {
this.frida = Boolean(0);
this.haystack = args[0];
this.needle = args[1];
if (this.haystack.readCString() !== null && this.needle.readCString() !== null) {
if (this.haystack.readCString().indexOf("frida") !== -1 ||
this.needle.readCString().indexOf("frida") !== -1 ||
this.haystack.readCString().indexOf("gum-js-loop") !== -1 ||
this.needle.readCString().indexOf("gum-js-loop") !== -1 ||
this.haystack.readCString().indexOf("gmain") !== -1 ||
this.needle.readCString().indexOf("gmain") !== -1 ||
this.haystack.readCString().indexOf("linjector") !== -1 ||
this.needle.readCString().indexOf("linjector") !== -1) {
this.frida = Boolean(1);
}
console.log("this.haystack.readCString():",this.haystack.readCString())
console.log("this.needle.readCString():",this.needle.readCString())
}
},
onLeave: function (retval) {
if (this.frida) {
retval.replace(ptr("0x0"));
}

}
})
console.log("anti anti-frida");
}

console.log("hook strstr over")
}

function main(){
hook_strstr();
}

setImmediate(main);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
function bypass_fgets() {
var fgetsPtr = Module.findExportByName("libc.so", "fgets");
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
console.log(fgetsPtr,fgets);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
var retval = fgets(buffer, size, fp);
var bufstr = buffer.readCString();
if (bufstr.indexOf("TracerPid:") > -1) {
Memory.writeUtf8String(buffer, "TracerPid:\t0");
}

if (bufstr.indexOf("Name:\tgmain") > -1 || bufstr.indexOf("Name:\tgdbus") > -1 || bufstr.indexOf("Name:\tpool-frida") > -1 || bufstr.indexOf("Name:\tgum-js-loop") > -1 || bufstr.indexOf("SigBlk:\tffffffe0fffbfaff") > -1) {
Memory.writeUtf8String(buffer, "TName:\t1");
}
return retval;
}, 'pointer', ['pointer', 'int', 'pointer']))
}

function hook_fopen() {
var open_addr = Module.findExportByName("libc.so", "fopen")
var io_map = Memory.allocUtf8String("/proc/13585/maps");
Interceptor.attach(open_addr, {
onEnter: function (args) {
if (args[0].readCString().indexOf("/maps") != -1) {
args[0] = io_map
}
this.pathname = args[0]
this.mode = args[1]
},
onLeave: function (retval) {
console.log("fopen pathname=" + this.pathname.readCString() + "---mode=" + this.mode)
}
})
}


function hook_readlink() {
var aaa, bbb, ccc;
Interceptor.attach(Module.findExportByName(null, "readlink"), {
onEnter: function (args) {
aaa = args[0];
bbb = args[1];
ccc = args[2];
},
onLeave: function (retval) {
// console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
if (bbb.readCString().indexOf("frida") !== -1 ||
bbb.readCString().indexOf("gum-js-loop") !== -1 ||
bbb.readCString().indexOf("gmain") !== -1 ||
bbb.readCString().indexOf("tmp") !== -1 ||
bbb.readCString().indexOf("linjector") !== -1) {
console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
bbb.writeUtf8String("/system/framework/boot.art")
console.log("replce with: "+bbb.readCString())
retval.replace(0x1A)
}
}
});
}

function main(){
bypass_fgets()
hook_fopen()
hook_readlink()
}

setImmediate(main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// hook_anti-thread.js
function hook_pthread_create() {
var pthread_create_addr = Module.findExportByName(null, "pthread_create");
const __android_log_print_ptr = Module.findExportByName(null, '__android_log_print')
const cm = new CModule(`
#include <gum/guminterceptor.h>
extern void onMessageStr (const gchar * message);
extern void onMessagePtr (void * message);
extern void onMessageInt (int a);
extern int __android_log_print(int prio, const char* tag, const char* fmt, ...);
void hello(){
}
void onEnter (GumInvocationContext * ic)
{
// void* arg2,arg3;
if((((int)gum_invocation_context_get_nth_argument(ic, 2))&0xfff)==0x599){
onMessageStr("replace success");
gum_invocation_context_replace_nth_argument(ic,2,(gpointer)hello);
}
// arg2 = gum_invocation_context_get_nth_argument (ic, 2);
onMessagePtr(gum_invocation_context_get_nth_argument (ic, 2));
// arg0 = (int)gum_invocation_context_get_nth_argument (ic, 2);
// gum_invocation_context_replace_nth_argument(ic,2,(gpointer)100);
// arg1 = (int)gum_invocation_context_get_nth_argument (ic, 3);
// log ("function add arg0=%d arg1=%d ", arg0,arg1);
// __android_log_print(3,"isDebug","arg0=%d,arg1=%d",arg0,arg1);
}
void
onLeave (GumInvocationContext * ic)
{
int result;
result = (int) gum_invocation_context_get_return_value (ic);
onMessageInt(result);
// gum_invocation_context_replace_return_value (ic,(gpointer)100);
}`, {
__android_log_print: __android_log_print_ptr,
onMessageStr: new NativeCallback(messagePtr => {
const message = messagePtr.readUtf8String();
console.log('onMessageStr:', message);
}, 'void', ['pointer']),
onMessagePtr: new NativeCallback(messagePtr => {
console.log('onMessagePtr:', messagePtr ,hexdump(messagePtr));
}, 'void', ['pointer']),
onMessageInt: new NativeCallback(messageInt => {
console.log('onMessageInt:', messageInt);
}, 'void', ['int']),
});
Interceptor.attach(pthread_create_addr, cm);
}

function replace_thread() {
var pthread_create_addr = Module.findExportByName(null, "pthread_create");
var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);
Interceptor.replace(pthread_create_addr, new NativeCallback((parg0, parg1, parg2, parg3) => {
var so_name = Process.findModuleByAddress(parg2).name;
var so_base = Module.getBaseAddress(so_name);
var offset = (parg2 - so_base);
var PC = 0;
console.log("normal find thread func offset", so_name, parg2,offset, offset.toString(16));
// i加密 guilvyouping 梆梆 budayuepao 360 leisu
if(
(so_name.indexOf("libexec.so")>-1 && offset===197069)||
(so_name.indexOf("libexec.so")>-1 && offset===196137)
){

}else if((so_name.indexOf("libDexHelper.so")>-1 && offset===684452)||
(so_name.indexOf("libDexHelper.so")>-1 && offset===724380)){

}
else{
PC = pthread_create(parg0, parg1, parg2, parg3);
}
return PC;
}, "int", ["pointer", "pointer", "pointer", "pointer"]));
}

setImmediate(replace_thread)

// frida -U -f com.darvin.security -l hook_anti-thread.js --no-pause -o out.log
// frida -UF -l hook_anti-thread.js


//frida -U -f com.gzlex.hui.guoziwei.travel --no-pause -l hook_anti-thread.js
//frida -U -f tv.danmaku.bili -l hook_anti-thread.js --no-pause

1、hook app调用的各关键内置函数的调用 so和偏移值

2、针对每次调用进行功能hook,多个进行hook,筛选关键功能

打快照为什么要关机

自编译Frida

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
安装全新ubuntu
sudo apt-get install proxychains
sudo gedit /etc/proxychains.conf
socks5 ip port
sudo apt-get install curl
proxychains curl www.httpbin.org/ip // 验证代理IP
sudo apt install git
sudo dpkg -i vscode.deb
code .
proxychains sudo apt-get install build-essential curl git lib32stdc++-9-dev libc6-dev-i386 nodejs npm python3-dev python3-pip # 此处默认是安装最新的nodejs,frida和nodejs有一定的对应关系
proxychains git clone -b 16.2.1 --recurse-submodules https://github.com/frida/frida
cd frida
sudo apt install m4
# 此处似乎就是报错
proxychains make -f Makefile.toolchain.mk
# 手动下载需要的sdk版本,查看frida对应的版本
cd releng
cat deps.mk | head -n 10 #查看deps.mk的前10行,版本是 20240123
# 下载sdk,frida目录下载到/build
sudo wget https://build.frida.re/deps/20240123/toolchain-linux-x86_64.tar.bz2
sudo wget https://build.frida.re/deps/20240123/sdk-linux-x86_64.tar.bz2
sudo wget https://build.frida.re/deps/20240123/sdk-android-arm64.tar.bz2
# /frida目录下执行
./releng/setup-env.sh
# 手动下载需要的ndk版本,查看frida对应的版本,可随意下载目录,环境变量指定路径
cd releng
cat setup-env.sh | grep ndk
# 下载ndk官网地址:https://developer.android.com/ndk/downloads,具体名字看官网
proxychains wget https://dl.google.com/android/repository/android-ndk-r22b-linux-x86_64.zip
unzip xxx
# 配置ndk路径
sudo gedit ~/.bashrc
export ANDROID_NDK_ROOT=/XXX/XXX/android-ndk-r24
export PATH=$ANDROID_NDK_ROOT:$PATH
ndk-build -v
# 下载对应版本的Node和npm,在github release有code的版本
https://blog.csdn.net/John_Lenon/article/details/136155942?spm=1001.2014.3001.5506
#编译
make core-android-arm64

# 再把手动下载的sdk放进去覆盖
make clean
rm -rf build/
frida/build/tmp-android-arm64/frida-core/server


注意注意:执行make clean时候会把自建的python文件删除

魔改Frida

1
2
3
4
https://github.com/hluwa/Patchs/tree/master/strongR-frida/frida-core

根据gmain修改gdbus
替换所有/data/local/tmp

ubuntu使用 v2

1
2
3
4
5
6
7
8
9
sudo apt install libfuse2
wget https://github.com/v2ray/v2ray-core/releases/download/v4.27.0/v2ray-linux-64.zip
wget https://github.com/Qv2ray/Qv2ray/releases/download/v2.6.3/Qv2ray.v2.6.3.linux-x64.AppImage
unzip v2ray-linux-64.zip
chmod +x Qv2ray.v2.6.3.linux-x64.AppImage
./Qv2ray.v2.6.3.linux-x64.AppImage
# 修改内核设置/可执行文件路径/资源目录

hiddify

unidbg 模拟执行 SO

用起来啊,不然学个der

  • 框架本身开启打印日志,就能打印出RegisterNatives的方法,找到动态注册的方法,通过IDA跳转过去,hook_RegisterNative.js
  • jadx中tab键切换smail代码
  • java项目,直接导入idea
  • frida代码.$className 可查看类对象
1
2
3
4
5
6
7
8
1、AndroidEmulatorBuilder 指定包名初始化、指定SDK
sdk19 -> Android 4(32位)
sdk23 -> Android 6(64位)
2、创建虚拟机,指定APK
3、设定日志打印
4、指定loadLibrary,不然要加载的so文件名字为libhookdemo.so,只需要写hookdemo
5、根据包路径实例化类、初始化类
6、方法调用,指定方法声明&参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
androidEmulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.example.xx")
//创建根目录,这里是根目录是用项目目录模拟手机的/data目录
.setRootDir(new File("target/rootfs"))
.build();

memory = androidEmulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
dalvikVM= androidEmulator.createDalvikVM(new File("unidbg-android/src/test/java/fuck/xx.apk"));
dalvikVM.setJni(this);
dalvikVM.setVerbose(true);
// 去掉前后留存的中间的名字 libtestdemo.so
dalvikModule = dalvikVM.loadLibrary("test", true)
module = dalvikModule.getModule();
// 转化为16进制才是IDA看的地址,默认前面会加上unidbg的字符
System.out.println(module.findSymbolByName("Java_com_example_hookdemo_MainActivity_tesucanshu_1jni").getAddress());
dalvikModule.callJNI_OnLoad(androidEmulator);

JNI参数类型

https://www.jianshu.com/p/54a0b7ccc35e

主动调用&特殊参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void calltesucanshu_dizhi(){
DvmClass dvmClass = dalvikVM.resolveClass("com.example.xx.MainActivity");
DvmObject<?> dvmObject = dvmClass.newObject(null);

ArrayList<String> list3 = new ArrayList<>();
list3.add("test1");
list3.add("test2");

ArrayListObject arrayListObject = ArrayListObject.newStringList(dalvikVM,"test1","test2");

HashMap<Integer, String> Sites3 = new HashMap<>();
Sites3.put(1, "Google");
Sites3.put(2, "Runoob");
DvmObject<?> object_Sites3 = ProxyDvmObject.createObject(dalvikVM, Sites3);

List<Object> list = new ArrayList<>();
list.add(dalvikVM.getJNIEnv());
list.add(0);
list.add(1);
// 基本数据类型直接添加,复杂/对象类需要添加到虚拟机
list.add(dalvikVM.addGlobalObject(new StringObject(dalvikVM,"11111")));
list.add(dalvikVM.addGlobalObject(ProxyDvmObject.createObject(dalvikVM,new int[]{3, 1, 2, 6, 4, 2})));
list.add(dalvikVM.addGlobalObject(dalvikVM.resolveClass("com.example.xx.Person").newObject(null)));
list.add(dalvikVM.addGlobalObject(arrayListObject));
list.add(dalvikVM.addGlobalObject(object_Sites3));
list.add(dalvikVM.addGlobalObject(ProxyDvmObject.createObject(dalvikVM,new String[]{"nihao", "shijie"})));

// 参考 module/emulateFunction,通过地址调用SO函数
int number =module.callFunction(androidEmulator,0x63BBC,list.toArray()).intValue();
System.out.println("111->"+number);
System.out.println(dalvikVM.getObject(number));
}

JNI补环境

日志打印模式,src/test/resources/log4j.properties 修改test文件下debug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
public class testJNI extends AbstractJni {
private final AndroidEmulator androidEmulator;
private final Memory memory;
private final VM dalvikVM;
private final DalvikModule dalvikModule;
private final Module module;
public testJNI() {
androidEmulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.example.hookdemo")
//创建根目录
.setRootDir(new File("target/rootfs"))
.build();
memory = androidEmulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
dalvikVM = androidEmulator.createDalvikVM(new File("unidbg-android/src/test/java/lesson/testJNI/app-debug.apk"));
dalvikVM.setJni(this);
dalvikVM.setVerbose(true);
dalvikModule = dalvikVM.loadLibrary("hookdemo", true);
module = dalvikModule.getModule();
dalvikModule.callJNI_OnLoad(androidEmulator);
}

public static void main(String[] args) {
testJNI testJNI = new testJNI();
testJNI.callgetHash();
}

@Override
public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature){
case "com/example/hookdemo/Person->allocObject":{
return dvmClass.newObject(signature);
}
}
return super.allocObject(vm, dvmClass, signature);
}

private void callgetHash() {
DvmClass dvmClass = dalvikVM.resolveClass("com.example.hookdemo.MainActivity");
// String p5 = getApplicationContext().getpackageCodePath();
String apk_path = "/data/app/com.example.hookdemo-qmoUcGSJuvbQaByYAxdvOg==/base.apk";
DvmObject dvmObject = dvmClass.newObject(null);
DvmObject dvmObject1 = dvmObject.callJniMethodObject(androidEmulator, "getHash(Ljava/lang/String;)Ljava/lang/String;", apk_path);
System.out.println(dvmObject1.getValue());
}

@Override
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "java/util/zip/ZipFile-><init>(Ljava/lang/String;)V":{
// return vm.resolveClass("java/util/zip/ZipFile").newObject(signature);
// 没有具体调用方法就new,括号内是参数类型
String path = (String) vaList.getObjectArg(0).getValue();
if(path.equals("/data/app/com.example.hookdemo-qmoUcGSJuvbQaByYAxdvOg==/base.apk")){
try {
ZipFile zipFile = new ZipFile("unidbg-android/src/test/java/lesson/testJNI/app-debug.apk");
return vm.resolveClass("java/util/zip/ZipFile").newObject(zipFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return super.newObjectV(vm, dvmClass, signature, vaList);
}

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "java/util/zip/ZipFile->entries()Ljava/util/Enumeration;":{
// dvmObject获取当前对象
// signature 是package
// vaList获取参数
// 此处无括号是返回类型
ZipFile zipFile = (ZipFile) dvmObject.getValue();
Enumeration<? extends ZipEntry> entries = zipFile.entries();
// java内置zip
DvmClass ZipEntryClass = vm.resolveClass("java/util/zip/ZipEntry");

List<DvmObject<?>> objs = new ArrayList<>();
while (entries.hasMoreElements()){
ZipEntry zipEntry = entries.nextElement();
objs.add(ZipEntryClass.newObject(zipEntry));
}
return new com.github.unidbg.linux.android.dvm.Enumeration(dalvikVM,objs);
}
case "java/util/zip/ZipEntry->getName()Ljava/lang/String;":{
ZipEntry zipEntry = (ZipEntry) dvmObject.getValue();
String name = zipEntry.getName();
return new StringObject(dalvikVM,name);
}
case "java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;": {
// 括号内是参数类型,外边是返回类型
ZipFile zipFile = (ZipFile) dvmObject.getValue();
ZipEntry zipEntry = (ZipEntry) vaList.getObjectArg(0).getValue();

try {
InputStream inputStream = zipFile.getInputStream(zipEntry);
return vm.resolveClass("java/io/InputStream").newObject(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
case "java/security/MessageDigest->digest()[B":{
// 返回类型是byte
MessageDigest messageDigest = (MessageDigest) dvmObject.getValue();
byte[] digest = messageDigest.digest();
return new ByteArray(dalvikVM,digest);
}
// 仿写
//case "android/app/ActivityThread->getApplication()Landroid/app/Application;":
//return vm.resolveClass("android/app/Application",
//vm.resolveClass("android/content/ContextWrapper",
//vm.resolveClass("android/content/Context"))).newObject(signature);
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "java/io/InputStream->read([B)I":{
// 参数byte,默认返回
InputStream inputStream = (InputStream) dvmObject.getValue();
try {
int read = inputStream.read((byte[]) vaList.getObjectArg(0).getValue());
return read;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return super.callIntMethodV(vm, dvmObject, signature, vaList);
}

@Override
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "java/security/MessageDigest->update([B)V":{
MessageDigest messageDigest = (MessageDigest) dvmObject.getValue();
messageDigest.update((byte[]) vaList.getObjectArg(0).getValue());
return ;
}
case "java/io/InputStream->close()V" :{
InputStream inputStream = (InputStream) dvmObject.getValue();
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return ;
}
}
super.callVoidMethodV(vm, dvmObject, signature, vaList);
}
}

IDA 编译名转换

http://demangler.com/

Dobby&Unicorn

需要在onload之前hook

1
2
3
4
5
module = dalvikModule.getModule();
hookDobby();
hookUnicorn();
//相当于在SO加载前hook已经生效,但未实际调用,待后续调用时才触发hook
dalvikModule.callJNI_OnLoad(androidEmulator);
1
2
3
4
5
6
7
8
9
10
11
12
private void hookUnicorn() {
androidEmulator.attach().addBreakPoint(module.findSymbolByName("AES_ECB_PKCS7_Encrypt").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
System.out.println(emulator.getContext().getPointerArg(0).getString(0));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_PC,emulator.getContext().getLRPointer().peer);
//修改入参
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,0);
return true;
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public void hookDobby(){
Dobby dobby = Dobby.getInstance(androidEmulator);
dobby.replace(module.findSymbolByName("AES_ECB_PKCS7_Encrypt").getAddress(), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
//System.out.println("jinru");
//获取参数
//类似于hexdump
//Inspector.inspect(context.getPointerArg(1).getByteArray(0,100),"教学");

//修改参数
String fakeargs1 = "Hello world";
//得到长度
int length = fakeargs1.length();
//开辟一段内存空间
MemoryBlock fakeargs1malloc = emulator.getMemory().malloc(length, true);
//往内存空间写入数据
fakeargs1malloc.getPointer().write(fakeargs1.getBytes(StandardCharsets.UTF_8));
//映射到寄存器
emulator.getBackend().reg_write(Arm64_const.ARM64_REG_X0,fakeargs1malloc.getPointer().peer);
//String arg1 = context.getPointerArg(0).getString(0);
//System.out.println("第一个参数->"+arg1);

//context.push(context.getPointerArg(1));
//emulator.getBackend().reg_write(Arm64_const.ARM64_REG_X0,0);

//return super.onCall(emulator, context, context.getLR());
return HookStatus.RET(emulator,context.getLR());
}

@Override
public void postCall(Emulator<?> emulator, HookContext context) {

//得到返回值
//System.out.println(context.getPointerArg(0).getString(0));

//修改返回值
//String fakeargs1 = "Hello world";
//得到长度
//int length = fakeargs1.length();
//开辟一段内存空间
//MemoryBlock fakeargs1malloc = emulator.getMemory().malloc(length, true);
//往内存空间写入数据
//fakeargs1malloc.getPointer().write(fakeargs1.getBytes(StandardCharsets.UTF_8));
//映射到寄存器
//emulator.getBackend().reg_write(Arm64_const.ARM64_REG_X0,fakeargs1malloc.getPointer().peer);
//Pointer pop = context.pop();
//System.out.println("离开函数查看第二个参数->"+pop.getString(0));
super.postCall(emulator, context);
}
},true);
}

path_so

https://armconverter.com/

这个path的功能能否用hook实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void patch1() {
UnidbgPointer pointer = UnidbgPointer.pointer(androidEmulator, module.base + 0xE444);
byte[] code = new byte[]{0x40, 0x01, 0x00, 0x34};
pointer.write(code);

}

private void patch2() {
UnidbgPointer pointer = UnidbgPointer.pointer(androidEmulator, module.base + 0xE444);
Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
String huibian = "cbz w0, #0x28"; // 40010034
byte[] machineCode = keystone.assemble(huibian).getMachineCode();
pointer.write(machineCode);
}

文件访问 IO 流补环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class testIO extends AbstractJni implements IOResolver {
private final AndroidEmulator androidEmulator;
private final Memory memory;
private final VM dalvikVM;
private final DalvikModule dalvikModule;
private final Module module;
public testIO() {
androidEmulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.example.hookdemo")
//创建根目录
.setRootDir(new File("target/rootfs"))
.build();
memory = androidEmulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
dalvikVM = androidEmulator.createDalvikVM(new File("unidbg-android/src/test/java/lesson/testIO/app-debug.apk"));
dalvikVM.setJni(this);
dalvikVM.setVerbose(true);
dalvikModule = dalvikVM.loadLibrary("hookdemo", true);
androidEmulator.getSyscallHandler().addIOResolver(this);
//androidEmulator.getSyscallHandler().addIOResolver(new buwenjian());
module = dalvikModule.getModule();
dalvikModule.callJNI_OnLoad(androidEmulator);
}
public static void main(String[] args) {
testIO testIO = new testIO();
testIO.jiance_xp_frida();
}
private void jiance_xp_frida() {
DvmClass dvmClass = dalvikVM.resolveClass("com.example.hookdemo.MainActivity");
DvmObject<?> dvmObject = dvmClass.newObject(null);
DvmObject<?> dvmObject1 = dvmObject.callJniMethodObject(androidEmulator, "jiance_xp_frida()Ljava/lang/String;");
System.out.println(dvmObject1.getValue());
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
switch (pathname){
case "/sys/class/thermal/":{
return FileResult.<AndroidFileIO>success(new DirectoryFileIO(oflags,pathname,new File("target/rootfs/sys/class/thermal")));
}
case "/data":{
return FileResult.failed(UnixEmulator.EACCES);
}
case "/proc/self/cmdline":{
//return FileResult.<AndroidFileIO>success(new SimpleFileIO(oflags,new File("unidbg-android/src/test/java/com/lesson/testIO/cmdline"),pathname ));
return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags,pathname,"com.example.hookdemo\0".getBytes()));
}
case "/proc/self/maps":{
//return FileResult.<AndroidFileIO>success(new MapsFileIO(emulator,oflags,pathname,emulator.getMemory().getLoadedModules()));
return FileResult.<AndroidFileIO>success(new SimpleFileIO(oflags,new File("unidbg-android/src/test/java/lesson/testIO/maps"),pathname ));
}
}
System.out.println("file open-> "+pathname);
return null;
}
}

系统补环境

https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md

判断是JNI调用还是系统调用

  • svcNumber=0x0的是系统调用,NR调用号,ARM64调用号
  • 补环境要补哪个方法,根据NR调用号决定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JNI
[17:19:05 115] WARN [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:399) - handleInterrupt
intno=2, NR=-128672,
svcNumber=0x18d, PC=unidbg@0xfffe0964, LR=RX@0x40012b88[libhookdemo.so]0x12b88, syscall=null
java.lang.UnsupportedOperationException: android/os/Build->MODEL:Ljava/lang/String;
at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)


调用到系统API:
[17:22:56 521] WARN [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:399) - handleInterrupt
intno=2,
NR=165,
svcNumber=0x0, PC=RX@0x401ca3d4[libc.so]0x6a3d4, LR=RX@0x40011f28[libhookdemo.so]0x11f28, syscall=null

补系统的环境:

1、硬编码返回

hook下某个系统API的返回值,按一定格式返回

NR syscall name references %rax arg0 (%rdi) arg1 (%rsi) arg2 (%rdx) arg3 (%r10)
165 getrusage man/ cs/ 0xa5 int who struct rusage *ru
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Interceptor.attach(Module.findExportByName("libc.so", "getrusage"), {
onEnter: function (args) {
this.rusage = args[1]
},
onLeave: function (retval) {
console.log(hexdump(this.rusage,{length: 144}))
}
});

// hook到的系统API返回的内存值
7ff4b68f58 01 00 00 00 00 00 00 00 70 f3 05 00 00 00 00 00 ........p.......
7ff4b68f68 00 00 00 00 00 00 00 00 30 57 05 00 00 00 00 00 ........0W......
7ff4b68f78 10 64 01 00 00 00 00 00 00 00 00 00 00 00 00 00 .d..............
7ff4b68f88 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
7ff4b68f98 ff a5 00 00 00 00 00 00 98 01 00 00 00 00 00 00 ................
7ff4b68fa8 00 00 00 00 00 00 00 00 f0 7e 00 00 00 00 00 00 .........~......
7ff4b68fb8 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
7ff4b68fc8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
7ff4b68fd8 b8 04 00 00 00 00 00 00 5c 01 00 00 00 00 00 00 ........\.......
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// HookDemoArm64SysCallHandler.java
public class HookDemoArm64SysCallHandler extends ARM64SyscallHandler {
public HookDemoArm64SysCallHandler(SvcMemory svcMemory) {
super(svcMemory);
}

@Override
protected boolean handleUnknownSyscall(Emulator<?> emulator, int NR) {
switch (NR){
case 165:
getrusage(emulator);
return true;
}
return super.handleUnknownSyscall(emulator, NR);
}

private void getrusage(Emulator<?> emulator) {
UnidbgPointer register = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1);
byte[] bytes = hexStringToByteArray("010000000000000020300500000000000000000000000000e022020000000000149a0100000000000000000000000000000000000000000000000000000000008663000000000000ac190000000000000000000000000000382f000000000000000000000000000000000000000000000000000000000000000000000000000075010000000000009800000000000000");
for (int i=0;i<bytes.length;i++){
register.setByte(i,bytes[i]);
}
}

// 将十六进制字符串转换为字节数组的函数
public static byte[] hexStringToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}

@Override
protected int clock_gettime(Emulator<?> emulator) {
RegisterContext context = emulator.getContext();
int clk_id = context.getIntArg(0) & 0x7;
Pointer tp = context.getPointerArg(1);
long offset = clk_id == 0 ? currentTimeMillis() * 1000000L : System.nanoTime() - System.nanoTime();
long tv_sec = offset / 1000000000L;
long tv_nsec = offset % 1000000000L;

switch (clk_id) {
case 3:
tp.setLong(0, tv_sec);
tp.setLong(8, tv_nsec);
return 0;
}
return super.clock_gettime(emulator);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
public class lesson11 extends AbstractJni implements IOResolver {
//定义一个全局可用的模拟器对象
public final AndroidEmulator androidEmulatorBuilder;
public final VM vm;
public final DalvikModule dalvikModule;
public final Memory memory;
public final Module module;

//定义一个构造函数进行初始化操作
public lesson11() {
AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(true) {
@Override
public AndroidEmulator build() {
return new AndroidARM64Emulator(processName, rootDir, backendFactories) {
@Override
protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) {
return new HookDemoArm64SysCallHandler(svcMemory);
}
};
}
};

//初始化模拟器
// androidEmulatorBuilder = AndroidEmulatorBuilder
// .for64Bit()
// .setProcessName("com.example.hookdemo")
// .addBackendFactory(new Unicorn2Factory(true))
// .setRootDir(new File("target/rootfs"))
// .build();

androidEmulatorBuilder = builder
.addBackendFactory(new Unicorn2Factory(true))
.setRootDir(new File("target/rootfs"))
.setProcessName("com.example.hookdemo")
.build();

vm = androidEmulatorBuilder.createDalvikVM(new File("unidbg-android/src/test/java/lesson/lesson11/app-debug.apk"));
vm.setJni(this);
vm.setVerbose(true);
androidEmulatorBuilder.getSyscallHandler().addIOResolver(this);
memory = androidEmulatorBuilder.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
// getprop ro.build.id
SystemPropertyHook systemPropertyHook = new SystemPropertyHook(androidEmulatorBuilder);
systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
@Override
public String getProperty(String key) {
System.out.println("getProperty systemkey:" + key);
switch (key) {
case "net.hostname": {
return "MIX2S-zhongeryayadeM";
}
case "ro.serialno": {
return "f8a995f5";
}
case "ro.boot.serialno": {
return "f8a995f5";
}
case "ro.product.brand": {
return "Xiaomi";
}
case "ro.product.manufacturer": {
return "Xiaomi";
}
case "ro.product.model": {
return "MIX 2S";
}
case "ro.product.cpu.abi": {
return "arm64-v8a";
}
case "ro.product.cpu.abilist": {
return "arm64-v8a,armeabi-v7a,armeabi";
}
case "ro.boot.vbmeta.digest": {
return null;
}
case "init.svc.droid4x": {
return null;
}
case "ro.build.id":{
return "PKQ1.190118.001";
}
}
return "";
}
});
memory.addHookListener(systemPropertyHook);
dalvikModule = vm.loadLibrary("hookdemo", true);
module = dalvikModule.getModule();
dalvikModule.callJNI_OnLoad(androidEmulatorBuilder);
}

public static void main(String[] args) {
lesson11 lesson11 = new lesson11();
lesson11.setEnv();
lesson11.hookgetEnv();
lesson11.xitongdiaoyong();
}

private void xitongdiaoyong() {
DvmClass dvmClass = vm.resolveClass("com.example.hookdemo.MainActivity");
DvmObject<?> dvmObject = dvmClass.newObject(null);
dvmObject.callJniMethod(androidEmulatorBuilder, "xitongdiaoyong()V");
}

@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("file open:" + pathname);
return null;
}

@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/os/Build->MODEL:Ljava/lang/String;": {
return new StringObject(vm, "MI 6");
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}

@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("file open:" + pathname);
switch (pathname){
case "stat /data":{
return FileResult.success(new ByteArrayFileIO(oflags,pathname,(" File: `/data'\n" +
" Size: 4096\t Blocks: 16\t IO Blocks: 512\tdirectory\n" +
"Device: 10301h/66305d\t Inode: 2\t Links: 46\n" +
"Access: (771/drwxrwx--x)\tUid: ( 1000/ system)\tGid: ( 1000/ system)\n" +
"Access: 2019-08-22 00:32:54.000000000\n" +
"Modify: 2023-05-29 08:37:38.813999995\n" +
"Change: 1970-08-12 09:44:20.749999999\n").getBytes()));
}
}
return null;
}

@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/os/Build->MODEL:Ljava/lang/String;": {
return new StringObject(vm, "MI 6");
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}

// 设置环境变量
public void setEnv(){
Symbol setenv = module.findSymbolByName("setenv", true);
setenv.call(androidEmulatorBuilder, "muyang", "6666");
};
// hook并修改环境变量
public void hookgetEnv() {
Dobby dobby = Dobby.getInstance(androidEmulatorBuilder);
dobby.replace(module.findSymbolByName("getenv"), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
emulator.set("envkey", context.getPointerArg(0).getString(0));
return super.onCall(emulator, context, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
String key = emulator.get("envkey");
switch (key) {
case "muyang":
//修改返回值
String fakeInput = "6666";
//得到长度
int length = fakeInput.length();
//开辟一段内存空间 长度是字符串的长度
MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);
//往开辟的内存空间中写入byte数据
fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));
//再通过得到寄存器的方式 去写入进去
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0, fakeInputBlock.getPointer().peer);
}
super.postCall(emulator, context);
}
}, true);
}

public void hookPopen() {
Dobby dobby = Dobby.getInstance(androidEmulatorBuilder);
UnidbgPointer stat_data_pointer = UnidbgPointer.pointer(androidEmulatorBuilder, module.findSymbolByName("fopen").call(androidEmulatorBuilder, "stat /data", "r"));

dobby.replace(module.findSymbolByName("popen"), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
RegisterContext registerContext = emulator.getContext();
String command = registerContext.getPointerArg(0).getString(0);
System.out.println("command:" + command);

emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_PC,registerContext.getLRPointer().peer);
switch (command){
case "stat /data":{
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,stat_data_pointer.peer);
return super.onCall(emulator, originFunction);
}
}

return super.onCall(emulator, originFunction);
}
});
}
}

打开调试模式可查看更详细报错

2、仿写框架

根据unidbg提供的补其他系统API的例子写

访写 src/main/java/com/github/unidbg/ios/ARM64SyscallHandler.java

1
2
3
4
5
6
7
8
9
private void getrusage(Emulator<?> emulator) {
RegisterContext context = emulator.getContext();
int who = context.getIntArg(0);
Pointer r_usage = context.getPointerArg(1);
RUsage64 usage64 = new RUsage64(r_usage);
usage64.unpack();
usage64.fillDefault();
usage64.pack();
}

https://bbs.kanxue.com/thread-272751.html

https://buaq.net/go-129256.html

https://github.com/gl953236368

函数初始化

trace工具

Java层trace工具:https://github.com/r0ysue/r0tracer

Jnitrace:https://github.com/chame1eon/jnitrace

所以unidbg就是用来不运行app的情况下,调用so方法,补环境属于其中的前置环境

dump apk上下文环境

https://blog.seeflower.dev/search/dump/2/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
https://www.jianshu.com/p/c546783ad284
https://developer.android.com/ndk/guides/other_build_systems?hl=zh-cn
https://www.cnblogs.com/burner/p/clang-fen-si-bu-bian-yimainc.html 编译博客也不错
https://blog.csdn.net/qq_31865983/article/details/89402100 这个更加完整一点
https://github.com/hugsy/gef
https://www.likecs.com/show-204245381.html#sc=400
Cortex-A7 常用汇编指令资料:
https://www.cnblogs.com/iron2222/p/15640269.html

gdb调试器快速上手教程
https://blog.csdn.net/qq_45953886/article/details/127678876

ARM官方汇编指令
https://blog.csdn.net/oqqHuTu12345678/article/details/125683244

ARM汇编基础
https://www.cnblogs.com/hilfloser/p/10516610.html

ARM关于标志位影响详解
https://blog.csdn.net/tabactivity/article/details/90407858

ARM的九种寻址方式
https://blog.csdn.net/qq_43743762/article/details/105056991

### 5.编写第一个汇编程序

> ​ https://www.w3cschool.cn/c/c-file-io.html

参考资料:https://softool.cn/read/arm_assembly_basic_aye/21010203.html

https://blog.csdn.net/qq_41028985/article/details/119407917

### 6.逆向C程序-数据类型

> 计算器:
> https://www.bangnishouji.com/tools/224457.html

##### 1.32位浮点数表示方法:

> 二进制数怎么转化为浮点数?阶码又是怎么算出来的?
> https://zhidao.baidu.com/question/750285508423217012.html

算法篇:ios和安卓通用

参考连接:
https://www.cnblogs.com/xiaxveliang/p/15004954.html
https://blog.csdn.net/sinat_27933301/article/details/79538169
https://www.cnblogs.com/DeeLMind/p/7581423.html
https://www.bilibili.com/video/BV1aP411M7ug:强推

https://www.bilibili.com/video/BV1V14y1T7LM
https://blog.csdn.net/ZCShouCSDN/article/details/84675235