SO逆向之动态调试入门

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

基本环境

as-create new project-Native C++,Language选择Java引入easyso1自动安装ndk frida启动hook native方法demo.js

frida -U -f com.roysue.easyso1 -l demo.js --no-pause

1
2
3
4
5
6
7
8
9
10
11
12
13
setImmediate(function(){
Java.perform(function(){
Java.use("com.roysue.easyso1.MainActivity").onCreate.implementation = function(x){
console.log("Entering onCreate!");
return this.onCreate(x);
}
Java.use("com.roysue.easyso1.MainActivity").stringFromJNI.implementation = function(){
var result = this.stringFromJNI();
console.log("return value of stringFromJNI is => ",result);
return result;
}
})
})

ghidra jeb 反编译工具
build.gradle中android.defaultConfig.ndk配置abiFilters的cpu为’arm64-v8a’,’x86_64’,’armeabi-v7a’ 多个版本时

1
2
3
4
5
objection -g com.android.settings explore  获取系统版本
frida
plugin load /root/Desktop/Wallbreaker
plugin wallbreaker classdump
plugin wallbreaker classdump android.os.Build

adb install -r -t --abi arm-v7a app-debug.apk 强制指定安装测试版本

https://github.com/android/ndk-samples.git 打开hello-jni加上defaultConfig中添加ndk的abiFilters无效,被productFlavors中ndk的abiFilters覆盖成v8a

md5

编译md5

1
2
3
4
git clone https://github.com/pod32g/MD5.git
gcc -o md5 md5.c
./md5 onejane
file md5 查看编译生成后的文件格式

搜索 ndk cross compile 安卓交叉编译,源码编译成机器码

1
2
3
4
/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -target aarch64-linux-android21 md5.c  生成a.out
file a.out 是ELF 64-bit,arm aarch64
adb push a.out /data/local/tmp && chmod 777
./a.out onejane

整合md5

ndk开发以32位为主

1
2
3
ndk {
abiFilters 'armeabi-v7a'
}

build.gradle中引入的cmake为CMakeLists.txt

1
2
3
4
5
6
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}

修改CMakeLists.txt配置so库

1
2
3
4
5
6
7
8
9
# 配置so库
add_library( # Sets the name of the library.
roysue

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
roysue.c )

修改MainActivity

1
2
3
static {
System.loadLibrary("roysue");
}

新建roysue.c

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
#include <string.h>
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
JNIEXPORT jstring JNICALL
Java_com_roysue_easyso1_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz )
{

#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#if defined(__ARM_NEON__)
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a/NEON (hard-float)"
#else
#define ABI "armeabi-v7a/NEON"
#endif
#else
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a (hard-float)"
#else
#define ABI "armeabi-v7a"
#endif
#endif
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#define ABI "x86"
#elif defined(__x86_64__)
#define ABI "x86_64"
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#else
#define ABI "unknown"
#endif

int i, sum=0;
i = 1; //语句①
while(i<=10 /*语句②*/ ){
sum+=i;
i++; //语句③
__android_log_print(ANDROID_LOG_INFO, "r0ysue", "now sum is %d", sum);
}
// return (*env)->NewStringUTF(env, "Hello from JNI" ABI ".");
char* msg = "r0ysue";
size_t len = strlen(msg);
uint8_t result[16];
//
// md5((uint8_t*)msg, len, result);
//
// char* tmp = (char*)malloc(16);
// //字符串置空
// memset(tmp,0x00,sizeof(char)*16);
//
// char* final = (char*)malloc(16);
// //字符串置空
// memset(final,0x00,sizeof(char)*16);
//
// // display result
// for (i = 0; i < 16; i++){
// sprintf(tmp,"%2.2x", result[i]);
// __android_log_print(ANDROID_LOG_INFO, "r0ysue", "now tmp is %s", tmp);
// sprintf(final, "%s%s", final, tmp);
// }
// __android_log_print(ANDROID_LOG_INFO, "r0ysue", "now final is %s", final);

// return (*env)->NewStringUTF(env, final);

char *r = (char *) malloc(16);
memset(r,0x00,sizeof(char)*16);

char *final = (char *) malloc(16);
memset(final,0x00,sizeof(char)*16);

md5((uint8_t*)msg,len,result);

for (i = 0; i < 16; i++){
sprintf(r, "%2.2x", result[i]);
__android_log_print(ANDROID_LOG_INFO, "r0ysue", "Hi,now i is %s\n",r);
sprintf(final, "%s%s", final, r);
}
jstring jresult = (*env)->NewStringUTF(env,final);
return jresult;
}

lldb

调试第三方app有一下两种方案

  1. app以debug模式启动,apk保重debuggable==true(重打包或xposed/frida去hook)
  2. aosp系统编译成userdebug模式(n5x/sailfish)

具体见Android调试利器之LLDB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cd ~/Android/Sdk/ndk/22.0.7026061
tree -NCfhl | grep -i lldb-server
cp toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/11.0.5/lib/linux/aarch64/lldb-server ~/Desktop/ls64
cp toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/11.0.5/lib/linux/arm/lldb-server ~/Desktop/ls
adb push ls* /data/local/tmp
./ls64 platform --listen "0.0.0.0:10086" --server
将/root/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x96_64/bin加入path,并source ~/.zshrc
lldb
platform select remote-android
platform connect connect://192.168.0.183:10086
adb shell
ps -e|grep easy 拿到进程后
attach -p 23171 机器码llvm反汇编成16进制
thread list 当前线程
dis 当前执行函数
register read x0

as调试模式Debug时也可以看到LLDB反汇编的结果

image-20210710184308921

1
2
apt install neofetch
neofetch

Capstone/Keystone跨平台反汇编器,硬编码直接修改so

frida调试native通过dwarf

1
2
3
git clone https://github.com/iGio90/Dwarf.git
pip install -r requirements.txt
python dwarf.py 启动frida和dwarf,debug启动easyso

frida

objection -g com.roysue.easyso1 explore

1
2
memory list modules  找到libroysue.so
memory list exports libroysue.so

image-20210710205512944

1
2
3
4
5
6
7
8
9
10
11
function dis(address, number) {
for (var i = 0; i < number; i++) {
var ins = Instruction.parse(address);
console.log("address:" + address + "--dis:" + ins.toString());
address = ins.next;
}
}
setImmediate(function(){
var stringFromJniaddr = Module.findExportByName("libroysue.so","Java_com_roysue_easyso1_MainActivity_stringFromJNI")
dis(stringFromJniaddr,10);
})

frida -UF -l Intruction.js 打印地址

image-20210710210702658

1
2
3
4
5
6
7
8
9
10
11
12
13
arm-linux-androidabi-objdump -i  libroysue.so
objdump -t libroysue.so 反汇编目标文件显示基本信息,如果显示 no symbols说明符号被抽掉了
objdump -tT libroysue.so 查看导出表
arm-linux-androidabi-objdump -tT libroysue.so 查看动态导出表
arm-linux-androidabi-objdump --all-headers libroysue.so 显示所有可用头信息包括符号表,重定位入口
arm-linux-androidabi-objdump -D libroysue.so 反汇编section
arm-linux-androidabi-objdump -D libroysue.so | grep -100 0c44
arm-linux-androidabi-objdump -g libroysue.so
strings libroysue.so 打印符号表
nm -D libroysue.so
readelf -s libroysue.so 查看符号表
readelf -A libroysue.so 查看支持的CPU和ABI架构信息
size libroysue.so

具体工具查看GNU Binutils

patch so

010Editor安装插件,Templates-Template Repository-ELF.bt,修改so中的值保存,

image-20211030232733752

1
2
3
4
adb push libroysue.so /sdcard/Download
ps -e|grep -i easyso
cat /proc/17518/maps | grep -i libroysue 找到so存储位置并进入后
cp /sdcard/Download/libroysue.so ./ 调整权限后再次运行app,启动后打印logcat | grep -i roysue确实由now改为ooo

反汇编器

ghidra

rm -rf ~/.cache/vmware

1
2
3
4
5
6
apt search openjdk|grep openjdk
apt --fix-broken install
apt install openjdk-14-jdk
update-alternatives --config java 查看本机java版本并选择主版本
cd ghidra_9.2.1_PUBLIC
./ghidraRun 将roysue.so加入分析

image-20211030235427918

jeb

JEB Decompiler PRO 3.19.1./jeb_linux.sh,首次输入密码pwd_ilbtcdnwiuypbzeo_,License data使用python2 jebKeygen.py激活

打开roysue

image-20211030234603046

ida

binary ninja

image-20211031000930951

radare2

Cutter

C算法

WjCryptLib

1
2
3
4
5
6
7
8
git clone https://github.com/WaterJuice/WjCryptLib
apt install cmake
cmake ./
make
cd projects/Md5String
./Md5String r0ysue 本身是8e2b0b38f557578f578ef21b33e4df0d,得到0x8e,0x2b,0x0b,0x38,...
cd projects/Sha256String
./Sha256String r0ysue

AntiFrida

通过Frida内存特征对maps中elf文件进行扫描匹配特征,支持frida-gadget和frida-server

easyso1

cyberchef加解密Android Inline Hook中的指令修复详解Android APP逆向分析基础Android NDK AES 加解密,**aesTool实现aes加解密**

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
add_library( # Sets the name of the library.
roysue

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
aes.h
aes.c
hex_utils.h
hex_utils.c
tools.h
tools.cpp
aes_utils.h
aes_utils.c
roysue.c)

首先,我们要创建一个 Android 工程,还有一个 MainActivity

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
public class MainActivity extends AppCompatActivity {

// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("roysue");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(method02( method01("r0ysue")));

// Runnable mRunnable = new Runnable() {
// @Override
// public void run() {
// while (true) {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e){
// e.printStackTrace();
// }
// Log.i("roysue",method01("roysue"));
// }
// }
// };
// mRunnable.run();
new Thread(){
@Override
public void run() {
//需要在子线程中处理的逻辑
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("roysueeasyso1",method01("r0syue"));
}
}
}.start();
}

/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();


/**
* AES加密, CBC, PKCS5Padding
*/
public static native String method01(String str);

/**
* AES解密, CBC, PKCS5Padding
*/
public static native String method02(String str);
}

对应的 c 文件 roysue.c

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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#include <string.h>
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "aes_utils.h"
#include "tools.h"
#include "junk.h"

#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))



/* This is a trivial JNI example where we use a native method
* to return a new VM String. See the corresponding Java source
* file located at:
*
* hello-jni/app/src/main/java/com/example/hellojni/HelloJni.java
*/
// Constants are the integer part of the sines of integers (in radians) * 2^32.
const uint32_t k[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee ,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 ,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be ,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 ,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa ,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 ,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed ,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a ,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c ,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 ,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 ,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 ,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 ,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 ,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 ,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 };

// r specifies the per-round shift amounts
const uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};

// leftrotate function definition
#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))

void to_bytes(uint32_t val, uint8_t *bytes)
{
bytes[0] = (uint8_t) val;
bytes[1] = (uint8_t) (val >> 8);
bytes[2] = (uint8_t) (val >> 16);
bytes[3] = (uint8_t) (val >> 24);
}

uint32_t to_int32(const uint8_t *bytes)
{
return (uint32_t) bytes[0]
| ((uint32_t) bytes[1] << 8)
| ((uint32_t) bytes[2] << 16)
| ((uint32_t) bytes[3] << 24);
}

void md5(const uint8_t *initial_msg, size_t initial_len, uint8_t *digest) {

// These vars will contain the hash
uint32_t h0, h1, h2, h3;

// Message (to prepare)
uint8_t *msg = NULL;

size_t new_len, offset;
uint32_t w[16];
uint32_t a, b, c, d, i, f, g, temp;

// Initialize variables - simple count in nibbles:
h0 = 0x67452301;
h1 = 0xefcdab89;
h2 = 0x98badcfe;
h3 = 0x10325476;

//Pre-processing:
//append "1" bit to message
//append "0" bits until message length in bits ≡ 448 (mod 512)
//append length mod (2^64) to message

for (new_len = initial_len + 1; new_len % (512/8) != 448/8; new_len++)
;

msg = (uint8_t*)malloc(new_len + 8);
memcpy(msg, initial_msg, initial_len);
msg[initial_len] = 0x80; // append the "1" bit; most significant bit is "first"
for (offset = initial_len + 1; offset < new_len; offset++)
msg[offset] = 0; // append "0" bits

// append the len in bits at the end of the buffer.
to_bytes(initial_len*8, msg + new_len);
// initial_len>>29 == initial_len*8>>32, but avoids overflow.
to_bytes(initial_len>>29, msg + new_len + 4);

// Process the message in successive 512-bit chunks:
//for each 512-bit chunk of message:
for(offset=0; offset<new_len; offset += (512/8)) {

// break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15
for (i = 0; i < 16; i++)
w[i] = to_int32(msg + offset + i*4);

// Initialize hash value for this chunk:
a = h0;
b = h1;
c = h2;
d = h3;

// Main loop:
for(i = 0; i<64; i++) {

if (i < 16) {
f = (b & c) | ((~b) & d);
g = i;
} else if (i < 32) {
f = (d & b) | ((~d) & c);
g = (5*i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3*i + 5) % 16;
} else {
f = c ^ (b | (~d));
g = (7*i) % 16;
}

temp = d;
d = c;
c = b;
b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]);
a = temp;

}

// Add this chunk's hash to result so far:
h0 += a;
h1 += b;
h2 += c;
h3 += d;

}

// cleanup
free(msg);

//var char digest[16] := h0 append h1 append h2 append h3 //(Output is in little-endian)
to_bytes(h0, digest);
to_bytes(h1, digest + 4);
to_bytes(h2, digest + 8);
to_bytes(h3, digest + 12);
}

JNIEXPORT jstring JNICALL
Java_com_roysue_easyso1_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz )
{
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#if defined(__ARM_NEON__)
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a/NEON (hard-float)"
#else
#define ABI "armeabi-v7a/NEON"
#endif
#else
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a (hard-float)"
#else
#define ABI "armeabi-v7a"
#endif
#endif
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#define ABI "x86"
#elif defined(__x86_64__)
#define ABI "x86_64"
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#else
#define ABI "unknown"
#endif

int i, sum=0;
i = 1; //语句①
while(i<=10 /*语句②*/ ){
sum+=i;
i++; //语句③
__android_log_print(ANDROID_LOG_INFO, "r0ysue", "now sum is %d", sum);
}

char* msg = "r0ysue";
size_t len = strlen(msg);
uint8_t result[16];
//
// md5((uint8_t*)msg, len, result);
//
// char* tmp = (char*)malloc(16);
// memset(tmp,0x00,sizeof(char)*16);
//
// char* final = (char*)malloc(16);
// memset(final,0x00,sizeof(char)*16);
//
// // display result
// for (i = 0; i < 16; i++){
// sprintf(tmp,"%2.2x", result[i]);
// __android_log_print(ANDROID_LOG_INFO, "r0ysue", "now tmp is %s", tmp);
// sprintf(final, "%s%s", final, tmp);
// }
// __android_log_print(ANDROID_LOG_INFO, "r0ysue", "now final is %s", final);
//// puts("");
// return (*env)->NewStringUTF(env, final);

char *r = (char *) malloc(16);
memset(r,0x00,sizeof(char)*16);

char *final = (char *) malloc(16);
memset(final,0x00,sizeof(char)*16);

md5((uint8_t*)msg,len,result);

for (i = 0; i < 16; i++){
sprintf(r, "%2.2x", result[i]);
__android_log_print(ANDROID_LOG_INFO, "r0ysue", "Hi,now i is %s\n",r);
sprintf(final, "%s%s", final, r);
}
jstring jresult = (*env)->NewStringUTF(env,final);
return jresult;
}

JNIEXPORT jstring JNICALL
Java_com_roysue_easyso1_MainActivity_method01(JNIEnv *env, jclass clazz, jstring str_) {
// TODO: implement method01()

const char *str = (*env)->GetStringUTFChars(env,str_, JNI_FALSE);
char *result = AES_128_CBC_PKCS5_Encrypt(str);

(*env)->ReleaseStringUTFChars(env,str_, str);

jstring jResult = getJString(env, result);
free(result);

return jResult;

}

JNIEXPORT jstring JNICALL
Java_com_roysue_easyso1_MainActivity_method02(JNIEnv *env, jclass clazz, jstring str_) {
// TODO: implement method02()


const char *str = (*env)->GetStringUTFChars(env,str_, JNI_FALSE);
char *result = AES_128_CBC_PKCS5_Decrypt(str);

(*env)->ReleaseStringUTFChars(env,str_, str);

jstring jResult = getJString(env, result);
free(result);

return jResult;

}

编译好后使用ghidra分析,New project后新建文件夹

1
2
7z x ghidra_9.2.1_PUBLIC_20201215.zip
./ghidraRun 将编译后的解包拿到libroysue.so拖入ghidra

image-20211106105742596

image-20211101090607364

右侧反编译后的代码已经被混淆

frida

根据方法名获取地址frida_hook_libart,** hook_ArtMethod_RegisterNative**

1
2
3
4
5
6
7
8
9
10
11
12
13
setImmediate(function () {
console.log("Invoking...")
var method01_addr = Module.findExportByName("libroysue.so", "Java_com_roysue_easyso1_MainActivity_method02");
console.log("method01 addr is =>", method01_addr)

# Java_com_roysue_easyso1_MainActivity_method02(JNIEnv *env, jclass clazz, jstring str_)
Java.perform(function () {
var jstring = Java.vm.getEnv().newStringUtf("4e8de2f3c674d8157b4862e50954d81c")
var method01 = new NativeFunction(method01_addr, "pointer", ["pointer", "pointer", "pointer"]);
var result = method01(Java.vm.getEnv(), jstring, jstring)
console.log("final result is => ", Java.vm.getEnv().getStringUtfChars(result, null).readCString());
})
})

frida -UF -l FridaInvokeNative.js invoke()

ExAndroidNativeEmu

1
2
3
4
git clone https://github.com/maiyao1988/ExAndroidNativeEmu.git  基于Unicorn的模拟器
proxychains pyenv install 3.7.0
pyenv local 3.7.0
proxychains pip install -r requirements.txt

easyso1.py

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
import logging
import posixpath
import sys
import os

from unicorn import *
from unicorn.arm_const import *

from androidemu.emulator import Emulator
from androidemu.java.java_class_def import JavaClassDef
from androidemu.java.java_method_def import java_method_def
import androidemu.utils.debug_utils
from androidemu.utils.chain_log import ChainLogger
from androidemu.java.classes.string import String

import capstone
import traceback

g_cfd = ChainLogger(sys.stdout, "./ins-jni.txt")
def hook_code(mu, address, size, user_data):
try:
emu = user_data
if (not emu.memory.check_addr(address, UC_PROT_EXEC)):
logger.error("addr 0x%08X out of range"%(address,))
sys.exit(-1)
androidemu.utils.debug_utils.dump_code(emu, address, size, g_cfd)
except Exception as e:
logger.exception("exception in hook_code")
sys.exit(-1)

def hook_mem_read(uc, access, address, size, value, user_data):
pc = uc.reg_read(UC_ARM_REG_PC)

if (address == 0xCBC80640):
logger.debug("read mutex")
data = uc.mem_read(address, size)
v = int.from_bytes(data, byteorder='little', signed=False)
logger.debug(">>> Memory READ at 0x%08X, data size = %u, data value = 0x%08X, pc: 0x%08X," % (address, size, v, pc))

def hook_mem_write(uc, access, address, size, value, user_data):
pc = uc.reg_read(UC_ARM_REG_PC)
if (address == 0xCBC80640):
logger.debug("write mutex")
logger.debug(">>> Memory WRITE at 0x%08X, data size = %u, data value = 0x%08X, pc: 0x%08X" % (address, size, value, pc))


class MainActivity(metaclass=JavaClassDef, jvm_name='local/myapp/testnativeapp/MainActivity'):

def __init__(self):
pass

@java_method_def(name='stringFromJNI', signature='()Ljava/lang/String;', native=True)
def string_from_jni(self, mu):
pass

def test(self):
pass


logger = logging.getLogger(__name__)

emulator = Emulator(
vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs")
)

# Register Java class.
emulator.java_classloader.add_class(MainActivity)
# emulator.mu.hook_add(UC_HOOK_CODE, hook_code, emulator)

# emulator.mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write)
# emulator.mu.hook_add(UC_HOOK_MEM_READ, hook_mem_read)

# Load all libraries.
lib_module = emulator.load_library("tests/bin/libroysue.so")

logger.info("Loaded modules:")

for module in emulator.modules:
logger.info("=> 0x%08x - %s" % (module.base, module.filename))

try:
logger.info("Response from JNI call: %s" % emulator.call_symbol(lib_module,"Java_com_roysue_easyso1_MainActivity_method01",emulator.java_vm.jni_env.address_ptr, 0x00, String('Hello')))
logger.info("Response from JNI call: %s" % emulator.call_symbol(lib_module,"Java_com_roysue_easyso1_MainActivity_method02",emulator.java_vm.jni_env.address_ptr, 0x00, String('a8e98de3a13365e39030feefb6c508f7')))

logger.info("Exited EMU.")
logger.info("Native methods registered to MainActivity:")

except UcError as e:
print("Exit at %x" % emulator.mu.reg_read(UC_ARM_REG_PC))
raise

gdb调试

  • 可调试内核
  • 支持全平台
  • qemu内置
  • 内核硬件断点,开启内存断点

建议使用aosp810自行编译的系统,将app编译成带调试符号的so

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
tree -NChfl | grep -i gdb
file aosp810r1/prebuilts/misc/android-arm64/gdbserver64 app端将misc文件夹中所有架构的gdb拷贝出来放到/data/local/tmp/gdbserver
adb shell
./data/local/tmp/gdbserver/android-arm64/gdbserver64 --help
./fs1280arm64
objection -g com.roysue.easyso1 explore
frida 查看Process Architecture架构为arm架构,即32位
ps -e|grep -i zgygote 查看孵化器进程id,如zgygote为3316,zgygote64位3317
ps -e|grep -i 3316 查看32位的包
chmod 777 android-arm/gdbserver
adb shell dumpsys activity top|grep com.roysue.easyso1 查看顶层app包名
adb shell am start -D -n com.roysue.easyso1/.MainActivity 调试模式运行
ps -e|grep -i roysue 获取进程id为19175
./gdbserver 0.0.0.0:23946 --attach 19175
file aosp810r1/prebuilts/gcc/linux-x86 在ip为192.168.0.129的kali中启动./linux-x86/bin/gdb
(gdb) target remote 192.168.0.183:23946 app端ip为192.168.0.183
(gdb) info shared 查看so的共享库
(gdb) set disassemble-next on 指令回显
(gdb) set step-mode on 打开单步调试
(gdb) c

./root/Android/Sdk/tools/jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700 继续执行

./root/Android/Sdk/tools/
ln -s /root/Desktop/android-studio/jre/ /root/Android/Sdk/tools/lib/monitor-x86_64/ 启动ddms,./root/Android/Sdk/tools/monitor

image-20211106121315455

配置CMakeLists.txt 允许调试模块

1
2
3
SET(CMAKE_BUILD_TYPE "Debug")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

在build.gradle的android模块中加入

1
2
3
4
5
packagingOptions{
doNotStrip "*/armeabi/*.so"
doNotStrip "*/armeabi-v7a/*.so"
doNotStrip "*/x86/*.so"
}

编译好后解包拿到libroysue.so

1
2
3
objdump -T libroysue.so  导出表
objdump -t libroysue.so | more 符号表
nm -s libroysue.so | grep method 查看符号表偏移 000033f5

image-20211107025324811

HyperPwn调试

https://github.com/bet4it/hyperpwn.git,[Kali安装Hyperpwn(基于gdb的调试利器)](https://bbs.pediy.com/thread-265856.htm),[Hyperpwn:基于gdb的调试利器,让你的调试过程更轻松](https://bbs.pediy.com/thread-257344.html)

apt –fix-broken install

dpkg -i hyper_3.1.0-canary.4_amd64.deb

1
2
3
4
5
6
su kali  使用非root用户权限运行
hyper version
cd /opt/Hyper
sudo chmod 4755 chrome-sandbox
hyper -v 报错cannot open display: :0.0,将kali切换用户switch user,kali/kali 登录后启动
hyper 启动

安装插件需要代理,虽然proxychains可以代理,但是只能单个命令使用

1
vim /etc/redsocks.conf

image-20211106231625249

1
redsocks  直接启动,ss -nltp|more  检查代理是否启动

sh iptables.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#不重定向目的地址为服务器的包
sudo iptables -t nat -A OUTPUT -d 192.168.179.1 -j RETURN #请用你的shadowsocks服务器的地址替换$SERVER_IP

#不重定向私有地址的流量
sudo iptables -t nat -A OUTPUT -d 10.0.0.0/8 -j RETURN
sudo iptables -t nat -A OUTPUT -d 172.16.0.0/16 -j RETURN
sudo iptables -t nat -A OUTPUT -d 192.168.0.0/16 -j RETURN

#不重定向保留地址的流量,这一步很重要
sudo iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN

#重定向所有不满足以上条件的流量到redsocks监听的12345端口
sudo iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 12345 #12345是你的redsocks运行的端口,请根据你的情况替换它

curl ip.sb 查看全局科学的代理配置完成

安装插件

1
2
hyper i hyperinator
hyper i hyperpwn

切换用户到kali

1
hyper  更新插件报错error An unexpected error occurred: "...getaddrinfo ENOENT raw.githubusercontent.com".

image-20211106235927760

1
2
3
vim /etc/hosts
151.101.76.133 raw.githubusercontent.com
重新Update,知道右上角出现Plugins Updated

切回用户root

1
2
3
4
5
6
adb push ~/Android/Sdk/ndk/22.0.7026061/prebuild /data/local/tmp
apt search gdb-multiarch
apt install gdb-multiarch
gdb
q
cat /proc/13709/status 查看TracerPid为0就是不可调试

切回kali插件安装

1
2
3
4
5
6
7
git clone https://github.com/pwndbg/pwndbg.git
cd pwndbg
./setup.sh
wget -O ~/.gdbinit-gef.py -q http://gef.blah.cat/py
echo source ~/.gdbinit-gef.py >> ~/.gdbinit
gdb-multiarch
hyper

image-20211107011428407

1
2
3
4
5
6
7
经常忘记:kali开sshd服务:
# vim /etc/ssh/sshd_config
PermitRootLogin yes
# /etc/init.d/ssh start

开机自启:
update-rc.d ssh enable

开始attach,通过查看app架构为32位

1
2
3
4
5
6
ps -e|grep roysue  父进程id为3109,进程id为13709
ps -e|grep zygote 进程也为3109
ps -e|grep 3109
chmod 777 /data/local/tmp/prebuilt/android-arm/gdbserver/gdbserver
./gdbserver 0.0.0.0:23946 --attach 13709
cat /proc/13709/maps | grep libroysue 查看带x权限的基地址

image-20211107024948516

切换到kali账户

1
2
3
4
5
6
7
8
9
10
11
hyper
target remote 192.168.0.183:23946
set arch arm
set arm fallback-mode thumb
c 运行到下个断点
ctrl+c 停止执行
info share 看所有加载so
x/20i 0x33f5+0xcfc2e000 查看
b *(0x33f5+0xcfc2e000) 下断点 so基地址+函数偏移
info b 查看断点
c 运行到下个断点F8 进入函数F7

image-20211107030547532

image-20211107030953974

1
2
3
objection -g com.roysue.easyso1 explore
memory list modules 找到libroysue.so的基地址
memory list exports libroysue.so 找出so的所有函数即地址

image-20211107034206883

1
2
3
4
b *0xd3c31377
b *0xd3c324dd
c
invoke()

arm汇编

Android CPU 架构详解

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
file ~/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/*
~/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++ -target aarch64-linux-android21 md5.c 编译c文件
./md5 r0ysue
clang -target aarch64-linux-android21 md5.c -o md5arrch64 编译成64位
adb push md5aarch64 /data/loca/tmp
./md5aarch64 r0ysue
clang -target armv7a-linux-android21 md5.c -o md5v7a 编译成32位
clang -target arm-linux-android21 md5.c -o md5arm
clang -target arm-linux-android21 -E md5.c -o md5arm.i 预处理,将依赖的库进行展开
clang -target arm-linux-android21 -S md5arm2.i -o md5arm.s 编译
clang -target arm-linux-android21 md5arm.o 汇编成二进制
clang -target arm-linux-android21 md5arm.o -o md5Arm2 链接
./md5Arm2 roysue
llvm-readelf --sections md5Arm2
llvm-nm md5Arm2
man nm

clang -target arm-linux-android21 md5.c -c -o md5.bc -emit-llvm 二进制文件不可读
llvm-dis md5.bc -o md5.ll 生成字节码
llvm-as md5.ll -o md52.bc
apt install llvm-ll-tools
llc md52.bc -o md52.s
clang -target arm-linux-android21 -c md52.s -o md52.o 打包成可执行程序但是没有链接符号
clang -target arm-linux-android21 md52.o -o md52
./md52 roysue

ARM 反汇编基础常用ARM指令

unicorn

hello.c

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main()
{
/* 我的第一个 C 程序 */
getchar();
printf("Hello, World! \n");

return 0;
}

编译

1
2
3
4
5
clang -target armv7a-linux-android21 -E hello.c -o hello.i  预编译
clang -target arm-linux-android21 -S hello.i -o hello.s 编译成汇编
clang -target arm-linux-android21 -mthumb -S hello.i -o helloTHUMB.s 开启thumb模式
clang -target arm-linux-android21 hello.s -o hello
./hello roysue

全局科学

1
2
3
sh iptables.sh
redsocks
curl ip.sb

调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
./Hyper-3.1.0-canary.4.AppImage --no-sandbox
vim ~/.zshrc
alias hyper2='/root/Desktop/Hyper-3.1.0-canary.4.AppImage --no-sandbox'
source ~/.zshrc
hyper2
npm install hyperinator
npm install hyperpwn
git clone https://github.com/pwndbg/pwndbg.git
cd pwndbg
./setup.sh
wget -O ~/.gdbinit-gef.py -q http://gef.blah.cat/py
echo source ~/.gdbinit-gef.py >> ~/.gdbinit
vim ~/.hyper.js
plugins: ["hyperinator","hyperpwn"]
hyper2 左上角Plugins-update
gdb-multiarch
hyper

adb shell
./gdbserver 0.0.0.0:23946 hello

image-20211107214942712

1
2
3
4
target remote 192.168.3.13:23946
b main 下断点
c 执行到下一个断点
disassemble main 反编译main函数,等同于objdump -t hello , 查看main函数的起始地址00001410

image-20211107221013255

1
2
3
adb shell
ps -e|grep -i hello 进程id为13120
cat /proc/13120/maps | grep hello

image-20211107220755286

hex(0xaae13000+0x1410)=0xaae14410 是main函数的起始地址,但是断点却下在了0xaae1441c,ARM三级流水线,pc值=当前指令内存地址+8。

1
2
3
4
5
6
b *0xaae14410   因为已经过了断点,重启gdbserver,在hyper中重打断点,但是每次启动地址可能不一样
info registers 查看指令对应的值
ni 汇编级别逐步调试,n是源码级别逐步调试
x/4xw 0xab24043c 查看内存中的值
hexdump 0xab23f370+0x8 打印地址值
si 进入指令

结合frida

1
2
3
4
5
6
7
clang -target arm-linux-android21 -mthumb hello.c -o hellogetchar
./hellogetchar
frida -U -f "/data/local/tmp/hello" --no-pause
Process 进程id为14526
Process.enumerateModules()
Process.findModuleByName("hellogetchar") 获取base地址
./gdbserver 0.0.0.0:23946 --attach 14526

hello.s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	.text
.syntax unified
.fpu neon
.file "hello.c"
.globl main @ -- Begin function main
.p2align 2
.type main,%function
.code 32 @ @main
main:
push {lr}
ldr r0,[r1,#8]
bl printf
mov r0,#0
pop {lr}
bx lr

编译

1
2
3
4
5
6
7
8
9
10
11
12
13
clang -target armv7a-linux-android21 hello.s -o helloLAST
adb push helloLAST /data/local/tmp
./helloLAST 1 123

./gdbserver 0.0.0.0:23946 ./helloLAST 1 abc
hyper2
target remote 192.168.3.13:23946
disassemble main
b *0xaae46400
c
info registers
ni
hexdump *(0xfffefa14+0x8)

objdump

1
2
3
clang -target armv7a-linux-android21 -mthumb helloTHUMB.s -o helloTHUMB
adb push helloTHUMB /data/local/tmp
objdump -d helloTHUMB 报错can't disassemble for architecture UNKNOWN! 不能在x86机器反编译arm的汇编,kali nethunter上pkg install binutils安装objdump,在手机端调用反编译,或者将手机中的该文件拷贝出来使用,反编译拿到指令

主动执行汇编

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
import unicorn 
import capstone
import binascii

# 142a: 4608 mov r0, r1
# 141c: 4479 add r1, pc

def testthumb():
# CODE = b'\x08\x46\x79\x44'
CODE = b'\x0a\x46\x03\x46'
CP = capstone.Cs(capstone.CS_ARCH_ARM,capstone.CS_MODE_THUMB)
for i in CP.disasm(CODE,0,len(CODE)):
print("[addr:%x]:%s %s\n"%(i.address,i.mnemonic,i.op_str))

mu=unicorn.Uc(unicorn.UC_ARCH_ARM,unicorn.UC_MODE_THUMB)
ADDRESS=0x1000
SIZE=1024
mu.mem_map(ADDRESS,SIZE)
mu.mem_write(ADDRESS,CODE)
bytes = mu.mem_read(ADDRESS,10)
print("ADDRESS:0x%x,contents:%s"%(ADDRESS,binascii.b2a_hex (bytes)))
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0,0x100)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1,0x200)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R2,0x300)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R3,0x400)
mu.emu_start(ADDRESS+1,ADDRESS+4)
print("result is 0x%x"%(mu.reg_read(unicorn.arm_const.UC_ARM_REG_R2)))
print("result is 0x%x"%(mu.reg_read(unicorn.arm_const.UC_ARM_REG_R3)))


if __name__=='__main__':
testthumb()

Unidbg

image-20211225102711312

基于Unicorn(直接执行arm汇编),配合libc.so,libart.so等so库对精简安卓系统的模拟实现。

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
function stringToByte(str) {
var len, c;
len = str.length;
var bytes = [];
for(var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if(c >= 0x010000 && c <= 0x10FFFF) {
bytes.push(((c >> 18) & 0x07) | 0xF0);
bytes.push(((c >> 12) & 0x3F) | 0x80);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(((c >> 12) & 0x0F) | 0xE0);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000080 && c <= 0x0007FF) {
bytes.push(((c >> 6) & 0x1F) | 0xC0);
bytes.push((c & 0x3F) | 0x80);
} else {
bytes.push(c & 0xFF);
}
}
return new Int8Array(bytes);
}

var handle;
function testdlopen(){
var dlopen_addr=Module.findExportByName("libc.so","dlopen");
var dlopen=new NativeFunction(dlopen_addr,"pointer",["pointer","int"]);
var soPath=stringToByte("/system/lib/libmobileServer.so");
const pathAddr = Memory.alloc(soPath.length);
Memory.writeByteArray(pathAddr,soPath);
handle=dlopen(pathAddr,1);
}

function testdlclose(){
var dlclose_addr=Module.findExportByName("libc.so","dlclose");
var dlclose=new NativeFunction(dlclose_addr,"int",["pointer"]);
var retVal=dlclose(handle);
console.log("retVal",retVal);
}

cat /proc/10783/maps | grep system/lib64

简单粗暴的so加解密实现

从零打造简单的SODUMP工具

Android逆向中So模块自动化修复工具+实战一发

Android加固中So文件自动化修复工具GUI

加固技术

IDA

看雪第三题

对frida做了检测,输入账户密码后上frida闪退

image-20211226140738745

IDA64bit打开libnative-lib.so,查看导出表,shift+F12查看so中的所有字符串,c++filt _ZN7_JNIEnv15RegisterNativesEP7_jclassPK15JNINativeMethodi,IDA会自动将这些字符串通过c++filt转为导出函数

image-20211226140842694

进入init函数,F5反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 init()
{
unsigned __int64 v0; // x8
unsigned __int64 v1; // ST00_8
__int64 result; // x0
char v3; // [xsp+20h] [xbp-10h]
__int64 v4; // [xsp+28h] [xbp-8h]

v0 = _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v4 = *(_QWORD *)(v0 + 40);
v1 = v0;
result = pthread_create(&v3, 0LL, detect_frida_loop, 0LL);
*(_QWORD *)(v1 + 40);
return result;
}

进入detect_frida_loop,可以尝试不要检测任意地址0.0.0.0,或者strcmp(&v2, "REJECT")不相等即可

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
void __fastcall __noreturn detect_frida_loop(void *a1)
{

......
inet_aton("0.0.0.0", &v8);
while ( 1 )
{
for ( i = 1; i <= 65533; ++i )
{
v5 = socket(2LL, 1LL, 0LL);
v7 = bswap32((unsigned __int16)i) >> 16;
if ( (unsigned int)connect(v5, &v6, 16LL) != -1 )
{
......
v3 = recvfrom(v5, &v2, 6LL, 64LL, 0LL, 0LL);
if ( v3 != -1 )
{
if ( (unsigned int)strcmp(&v2, "REJECT") )
{
__android_log_print(4LL, "pediy", "not FOUND FRIDA SERVER");
}
else
{
v1 = getpid();
kill(v1, 9LL);
}
}
}
close(v5);
}
}
}

双击0.0.0.0,进入只读段的汇编代码

image-20211226142233026

点中”0.0.0.0”,进入Hex View-1

image-20211226142334485

C编码是ASCII码表,JAVA是UNICODE,尝试hex-to-ascii

image-20211226142509893

Swap后将2.2.2.2转为十六进制

image-20211226142544870

F2修改So中的Hex,改完后再F2再应用

image-20211226142740220

保存

image-20211226142916201

回到Pseudocode-C重新F5反编译刷新

image-20211226143011624

一般使用apk d 3.apk,再通过将so放到解开的文件夹中替换,apktool b 3 -o 3_mod.apk,但是没有签名无法安装,签名回编译uber-apk-signer,java -jar uber-apk-signer-1.1.0.jar -a 3_mod.apk --allowResign完成自动签名,重新安装即可。

回到Pseudocode-C页面,点中REJECT

image-20211226144558547

双击进入IDA View-A,点中REJECT,进入Hex View-1

image-20211226144818713

image-20211226144905207

将REJECT改成“666666”

image-20211226145018201

同样F2修改原字符串的十六进制,F2应用并保存

image-20211226145108697

image-20211226145220890

重新解析

image-20211226145303623

完成过frida检测。

adb shell && ls /system/lib64 查看所有so

frida-ps -Ua 查看所有进程的包名

Frida尝试hook so层的函数

frida -U -f com.kanxue.pediy1 -l demo.js 启动后使用%resume重新加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function hookstrcmp(){
Java.perform(function() {

var addr_strcmp = Module.findExportByName("libc.so","strcmp");
Interceptor.attach(addr_strcmp, {
onEnter: function (args) {

if(ptr(args[1]).readCString().indexOf("REJECT")>=0){
console.log("[*] strcmp (" + ptr(args[0]).readCString() + "," + ptr(args[1]).readCString()+")");
this.isREJECT = true;
}
},onLeave:function(retval){
if(this.isREJECT){
retval.replace(0x1);
console.log("the REJECT's result :",retval);
}
}
});
})
}
setImmediate(hookstrcmp);

52第三题

https://www.52pojie.cn/thread-1369661-1-1.html

https://www.52pojie.cn/thread-1378761-1-1.html
https://www.52pojie.cn/thread-1371527-1-1.html
https://www.52pojie.cn/thread-1383999-1-1.html

1
adb install 2021wuai.apk

image-20211230224303554

直接ida打开libnative-lib.so,搜索check,或者查看shift+f12查看so中的字符串

image-20211230224619481

查看Java_cn_pojie52_cm01_MainActivity_check,并F5查看源代码,修改类型

image-20211230224815528

image-20211230224928594

Frida开始hook frida-ps -Ua 查看所有包名

frida -U -n com.pojie52.cm01 -l 2021wuai.js 调用inline_hook()

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
// sub_B90((int)dest, v7, "areyousure??????");
function inline_hook() {

Java.perform(function() {

var addr_libnative = Module.findBaseAddress("libnative-lib.so");

if (addr_libnative) {
console.log("so基址: ", addr_libnative);
// 尝试hook sub_B90((int)dest, v7, "areyousure??????") 这行语句打印出入参
var sub_B90 = addr_libnative.add(0xB90);
// so基址+物理地址(0xB90)= 0x73bdd49000+8AC=0x73bdd498AC
Interceptor.attach(sub_B90, {
onEnter: function(args) {
this.arg0 = args[0]; // 传入明文字符串
this.arg1 = args[1]; // 明文字符串长度
this.arg2 = args[2]; // 固定字符串


console.log("\n",hexdump(this.arg0));
console.log("\n",this.arg1);
console.log("\n",hexdump(this.arg2));
},
onLeave:function(retval){

console.log("-------sub_B90 retval -------");
console.log("\n",hexdump(this.arg0));
console.log("\n",this.arg1);
console.log("\n",hexdump(this.arg2));
}
});
}
});
}

setImmediate(inline_hook);

image-20220306124609486

调试frida又想调试ida,需要先跑frida再挂IDA,F2加上断点方便对比,8AC

image-20220306120535912

image-20220306122331999

1
2
3
adb push android_server64 /data/local/tmp
mv android_server64 as64
chmod 777 as64

新起IDA

image-20220306121314429

image-20220306121338607

image-20220306121358049

image-20220306121546087

点击放行,由于so基地址通过frida拿到为0x73bdd49000+8AC=0x73bdd498AC,可以用python十进制转十六进制,hex(0x73bdd49000+0x8AC)G直接跳转,C转成字节码

image-20220306123940417

image-20220306124059636

对比静态分析

image-20220306124121047

打上断点

image-20220306124216278

点击屏幕删干掉验证按钮,F8单步调试,查看X0,就是我们输入的字符串值

image-20220306124412124

image-20220306124419406

以上是两种获取寄存器中值的方案。

hook方法sub_B90对输入内容加密和sub_D90是一个base64编码

image-20220306135325103

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
  v29 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
if ( (*a1)->GetStringUTFLength(a1, a3) != 30 )
return 0;
v5 = (*a1)->GetStringUTFChars(a1, a3, 0LL);
v28 = 0u;
v27 = 0u;
v26 = 0u;
*dest = 0u;
v6 = strlen(v5);
strncpy(dest, v5, v6);
(*a1)->ReleaseStringUTFChars(a1, a3, v5);
v7 = strlen(dest);
sub_B90(dest, v7, "areyousure??????");
v8 = strlen(dest);
v9 = sub_D90(dest, v8);
*v19 = unk_11A1;
*&v19[16] = unk_11B1;
*&v19[25] = unk_11BA;
v10.n128_u64[0] = 0xB2B2B2B2B2B2B2B2LL;
v10.n128_u64[1] = 0xB2B2B2B2B2B2B2B2LL;
v11.n128_u64[0] = 0xFEFEFEFEFEFEFEFELL;
v11.n128_u64[1] = 0xFEFEFEFEFEFEFEFELL;
v19[0] = 53;
v12 = veorq_s8(vaddq_s8(veorq_s8(vaddq_s8(*&v19[1], v10), xmmword_1130), xmmword_1140), v11);
v13.n128_u64[0] = 0x101010101010101LL;
v13.n128_u64[1] = 0x101010101010101LL;
v14.n128_u64[0] = 0x3E3E3E3E3E3E3E3ELL;
v14.n128_u64[1] = 0x3E3E3E3E3E3E3E3ELL;
*&v19[1] = vaddq_s8(
veorq_s8(vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v12, 7uLL), vshlq_n_s8(v12, 1uLL))), xmmword_1150),
v14);
v20 = 1782990162;
v15 = veorq_s8(vaddq_s8(veorq_s8(vaddq_s8(*&v19[17], v10), xmmword_1160), xmmword_1170), v11);
v21 = ((1
- ((2 * ((((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE)) | ((((((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE) & 0x80) != 0))) ^ 0x25)
+ 62;
v16 = 0LL;
v22 = ((1
- ((2 * ((((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE)) | ((((((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE) & 0x80) != 0))) ^ 0x26)
+ 62;
*&v19[17] = vaddq_s8(
veorq_s8(vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v15, 7uLL), vshlq_n_s8(v15, 1uLL))), xmmword_1180),
v14);
v23 = ((1
- ((2 * ((((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE)) | ((((((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE) & 0x80) != 0))) ^ 0x27)
+ 62;
v24 = ((1
- ((2 * ((((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE)) | ((((((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE) & 0x80) != 0))) ^ 0x28)
+ 62;
while ( v9[v16] == v19[v16] )
{
if ( v9[v16] )
{
if ( ++v16 != 41 )
continue;
}
v17 = 1;
goto LABEL_9;
}
v17 = 0;
LABEL_9:
free(v9);
return v17;

sub_B90(dest, v7, "areyousure??????");获得加密拿到新dest后,v8 = strlen(dest);是dest长度, v9 = sub_D90(dest, v8);是base64加密,后续在while ( v9[v16] == v19[v16] )中判断是否校验成功,和上面if ( (*a1)->GetStringUTFLength(a1, a3) != 30 )一样判断一旦没有30位直接校验失败,只有返回1才校验成功,即v9[v16] == v19[v16]。获取此时v19的地址0xB30,base64(密文)=v19

image-20220306140145540

1
2
3
4
5
6
7
8
9
10
11
var libnative = Module.findBaseAddress("libnative-lib.so");
console.log("libnative: " + libnative);
//获取flag加密后的内容
var LDRB = libnative.add(0xB30);
send("LDRB: " + LDRB); // ldrb从存储器中将一个8位的字节数据传送到目的寄存器,将存储器地址为X9+X8的字节数据读入寄存器W11,并将W11的高24位清零
Interceptor.attach(LDRB, {
onEnter: function(args){
send("LDRB onEnter");
console.log(Memory.readCString(this.context.x9));
}
});

打印5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8,IDA调试如下

image-20220306152819590

sub_B90中我们传入的a1参数只有最后进行了异或操作并重新赋值得到密文。

image-20220306140721996

明文 ^ 密钥 = 密文

明文 ^ 密文 = 密钥

上述方案可得我们根据明文及密文可以拿到密钥,三十个字符总共需要异或30次

image-20220306141359584

image-20220306141442335

依次类推拿到

十进制密钥=[209, 90, 6, 144, 68, 230, 199, 229, 222, 40, 247, 242, 102, 145, 200, 133, 66, 223, 249, 224, 130, 1, 43, 59, 56, 99, 55, 189, 46, 77]

十六进制密钥=[0xd1,0x5a,0x6,0x90,0x44,0xe6,0xc7,0xe5,0xde,0x28,0xf7,0xf2,0x66,0x91,0xc8,0x85,0x42,0xdf,0xf9,0xe0,0x82,0x1,0x2b,0x3b,0x38,0x63,0x37,0xbd,0x2e,0x4d]

或者通过hook寄存器方式拿到EOR地址为D58,需要的寄存器是x12

image-20220306151420765

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var ishook = true;
var EOR = libnative.add(0xD58);
var eor = [];
var eorlen = 0;
send("EOR: " + EOR);
Interceptor.attach(EOR, {
onEnter: function(args){
if (ishook){
if (eorlen < 30){
eor.push(this.context.x12);
eorlen += 1;
}else{
ishook = false;
console.log(eor);
}
}
}
})

python实现,返回52pojieHappyChineseNewYear2021就是明文,在sub_B90与密钥[0xd1,0x5a,0x6,0x90,0x44,0xe6,0xc7,0xe5,0xde,0x28,0xf7,0xf2,0x66,0x91,0xc8,0x85,0x42,0xdf,0xf9,0xe0,0x82,0x1,0x2b,0x3b,0x38,0x63,0x37,0xbd,0x2e,0x4d]异或返回dest,在sub_D90中加密得到密文5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8

1
2
3
4
5
import base64
xordata = [0xd1,0x5a,0x6,0x90,0x44,0xe6,0xc7,0xe5,0xde,0x28,0xf7,0xf2,0x66,0x91,0xc8,0x85,0x42,0xdf,0xf9,0xe0,0x82,0x1,0x2b,0x3b,0x38,0x63,0x37,0xbd,0x2e,0x4d]
data = base64.b64decode('5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8'.encode())
flag = bytes([xordata[i] ^ data[i] for i in range(len(xordata))]).decode()
print(flag)

keypatch,**findcrypt-yara**放入IDA/plugin目录

1
2
3
4
pip install Keystone
pip install keystone-engine
pip install six
pip install yara-python

image-20220306155949368

image-20220306160114888

F5刷新伪代码永远返回1

image-20220306160149283

将改好的libnative-lib.so替换

1
2
aoktool d 2021wuai.apk  解包并替换so
apk b 2021wuai -o 2021wuai_mod.apk 重新打包

https://github.com/patrickfav/uber-apk-signer

1
java -jar uber-apk-signer-1.2.1.jar -a 2021wuai_mod.apk --allowResign  重签名

JNI

ndk是安卓开发工具包,用于快速开发C/C++动态库,jni作为java原生接口交互c++规范,分为动静态注册

app\build.gradle

1
2
3
4
5
6
7
8
9
10
11
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"

defaultConfig {
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}

}
}

加固流程

  • init_array
  • JNI_onLoad
  • 反调试过得比较粗糙
  • 自定义linker加载第二个so
  • 手工修复elf_header
  • 动态注册函数Interface11
  • native化java函数分发器分析
  • 根据注册vmp方法时的描述信息执行分支
  • 分析指令映射表
1
2
3
objection -g com.roysue.easyso1 explore
memory list modules
memory list exports libroysue.so 查看所有动静态方法,c++filt 动态注册的方法,拿到该方法的签名

动态加载

IDA从导出表中JNIEnv::RegisterNatives引用,找到动态加载调用的地方,双击到IDA View-A中找到动态注册的函数。

1
2
frida -U -f com.roysue.easyso1 -l hook_RegisterNatives.js --no-pause
jnitrace -l libroysue.so com.roysue.easyso1

roysue.cpp源码

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
JNIEXPORT jstring JNICALL Java_com_roysue_easyso1_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz ) {
return (*env)->NewStringUTF(env, "Hello from JNI !");
}

JNIEXPORT jstring JNICALL method02(JNIEnv *env, jclass jcls, jstring str_) {
if (str_ == nullptr) return nullptr;

const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
char *result = AES_128_CBC_PKCS5_Decrypt(str);

env->ReleaseStringUTFChars(str_, str);

jstring jResult = getJString(env, result);
free(result);

return jResult;
}


static JNINativeMethod method_table[] = {
{"decrypt", "(Ljava/lang/String;)Ljava/lang/String;", (void *) method02},
// (参数1类型标示;参数2类型标示;参数3类型标示...)返回值类型标示
};

static int registerMethods(JNIEnv *env, const char *className,
JNINativeMethod *gMethods, int numMethods) {
jclass clazz = env->FindClass(className);
if (clazz == nullptr) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL Java_com_roysue_easyso1_MainActivity_method01(JNIEnv *env, jclass jcls, jstring str_) {
if (str_ == nullptr) return nullptr;

const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
char *result = AES_128_CBC_PKCS5_Encrypt(str);

env->ReleaseStringUTFChars(str_, str);

jstring jResult = getJString(env, result);
free(result);

return jResult;
}


JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
_JUNK_FUN_0

JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
assert(env != nullptr);

// 注册native方法
if (!registerMethods(env, "com/roysue/easyso1/MainActivity", method_table,
NELEM(method_table))) {
return JNI_ERR;
}

return JNI_VERSION_1_6;
}

MainActivity源码

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
public class MainActivity extends AppCompatActivity {

static {
System.loadLibrary("roysue");
}


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
// tv.setText(method02( method01("r0ysue")));
tv.setText(decrypt("82e8edd5b05654bf0fedcdfc1c9b4b0f"));
new Thread(){
@Override
public void run() {
//需要在子线程中处理的逻辑
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("roysueeasyso1",method01("r0syue"));
}
}
}.start();
}

public native String stringFromJNI();


/**
* AES加密, CBC, PKCS5Padding
*/
public static native String method01(String str);

/**
* AES解密, CBC, PKCS5Padding
*/
public static native String decrypt(String str);
}

动静态hook

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
var is_hook_libart = false;

function hook_dlopen() {
Interceptor.attach(Module.findExportByName(null, "dlopen"), {
onEnter: function(args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("dlopen:", path);
if (path.indexOf("libroysue.so") >= 0) {
this.can_hook_libart = true;
console.log("[dlopen:]", path);
}
}
},
onLeave: function(retval) {
if (this.can_hook_libart && !is_hook_libart) {
is_hook_libart = true;

}
}
})

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function(args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("android_dlopen_ext:", path);
if (path.indexOf("libroysue.so") >= 0) {
this.can_hook_libart = true;
console.log("[android_dlopen_ext:]", path);
}
}
},
onLeave: function(retval) {
if (this.can_hook_libart && !is_hook_libart) {
is_hook_libart = true;
var method01 = Module.findExportByName("libroysue.so", "Java_com_roysue_easyso1_MainActivity_method01")
var method02 = Module.findExportByName("libroysue.so", "_Z8method02P7_JNIEnvP7_jclassP8_jstring")
console.log("method01 address is =>",method01)
console.log("method02 address is =>",method02)
Interceptor.attach(method01,{
onEnter:function(args){
console.log("arg[2=>",args[2])

},onLeave:function(retval){
console.log("retval=>",retval)

}
})
Interceptor.attach(method02,{
onEnter:function(args){
console.log("method02 arg[2=>",Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())

},onLeave:function(retval){
console.log("method02 retval=>",Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
}
})
}
}
});
}

主动调用并hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hookJava(){
Java.perform(function(){
console.log("hooking java...")
Java.use("com.roysue.easyso1.MainActivity").decrypt.implementation = function(str){
var result = this.decrypt(str)
console.log("str,result => ,",str,result)
return result;
}

})
}
function invokeJava(){
Java.perform(function(){
var result = Java.use("com.roysue.easyso1.MainActivity").decrypt("82e8edd5b05654bf0fedcdfc1c9b4b0f")
console.log("result => ,",result)

})
}

Native层主动调用

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
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];

//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("NewStringUTF") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrNewStringUTF = symbol.address;
console.log("NewStringUTF is at ", symbol.address, symbol.name);
NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
}
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
ENV = args[0];
var java_class = args[1];
JCLZ = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);

var methods_ptr = ptr(args[2]);

var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
if(name.indexOf("method01")>=0){
// method01addr = fnPtr_ptr;
continue;
}else if (name.indexOf("decrypt")>=0){
method02addr = fnPtr_ptr;
method02 = new NativeFunction(method02addr,'pointer',['pointer','pointer','pointer']);
method01addr = Module.findExportByName("libroysue.so", "Java_com_roysue_easyso1_MainActivity_method01")
}else{
continue;
}

}
}
});
}
}


function invokemethod01(contents){

console.log("method01_addr is =>",method01addr)
var method01 = new NativeFunction(method01addr,'pointer',['pointer','pointer','pointer']);
var NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
var result = null;
Java.perform(function(){
console.log("Java.vm.getEnv()",Java.vm.getEnv())
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
result = method01(Java.vm.getEnv(),JSTRING,JSTRING);
console.log("result is =>",result)
console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();

})
return result;
}

function invokemethod02(contents){
var result = null;
Java.perform(function(){
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
result = method02(Java.vm.getEnv(),JSTRING,JSTRING);
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();
})
return result;
}

git clone https://github.com/GravityBox/GravityBox.git 安装到xposed组件

Java与Native相互调用

1
2
3
4
XposedHelpers.getObjectField=getDeclaredField+setAccessible+getAccessible
XposedHelpers.setObjectField=getDeclaredField+setAccessible+setAccessible
XposedHelpers.callMethod(mTelephonyManager,"setDataEnabled",enabled)=
Method m = mPhoneWindowManagerClass.getDeclaredMethod("takeScreenshot",int.class)+m.setAccessible(true)+m.invoke(mPhoneWindowManager,1);

plugin wallbreaker classdump android.os.Build 查看系统参数信息

简单风控

roysue.cpp

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
#define TAG "roysuejni" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型


#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

JNIEXPORT jstring JNICALL method02(JNIEnv *env, jclass jcls, jstring str_) {
if (str_ == nullptr) return nullptr;

const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
char *result = AES_128_CBC_PKCS5_Decrypt(str);

env->ReleaseStringUTFChars(str_, str);

jstring jResult = getJString(env, result);
free(result);

return jResult;
}


bool function_check_tracerPID() {
bool b = false ;
int pid = getpid();
std::string file_name = "/proc/pid/status";
std::string line;
file_name.replace(file_name.find("pid"), 3, std::to_string(pid));
LOGE("replace file name => %s", file_name.c_str());
std::ifstream myfile(file_name, std::ios::in);
if (myfile.is_open()) {
while (getline(myfile, line)) {
// 获取父id, cat /proc/2345/status
size_t TracerPid_pos = line.find("TracerPid");
if (TracerPid_pos == 0) {
line = line.substr(line.find(":") + 1);
LOGE("file line => %s", line.c_str());
if (std::stoi(line.c_str()) != 0) {
LOGE("trace pid => %s, i want to exit.", line.c_str());
b = true ;
// kill(pid, 9);
break;
}
}
}
myfile.close();
}
return b ;
}

bool system_getproperty_check() {
char man[256], mod[156];
/* A length 0 value indicates that the property is not defined */
int lman = __system_property_get("ro.product.manufacturer", man);
int lmod = __system_property_get("ro.product.model", mod);
int len = lman + lmod;
char *pname = NULL;
if (len > 0) {
pname = static_cast<char *>(malloc(len + 2));
snprintf(pname, len + 2, "%s/%s", lman > 0 ? man : "", lmod > 0 ? mod : "");
}

bool b = false;
if(strstr(pname,"Google"))b=true;
LOGE("[roysue device]: [%s] result is => %d\n", pname ? pname : "N/A",b);
return b;
}

JNIEXPORT jstring JNICALL fuck(JNIEnv *env, jclass jcls, jstring str_) {
if (str_ == nullptr) return nullptr;

char *str = const_cast<char *>(env->GetStringUTFChars(str_, JNI_FALSE));

char* sign = "REAL";

jclass buildClazz = env->FindClass("android/os/Build");
jfieldID FINGERPRINT = env->GetStaticFieldID(buildClazz,"FINGERPRINT","Ljava/lang/String;");
jstring fingerprint = static_cast<jstring>(env->GetStaticObjectField(buildClazz, FINGERPRINT));

if( function_check_tracerPID() || system_getproperty_check() || strstr( env->GetStringUTFChars(fingerprint, JNI_FALSE),"aosp")){
sign = "FAKE";
}

// rightsign : 18d1fb4c2bac56c180f763e359d1d717500787ee90b32ed12f588e51f60e458d08303dcff7dd08a39b13c75e3f1ba9d9
//
// before entering aes => requestUserInfoFAKE
// wrongsign:bc57f886b8822f0465fb87bedd58c4ba9bad4efc9e7556df925701f0427c72cb8b23a1c25ff9241f8199cb6db5437fe2

strcat(str,sign);
jstring jstr = getJString(env, str);


LOGI("before entering aes => %s",str);

//// byte[] bytes = MessageDigest.getInstance("MD5").digest(input.getBytes());

jclass MessageGigest = env->FindClass("java/security/MessageDigest");
jmethodID getInstance = env->GetStaticMethodID(MessageGigest, "getInstance",
"(Ljava/lang/String;)Ljava/security/MessageDigest;");
jobject md5 = env->CallStaticObjectMethod(MessageGigest, getInstance,
getJString(env, "MD5"));
jmethodID digest = env->GetMethodID(MessageGigest, "digest", "([B)[B");

jclass stringClazz = env->FindClass("java/lang/String");
jmethodID getbytes = env->GetMethodID(stringClazz, "getBytes", "()[B");
jbyteArray jba = static_cast<jbyteArray>(env->CallObjectMethod(jstr, getbytes));
jbyteArray md5result = static_cast<jbyteArray>(env->CallObjectMethod(md5, digest, jba));

char *cmd5 = reinterpret_cast<char *>(env->GetByteArrayElements(md5result, 0));

char md5string[33];
for(int i = 0; i < 16; ++i)
sprintf(&md5string[i*2], "%02x", (unsigned int)cmd5[i]);

char *result = AES_128_CBC_PKCS5_Encrypt(str);
// strcat(result,"XXXXXX");
strcat(result,md5string);

jstring finalResult = getJString(env, result);

env->ReleaseStringUTFChars(str_, str);

free(result);

return finalResult;
}


static JNINativeMethod method_table[] = {
{"decrypt", "(Ljava/lang/String;)Ljava/lang/String;", (void *) method02},
{"Sign", "(Ljava/lang/String;)Ljava/lang/String;", (void *) fuck},
// (参数1类型标示;参数2类型标示;参数3类型标示...)返回值类型标示
};

static int registerMethods(JNIEnv *env, const char *className,
JNINativeMethod *gMethods, int numMethods) {
jclass clazz = env->FindClass(className);
if (clazz == nullptr) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL Java_com_roysue_easyso1_MainActivity_method01(JNIEnv *env, jclass jcls, jstring str_) {
if (str_ == nullptr) return nullptr;

const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
char *result = AES_128_CBC_PKCS5_Encrypt(str);

env->ReleaseStringUTFChars(str_, str);

jstring jResult = getJString(env, result);
free(result);

return jResult;
}


JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
_JUNK_FUN_0

JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
assert(env != nullptr);

// 注册native方法
if (!registerMethods(env, "com/roysue/easyso1/MainActivity", method_table,
NELEM(method_table))) {
return JNI_ERR;
}

return JNI_VERSION_1_6;
}

unidbg

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
public class MainActivity extends AbstractJni {

public static void main(String[] args) {
long start = System.currentTimeMillis();
com.r0ysue.easyso.MainActivity mainActivity = new com.r0ysue.easyso.MainActivity();
System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
mainActivity.crack();
}

private final AndroidEmulator emulator;
private final VM vm;
private final DvmClass dvmClass;
private String className = "com/roysue/easyso1/MainActivity";
private final Module module;

private MainActivity() {
emulator = AndroidEmulatorBuilder
.for32Bit()
// .addBackendFactory(new DynarmicFactory(true))
.build();
Memory memory = emulator.getMemory();
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);

vm = emulator.createDalvikVM(null);
System.out.println(vm);
vm.setVerbose(true);
vm.setJni(this);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libroysue.so"), false);

module = dm.getModule();

Dobby dobby = Dobby.getInstance(emulator);
// 2. 使用ida pro查看导出方法名,尝试hook
dobby.replace(module.findSymbolByName("_Z24function_check_tracerPIDv"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
@Override
// 3. contextk可以拿到参数,originFunction是原方法的地址
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
// System.out.println("create_thread_check_traceid.onCall function address => 0x" + Long.toHexString(originFunction));
System.out.println("calling _Z14function_checkv ....");
// return HookStatus.RET(emulator, originFunction);
// return null;
return HookStatus.LR(emulator, 0);
}
@Override

public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println(" calling _Z14function_checkv .... return false");

// context.getIntArg(0) ;

}
}, false);

dm.callJNI_OnLoad(emulator);
dvmClass = vm.resolveClass(className);
}

private void crack() {

DvmObject result = dvmClass.callStaticJniMethodObject(emulator,"Sign(Ljava/lang/String;)Ljava/lang/String;","requestUserInfo");
// long start = System.currentTimeMillis();

System.out.println("final result is => "+ result.getValue());
}
}

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
setTimeout(function () {
Java.perform(function () {
// Java.use("android.os.Build").FINGERPRINT.value = "12345"

console.log("found strcat address => ", Module.findExportByName(null, "strcat"));
var strcat = new NativeFunction(Module.findExportByName(null, "strcat"), "pointer", ["pointer", "pointer"])
Interceptor.replace(Module.findExportByName(null, "strcat"), new NativeCallback(function (arg0, arg1) {
console.log("arg[0] is => ", arg0.readCString());
console.log("arg[1] is => ", arg1.readCString());
// if(arg1.readCString().indexOf("FAKE")>=0){
// var REAL = Memory.allocUtf8String("REAL");
// arg1 = REAL ;
// console.log("change FAKE to REAL")
// }
var retval = strcat(arg0, arg1);
return retval;
}, "pointer", ["pointer", "pointer"]));
})
// var strcat_addr = Module.findExportByName(null,"strcat")
// console.log("found address is => ",strcat_addr);
// var strcat = new NativeFunction(strcat_addr,"pointer",["pointer","pointer"]);
// Interceptor.replace(strcat_addr, new NativeCallback(function(arg1, arg2){
// console.log("arg1 is => ",arg1);
// console.log("arg2 is => ",arg2);
// var result = strcat(arg1,arg2);
// console.log("result is => ",result);
// }, "pointer", ["pointer", "pointer"]));

}, 0);

function invoke() {
Java.perform(function () {
var result = Java.use("com.roysue.easyso1.MainActivity").Sign("requestUserInfo");
console.log("result is => ", result);

})
}

OnCreate的Native化

看jni签名的方法,jadx的smali面板

MainActivity

1
2
3
4
static {
System.loadLibrary("roysue");
}
protected native void onCreate(Bundle savedInstanceState);

roysue.cpp

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
// 动态注册不需要extern "C"
//extern "C"
//JNIEXPORT void JNICALL
//JAVA_com_roysue_easyso1_MainActivity_onCreate(JNIEnv *env,jobject thiz, jobject saved_instance_state)
void ononon(JNIEnv *env, jobject thiz, jobject saved_instance_state) {
// TODO: implement onCreate()

// super.onCreate(savedInstanceState);
jclass AppCompatActivity_class1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");
jclass MainActivity_class1 = env->FindClass("com/roysue/easyso1/MainActivity");

jclass MainActivity_class2 = env->GetObjectClass(thiz);
jclass AppCompatActivity_class2 = env->GetSuperclass(MainActivity_class2);
jmethodID onCreate = env->GetMethodID(AppCompatActivity_class2, "onCreate",
"(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz, AppCompatActivity_class2, onCreate, saved_instance_state);

// setContentView(R.layout.activity_main);
jmethodID setContentView = env->GetMethodID(AppCompatActivity_class2, "setContentView", "(I)V");
jclass Rlayout = env->FindClass("com/roysue/easyso1/R$layout");
jfieldID activity_main = env->GetStaticFieldID(Rlayout, "activity_main", "I");
jint activity_main_value = env->GetStaticIntField(Rlayout, activity_main);
env->CallVoidMethod(thiz, setContentView, activity_main_value);

// TextView tv = findViewById(R.id.sample_text);
jmethodID findViewById = env->GetMethodID(AppCompatActivity_class2, "findViewById",
"(I)Landroid/view/View;");
jclass Rid = env->FindClass("com/roysue/easyso1/R$id");
jfieldID sample_text = env->GetStaticFieldID(Rid, "sample_text", "I");
jint sample_text_value = env->GetStaticIntField(Rid, sample_text);
jobject tv = env->CallObjectMethod(thiz, findViewById, sample_text_value);
// tv.setText("r0ysue");
jstring r0ysue = env->NewStringUTF("r0ysue");
jclass textView = env->FindClass("android/widget/TextView");
jmethodID setText = env->GetMethodID(textView, "setText", "(Ljava/lang/CharSequence;)V");
jstring r0ysueEn = Java_com_roysue_easyso1_MainActivity_method01(env, MainActivity_class2,
r0ysue);
jmethodID Javamethod01 = env->GetStaticMethodID(MainActivity_class2, "method01",
"(Ljava/lang/String;)Ljava/lang/String;");
jstring r0ysueEn2 = static_cast<jstring>(env->CallStaticObjectMethod(MainActivity_class2,
Javamethod01, r0ysue));
// env->CallVoidMethod(tv, setText, r0ysueEn);
// 反射
env->CallVoidMethod(tv, setText, r0ysueEn2);

// 超出局部引用表最大数量不及时释放将导致JNI局部引用表的溢出
for (int i = 0; i < 2048; i++) {
jstring jstr = env->NewStringUTF("12345");
LOGI(" local Reference is Num %d : ", i);
}

}
static JNINativeMethod method_table[] = {
{"decrypt", "(Ljava/lang/String;)Ljava/lang/String;", (void *) method02},
{"Sign", "(Ljava/lang/String;)Ljava/lang/String;", (void *) fuck},
{"onCreate", "(Landroid/os/Bundle;)V", (void *) ononon},
// (参数1类型标示;参数2类型标示;参数3类型标示...)返回值类型标示
};

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
48
49
50
var ArtMethod_PrettyMethod
function readStdString(str) {
if ((str.readU8() & 1) === 1) { // size LSB (=1) indicates if it's a long string
return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}
return str.add(1).readUtf8String();
}
function attach(addr) {
Interceptor.attach(addr, {
onEnter: function (args) {
this.arg0 = args[0]
},
onLeave: function (retval) {
var modulemap = new ModuleMap()
var module = modulemap.find(retval)
if (module != null) {
var string = Memory.alloc(0x100)
ArtMethod_PrettyMethod(string, this.arg0, 1)
console.log('method_name =>', readStdString(string), ',offset=>', ptr(retval).sub(module.base), ',module_name=>', module.name)
if(readStdString(string).indexOf("method01")>=0){
Interceptor.attach(ptr(retval),{
onEnter:function(args){
console.log("entering method01",Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
},onLeave:function(){
console.log("leaving method01")
}
})
};
}
}
});
}
// hook_RegisterNative显示所有动态注册方法,静态注册只能Java调用过才显示
function hook_RegisterNative() {
var libart = Process.findModuleByName('libart.so')
var symbols = libart.enumerateSymbols()
for (var i = 0; i < symbols.length; i++) {
if (symbols[i].name.indexOf('PrettyMethod') > -1 && symbols[i].name.indexOf('ArtMethod') > -1 && symbols[i].name.indexOf("Eb") >= 0) {
ArtMethod_PrettyMethod = new NativeFunction(symbols[i].address, "void", ['pointer', "pointer", "bool"])
}
if (symbols[i].name.indexOf('RegisterNative') > -1 && symbols[i].name.indexOf('ArtMethod') > -1 && symbols[i].name.indexOf('RuntimeCallbacks') < 0) {
attach(symbols[i].address)
}

}

}
function main() {
hook_RegisterNative()
}

Android JNI(一)——NDK与JNI基础讲述了局部引用和全局引用的概念

JNI局部引用、全局引用和弱全局引用

JNI动静态绑定和追踪

一种通用超简单的Android Java Native方法Hook

Dalvik&Art_RegisterNativesTrace 目录下打印log

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
void ononon2(JNIEnv *env, jobject thiz,
jobject saved_instance_state) {
// TODO: implement onCreate()

// super.onCreate(savedInstanceState);
jclass AppCompatActivity_class1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");
jclass MainActivity_class1 = env->FindClass("com/roysue/easyso1/MainActivity");

jclass MainActivity_class2 = env->GetObjectClass(thiz);
jclass AppCompatActivity_class2 = env->GetSuperclass(MainActivity_class2);
jmethodID onCreate = env->GetMethodID(AppCompatActivity_class2,"onCreate","(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz,AppCompatActivity_class2,onCreate,saved_instance_state);

// setContentView(R.layout.activity_main);
jmethodID setContentView = env->GetMethodID(AppCompatActivity_class2,"setContentView","(I)V");
jclass Rlayout = env->FindClass("com/roysue/easyso1/R$layout");
jfieldID activity_main = env->GetStaticFieldID(Rlayout,"activity_main","I");
jint activity_main_value = env->GetStaticIntField(Rlayout,activity_main);
env->CallVoidMethod(thiz,setContentView,activity_main_value);

// TextView tv = findViewById(R.id.sample_text);
jmethodID findViewById = env->GetMethodID(AppCompatActivity_class2,"findViewById",
"(I)Landroid/view/View;");
jclass Rid = env->FindClass("com/roysue/easyso1/R$id");
jfieldID sample_text = env->GetStaticFieldID(Rid,"sample_text","I");
jint sample_text_value = env->GetStaticIntField(Rid,sample_text);
jobject tv = env->CallObjectMethod(thiz,findViewById,sample_text_value);
// tv.setText("r0ysue");
jstring r0ysue = env->NewStringUTF("r0ysueONONON2");
jclass textView = env->FindClass("android/widget/TextView");
jmethodID setText = env->GetMethodID(textView,"setText","(Ljava/lang/CharSequence;)V");
jstring r0ysueEn = Java_com_roysue_easyso1_MainActivity_method01(env,MainActivity_class2,r0ysue);
jmethodID Javamethod01 = env->GetStaticMethodID(MainActivity_class2,"method01","(Ljava/lang/String;)Ljava/lang/String;");
jstring r0ysueEn2 = static_cast<jstring>(env->CallStaticObjectMethod(MainActivity_class2,
Javamethod01, r0ysue));
env->CallVoidMethod(tv,setText,r0ysueEn2);
// env->CallVoidMethod(tv,setText,r0ysue);


jclass buildClazz = env->FindClass("android/os/Build");
jfieldID FINGERPRINT = env->GetStaticFieldID(buildClazz,"FINGERPRINT","Ljava/lang/String;");
jstring fingerprint = static_cast<jstring>(env->GetStaticObjectField(buildClazz, FINGERPRINT));
if( function_check_tracerPID() || system_getproperty_check() || strstr( env->GetStringUTFChars(fingerprint, JNI_FALSE),"aosp")){
int a,b,c;
a=1;
b=0;
c=a/b;
LOGI("roysuey is %d",c);
}
LOGI("roysuey is %d",100);

//
// for (int i = 0; i < 2048; i++) {
// jstring jstr = env->NewStringUTF("12345");
// LOGI(" local Reference is Num %d : ", i );
// }

}
static JNINativeMethod method_table[] = {
{"decrypt", "(Ljava/lang/String;)Ljava/lang/String;", (void *) method02},
{"Sign", "(Ljava/lang/String;)Ljava/lang/String;", (void *) fuck},
{"onCreate", "(Landroid/os/Bundle;)V", (void *) ononon},
{"onCreate", "(Landroid/os/Bundle;)V", (void *) ononon2},
// (参数1类型标示;参数2类型标示;参数3类型标示...)返回值类型标示
};
extern "C"
JNIEXPORT void JNICALL
Java_com_roysue_easyso1_MainActivity_onCreate(JNIEnv *env, jobject thiz,
jobject saved_instance_state) {
// TODO: implement onCreate()

// super.onCreate(savedInstanceState);
jclass AppCompatActivity_class1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");
jclass MainActivity_class1 = env->FindClass("com/roysue/easyso1/MainActivity");

jclass MainActivity_class2 = env->GetObjectClass(thiz);
jclass AppCompatActivity_class2 = env->GetSuperclass(MainActivity_class2);
jmethodID onCreate = env->GetMethodID(AppCompatActivity_class2,"onCreate","(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz,AppCompatActivity_class2,onCreate,saved_instance_state);

// setContentView(R.layout.activity_main);
jmethodID setContentView = env->GetMethodID(AppCompatActivity_class2,"setContentView","(I)V");
jclass Rlayout = env->FindClass("com/roysue/easyso1/R$layout");
jfieldID activity_main = env->GetStaticFieldID(Rlayout,"activity_main","I");
jint activity_main_value = env->GetStaticIntField(Rlayout,activity_main);
env->CallVoidMethod(thiz,setContentView,activity_main_value);

// TextView tv = findViewById(R.id.sample_text);
jmethodID findViewById = env->GetMethodID(AppCompatActivity_class2,"findViewById",
"(I)Landroid/view/View;");
jclass Rid = env->FindClass("com/roysue/easyso1/R$id");
jfieldID sample_text = env->GetStaticFieldID(Rid,"sample_text","I");
jint sample_text_value = env->GetStaticIntField(Rid,sample_text);
jobject tv = env->CallObjectMethod(thiz,findViewById,sample_text_value);
// tv.setText("r0ysue");
jstring r0ysue = env->NewStringUTF("r0ysueSTATIC");
jclass textView = env->FindClass("android/widget/TextView");
jmethodID setText = env->GetMethodID(textView,"setText","(Ljava/lang/CharSequence;)V");
// jstring r0ysueEn = Java_com_roysue_easyso1_MainActivity_method01(env,MainActivity_class2,r0ysue);
// jmethodID Javamethod01 = env->GetStaticMethodID(MainActivity_class2,"method01","(Ljava/lang/String;)Ljava/lang/String;");
// jstring r0ysueEn2 = static_cast<jstring>(env->CallStaticObjectMethod(MainActivity_class2,
// Javamethod01, r0ysue));
// env->CallVoidMethod(tv,setText,r0ysueEn2);
env->CallVoidMethod(tv,setText,r0ysue);
//
// for (int i = 0; i < 2048; i++) {
// jstring jstr = env->NewStringUTF("12345");
// LOGI(" local Reference is Num %d : ", i );
// }

}
文章作者: J
文章链接: http://onejane.github.io/2021/07/07/SO逆向之动态调试入门/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏