frida内存扫描以及关键代码定位.

frida 发布了新特性, 支持硬件断点, 能更好的对内存区域进行扫描, 并快速定位出哪段代码读写了这片内存区域
感兴趣的可以看下
贴上地址 https://frida.re/news/

为了测试这个新特性, 我自己写了一个app
img_1.png

当用户点击按钮, 数字就减去1

让我们来看看native层实现

1
2
3
4
5
6
7
8
9
static int num = 1000;

extern "C"
JNIEXPORT jint JNICALL
Java_com_zj_my_1md5_MainActivity_getInt(JNIEnv *env, jobject thiz) {
// TODO: implement getInt()
num = num - 1;
return num;
}

我们定义了一个全局静态变量, 每次用户点击button getInt函数就会被调用 并且返回-1后的值.

接下来我们试试用frida的新特性来快速分析一下, 看看到底是哪里实现了 -1 的操作.
根据官方文档

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
let matches = [];

function scan(pattern) {
const locations = new Set();
console.log(123)
console.log(Process.enumerateMallocRanges())
for (const r of Process.enumerateMallocRanges()) {

for (const match of Memory.scanSync(r.base, r.size, pattern)) {
locations.add(match.address.toString());
}
}
matches = Array.from(locations).map(ptr);
console.log('Found', matches.length, 'matches');
}

function reduce(val) {
matches = matches.filter(location => location.readU32() === val);
console.log('Filtered down to:');
console.log(JSON.stringify(matches));
}


function patternFromU32(val) {
return new MatchPattern(ptr(val).toMatchPattern().substr(0, 11));
}

我们执行一下

1
frida -U my_md5 -l 00bang/tt.js

然后执行

1
scan(patternFromU32(1000))

结果如下
img_1.png

搜了一圈, 应该是还不支持linux, 于是决定改写一下
主要是这段代码报错了 Process.enumerateMallocRanges(), 枚举所有的 malloc动态申请的堆内存.

那这篇文章主题就应该是 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
51
52
53
54
55
56
57
58
59
60
61
62
let matches = [];

function scan(pattern) {
const locations = new Set();
console.log(123)
console.log(Process.enumerateMallocRanges())
for (const r of Process.enumerateMallocRanges()) {

for (const match of Memory.scanSync(r.base, r.size, pattern)) {
locations.add(match.address.toString());
}
}
matches = Array.from(locations).map(ptr);
console.log('Found', matches.length, 'matches');
}


function linux_scan(pattern) {
const locations = new Set();
// console.log(123)

let target_so = null;
// const m = Process.enumerateModules()[0];
for (const process of Process.enumerateModules()){
// console.log("process ", process.name)
if(process.name.indexOf("libcheckout.so") != -1){
//if(process.name.indexOf("libmetasec_ml.so") != -1){
target_so = process
break;
}
}
const m = target_so;

console.log("so --->"+m.name)
// console.log(hexdump(m.base));

for (const match of Memory.scanSync(m.base, m.size, pattern)) {
locations.add(match.address.toString());
}
matches = Array.from(locations).map(ptr);
console.log(ptr(matches - m.base))
// console.log(matches.)
// Memory.writeU32(matches[0], 100)
console.log('Found', matches.length, 'matches');
console.log("打印 matches")
for(const match of matches){
// console.log("match ", ptr(matches - m.base))
console.log("match ", ptr(match - m.base))
console.log(hexdump(ptr(match)))
}
}

function reduce(val) {
matches = matches.filter(location => location.readU32() === val);
console.log('Filtered down to:');
console.log(JSON.stringify(matches));
}


function patternFromU32(val) {
return new MatchPattern(ptr(val).toMatchPattern().substr(0, 11));
}

然后执行一下

1
linux_scan(patternFromU32(1000))

结果如下
img_1.png

结果有四处 都匹配的1000, 1000的16进制是 0x3e8 跟图中正好匹配. 看来匹配的没毛病
然后我们点击一下按钮, 数字变成了 999, 然后执行一下

1
reduce(999)

这个功能太强大了, 类似于 CE以及 GG修改器中的搜索, 在上一次搜索的基础上再次搜索.
就相当于看看1000中的四处, 现在有几处变成了 999, 结果如下

img_1.png

可以看到只有一处.

这时候可以该地址 减去 so的基地址. 可以通过 frida api 获取基址或者 通过下面代码查看基址

1
cat /proc/$(pidof com.xx.my_md5 )/maps | grep checkout.so

img_1.png

0x7ac95687d8 - 0x7ac951f000 = 0x497d8

ida打开 checkout.so 查看 0x497d8
可以看到 已初始化的全局静态变量 放在 .data 段, 并且值是 0x3E8, 对应十进制 1000

img_1.png

我们对该地址添加一个写断点, 看看哪里往该地址写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function installWatchpoint(address, size, conditions) {
const thread = Process.enumerateThreads()[0];

Process.setExceptionHandler(e => {
console.log(`\n=== Handler got ${e.type} exception at ${e.context.pc}`);

if (Process.getCurrentThreadId() === thread.id &&
['breakpoint', 'single-step'].includes(e.type)) {
thread.unsetHardwareWatchpoint(0);
console.log('\tDisabled hardware watchpoint');
return true;
}

console.log('\tPassing to application');
return false;
});

thread.setHardwareWatchpoint(0, address, size, conditions);

console.log('Ready');
}

然后执行该函数, 结果如下

img_1.png

关注这个地址 0x7ac953dbbc
0x7ac953dbbc - so基址 = 0x1ebbc

ida查看地址 0x1ebbc
img_1.png

yyds!!!!!

最后放上完整代码

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
let matches = [];

function scan(pattern) {
const locations = new Set();
console.log(123)
console.log(Process.enumerateMallocRanges())
for (const r of Process.enumerateMallocRanges()) {

for (const match of Memory.scanSync(r.base, r.size, pattern)) {
locations.add(match.address.toString());
}
}
matches = Array.from(locations).map(ptr);
console.log('Found', matches.length, 'matches');
}


function linux_scan(pattern) {
const locations = new Set();
// console.log(123)

let target_so = null;
// const m = Process.enumerateModules()[0];
for (const process of Process.enumerateModules()){
// console.log("process ", process.name)
if(process.name.indexOf("libcheckout.so") != -1){
// if(process.name.indexOf("libmetasec_ml.so") != -1){
target_so = process
break;
}
}
const m = target_so;

console.log("so--->"+m.name)
// console.log(hexdump(m.base));

for (const match of Memory.scanSync(m.base, m.size, pattern)) {
locations.add(match.address.toString());
}
matches = Array.from(locations).map(ptr);
console.log(ptr(matches - m.base))
// console.log(matches.)
// Memory.writeU32(matches[0], 100)
console.log('Found', matches.length, 'matches');
console.log("打印 matches")
for(const match of matches){
// console.log("match ", ptr(matches - m.base))
console.log("match ", ptr(match - m.base))
console.log(hexdump(ptr(match)))
}
}


function installWatchpoint(address, size, conditions) {
const thread = Process.enumerateThreads()[0];

Process.setExceptionHandler(e => {
console.log(`\n=== Handler got ${e.type} exception at ${e.context.pc}`);

if (Process.getCurrentThreadId() === thread.id &&
['breakpoint', 'single-step'].includes(e.type)) {
thread.unsetHardwareWatchpoint(0);
console.log('\tDisabled hardware watchpoint');
return true;
}

console.log('\tPassing to application');
return false;
});

thread.setHardwareWatchpoint(0, address, size, conditions);

console.log('Ready');
}





function reduce(val) {
matches = matches.filter(location => location.readU32() === val);
console.log('Filtered down to:');
console.log(JSON.stringify(matches));
}


function patternFromU32(val) {
return new MatchPattern(ptr(val).toMatchPattern().substr(0, 11));
}

回顾一下, 学到了什么
1、通过frida 扫描内存中的数字/字符串
2、定位存放关键数据的内存后, 对该地址下写断点, 当该地址的值发生变化后, 就知道是哪里的代码操作的了.