Frida-Windows

Frida是一个跨平台的Hook框架,支持MacOS、Windows、Linux、IOS以及Android等平台,其官网如下:

https://github.com/frida/frida

frida-tools

frida-tools是一个Python包,提供了一些CLI工具,可通过下述命令进行安装.

1
pip install frida-tools

frida

frida命令行工具允许向指定进程进行代码注入,其参数说明如下:

相关命令 说明
-U, –usb 连接到USB设备
-H HOST, –host HOST 连接到指定HOST的frida-server
-f TARGET, –file TARGET 以spawn模式执行指定路径程序
-F, –attach-frontmost 附加最前面的应用程序
-p PID, –attach-pid PID 附加指定PID进程
-l SCRIPT, –load SCRIPT 加载脚本
-C USER_CMODULE, –cmodule USER_CMODULE 加载CMODULE
-o LOGFILE, –output LOGFILE 输出日志到文件
–runtime {qjs,v8} script运行引擎选择
–debug 开启调试
–pause 以spawn模式运行程序,添加该参数可使其暂停,使用**%resume**可恢复执行

frida-trace

frida-trace是一个动态跟踪函数调用的工具,其参数说明如下:

相关命令 说明
-U, –usb 连接到USB设备
-H HOST, –host HOST 连接到指定HOST的frida-server
-f TARGET, –file TARGET 以spawn模式执行指定路径程序
-F, –attach-frontmost 附加最前面的应用程序
-p PID, –attach-pid PID 附加指定PID进程
–runtime {qjs,v8} script运行引擎选择
-I MODULE, –include-module MODULE 包含模块
-X MODULE, –exclude-module MODULE 排除模块
-i FUNCTION, –include FUNCTION 包含函数,可使用通配符*
-x FUNCTION, –exclude FUNCTION 排除函数
-a MODULE!OFFSET, –add MODULE!OFFSET 添加模块偏移跟踪
-j JAVA_METHOD, –include-java-method JAVA_METHOD 包含Java方法
-J JAVA_METHOD, –exclude-java-method JAVA_METHOD 排除Java方法
-o OUTPUT, –output OUTPUT 输出信息到文件

frida-trace使用示例如下:

1
2
3
4
5
frida-trace -f "XXX.exe" -i "CreateFile*"
frida-trace -f "XXX.exe" -i "CreateFileW"
frida-trace -f "XXX.exe" -i "CreateFileW" -X "KERNEL32.DLL"
frida-trace -f "XXX.exe" -a "kernelbase.dll!0x11DAF0"
frida-trace -f "XXX.exe" -a "kernelbase.dll!0x11DAF0" -a "kernel32.dll!0x1EA70"

处理数据类型

字符串读取和分配

字符串分配API如下:

API 说明
Memory.allocAnsiString() 分配Ansi字符串(仅限Windows)
Memory.allocUtf8String() 分配UTF8字符串
Memory.allocUtf16String() 分配UTF16字符串(仅限Windows)

分配字符串时,一定要将其设置为常量,以避免字符串在某一时刻从内存中清除,示例如下:

1
const myTestString = Memory.allocAnsiString("Hello XiaLuoHun!");

字符串读取API如下:

API 说明
{NativePointer}.readCString() 读取C风格字符串
{NativePointer}.readAnsiString() 读取Ansi字符串
{NativePointer}.readUtf8String() 读取UTF8字符串
{NativePointer}.readUtf16String() 读取UTF16字符串

注意如下:

  1. {NativePointer}是一个指针,指向包含字符串的地址.
  2. 可以向这些API传递一个数字作为参数,以指定要读取的字节数.

使用示例如下:

1
2
3
4
5
//myTestString指向Ansi字符串
myTestString.readAnsiString();

//myTestString指向C风格字符串,且字符串长度为1024字节
myTestString.readCString(1024);

接下来使用一个案例来展示字符串读取API的使用.

测试代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
#include <Windows.h>

int main()
{
	WCHAR lpBuffer[MAX_PATH];
	LPWSTR* lpFilePart{};
	DWORD result;

	result = SearchPathW(NULL, L"c:\\windows\\", NULL, MAX_PATH, lpBuffer, lpFilePart);
	std::cout << "SearchPath retval: " << result << std::endl;
}

SearchPathW说明如下:

1
2
3
4
5
6
7
DWORD WINAPI SearchPathW(
    _In_opt_ LPCWSTR lpPath,
    _In_ LPCWSTR lpFileName,
    _In_opt_ LPCWSTR lpExtension,
    _In_ DWORD nBufferLength,
    _Out_writes_to_opt_(nBufferLength,return + 1) LPWSTR lpBuffer,
    _Out_opt_ LPWSTR* lpFilePart);

Frida Js Hook代码如下:

1
2
3
4
5
6
7
const searchPathPtr = Module.getExportByName("KERNELBASE.DLL", "SearchPathW");

Interceptor.attach(searchPathPtr, {
    onEnter(args) {
        console.log("Output: " + args[1].readUtf16String())
    }
});

与上述等效的TypeScript代码如下:

1
2
3
4
5
6
7
8
9
const searchPathPtr:NativePointer = Module.getExportByName("KERNELBASE.DLL", "SearchPathW");

class SearchPathW {
  onEnter(args:NativePointer[]) {
    console.log("Output: " + args[1].readUtf16String());
  }
}

Interceptor.attach(searchPathPtr, new SearchPathW);

注入命令如下:

1
2
npm run watch
frida -f "XXX.exe" -l .\_agent.js

数字

我们需要知道参数是纯数字类型还是其地址,如果不是内存中的地址,我们不能使用Frida的API处理数字类型,否则会导致目标进程出错.

通过值传递的参考代码如下:

1
2
3
4
int add(int a, int b)
{
	return a + b;
}

在Frida中可以使用下述代码进行参数读取:

1
2
3
4
5
6
Interceptor.attach(addPtr, {
  onEnter(args) {
    console.log("a: " + args[0].toInt32());
    console.log("b: " + args[1].toInt32());
  },
});

通过指针传递数值的参考代码如下:

1
2
3
4
5
//a = 1337, b=7331
int add(int* a, int* b)
{
	return *a + *b;
}

Frida提供的读取数值API如下:

API 说明
{}.readInt() 从给定地址读取整数
{}.readUInt() 从给定地址读取无符号整数
{}.readS8() 从指定地址读取带符号的8位、16位、32位或64位整数
{}.readShort() 从给定地址读取短整数
{}.readFloat() 从给定地址读取浮点数
{}.readDouble() 从给定地址读取双浮点数
{}.readLong() 从给定地址读取长数字
{}.readULong() 从给定地址读取无符号长数字
{}.readUShort() 从给定地址读取无符号短数
{}.readUS8() 从给定地址读取无符号整数

在Frida中可以使用下述代码进行参数读取:

1
2
3
4
5
6
Interceptor.attach(addPtr, {
  onEnter(args) {
    console.log("a: " + args[0].readInt());
    console.log("b: " + args[1].readInt());
  },
});

Frida提供的数值写入API如下:

API 说明
{}.writeInt() 向给定地址写入整数
{}.writeUInt() 向给定地址写入无符号整数
{}.writeS8() 向给定地址写入带符号的8位、16位、32位或64位整数
{}.writeShort() 向给定地址写入短整数
{}.writeFloat() 向给定地址写入浮点数
{}.writeDouble() 向给定地址写入双浮点数
{}.writeLong() 向给定地址写入长数字
{}.writeULong() 向给定地址写入无符号长数字
{}.writeUShort() 向给定地址写入无符号短数
{}.writeUS8() 向给定地址写入无符号整数

以上述指针传递参考代码为例,Frida可使用下述代码进行参数修改:

1
2
3
4
5
6
Interceptor.attach(addPtr, {
  onEnter(args) {
    args[0].writeInt(10);
    args[1].writeInt(20);
  },
});

指针

可以使用readPointer() API读取指针指向的地址.当有一个指向结构体的指针需要读取时,这种用法很有用.

recvfrom声明如下:

1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

Frida代码如下:

1
2
3
4
5
6
7
// ... 
    onEnter(args) {
        console.log(
            args[4].readPointer();
        );
    }
// ...

偏移量指针

Frida示例代码如下:

1
2
3
var myBaseAddr = Module.findBaseAddress('kernel32.dll');
var myOffsetPtr = myBaseAddr.add(0x76E)
console.log("myBaseAddr:  "+ myBaseAddr + "\nmyOffsetPtr: " + myOffsetPtr)

上述输出内容如下:

1
2
myBaseAddr:  0x7ffff0f00000
myOffsetPtr: 0x7ffff0f0076e

如果我们不知道模块名称或者模块名称与预期不符,可以使用Process.enumerateModulesSync() API,该API会返回模块列表.使用示例如下:

1
2
3
4
5
6
7
8
var modules = Process.enumerateModulesSync();
modules.forEach(function(module) {
    console.log("Name: " + module.name);
    console.log("Base Address: " + module.base);
    console.log("Size: " + module.size);
    console.log("Path: " + module.path);
    console.log("-------------------------------");
});

获取导出函数地址

在Frida中可以通过下述两种方式获取模块导出函数地址:

1
2
3
//var MessageBoxA = Module.findExportByName('user32.dll', 'MessageBoxA');
var MessageBoxA = Module.getExportByName('user32.dll', 'MessageBoxA');
console.log("MessageBoxA:", MessageBoxA)

当找不到导出函数地址时:

  • findExportByName返回NULL
  • getExportByName抛异常

故建议使用getExportByName,如果想使用findExportByName,请务必检查返回值.

ArrayBuffer指针

在分配字符串时,返回的是一个指针,拿到这个指针就可以调用read或write API对其内容进行修改.

1
2
[Local]-> Memory.allocUtf8String('foo')
"0x1e9f5446be0"

但是对于不返回地址的ArrayBuffer就不同了

1
2
[Local]-> array = new ArrayBuffer(10)
0000  00 00 00 00 00 00 00 00 00 00                     ..........

这种情况下,可以调用unwrap函数,返回指向ArrayBuffer的第一个元素指针

1
2
[Local]-> array.unwrap()
"0x1e9f5447210"

有时候为了保证脚本的可移植性,需要考虑指针的大小,可以调用下述API返回被检测进程的指针大小

1
Process.pointerSize

Hexdump

Hexdump在给定一个NativePointer或ArrayBuffer时,返回16进制试图,其语法如下:

1
2
3
4
5
6
7
8
9
hexdump(address, [, options])

//options值如下:
{
  offset: number,
  length: number,
  header: true|false,
  ansi: true|false,
}

使用示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
[Local]-> Process.enumerateModules()
[
    {
        "base": "0x7ff78ef10000",
        "name": "luoDst.exe",
        "path": "E:\\SoftWareWork\\VSWork\\luoDst\\x64\\Debug\\luoDst.exe",
        "size": 2744320
    },
    //---
]
[Local]-> console.log(hexdump(ptr(0x7ff78ef10000), {
    offset:0,
    length:100
}))
               0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
7ff78ef10000  4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00  MZ..............
7ff78ef10010  b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  ........@.......
7ff78ef10020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
7ff78ef10030  00 00 00 00 00 00 00 00 00 00 00 00 f8 00 00 00  ................
7ff78ef10040  0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68  ........!..L.!Th
7ff78ef10050  69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f  is program canno
7ff78ef10060  74 20 62 65

释放已分配内存

在调用Memory.alloc API时,当完成有效任务后,如何释放已分配的内存?

最好的做法就是对上述保存alloc地址的变量重新赋值,从而允许Frida释放它.示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
[Local]->  t = Memory.alloc(32);
"0x1a482928170"
[Local]->  t.writeUtf8String('Hello XiaLuoHun!')
"0x1a482928170"
[Local]-> console.log(hexdump(t))
              0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
1a482928170  48 65 6c 6c 6f 20 58 69 61 4c 75 6f 48 75 6e 21  Hello XiaLuoHun!
1a482928180  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
1a482928190  00 00 00 00 00 00 00 00 63 00 00 00 00 00 00 00  ........c.......
1a4829281a0  07 00 00 00 80 01 3e 00 00 00 00 00 02 00 00 00  ......>.........
1a4829281b0  20 81 92 82 a4 01 00 00 10 e8 91 82 a4 01 00 00   ...............
1a4829281c0  f0 23 92 82 a4 01 00 00 40 8b 90 82 a4 01 00 00  .#......@.......
1a4829281d0  00 00 00 00 00 00 00 00 a0 ae a3 82 a4 01 00 00  ................
1a4829281e0  d0 67 90 82 00 00 00 00 22 70 61 79 6c 6f 61 64  .g......"payload
1a4829281f0  60 00 00 00 00 00 00 00 73 00 00 00 00 00 00 00  `.......s.......
1a482928200  01 00 00 00 00 01 0c 00 00 00 00 00 00 00 00 00  ................
1a482928210  70 59 92 82 a4 01 00 00 20 c3 90 82 a4 01 00 00  pY...... .......
1a482928220  10 95 8f 82 a4 01 00 00 10 70 92 82 a4 01 00 00  .........p......
1a482928230  00 00 00 00 00 00 00 00 20 f7 8e 82 a4 01 00 00  ........ .......
1a482928240  88 b1 9a 5d ff 7f 00 00 01 01 00 00 a4 01 00 00  ...]............
1a482928250  70 82 92 82 a4 01 08 00 00 00 00 00 00 00 00 00  p...............
1a482928260  70 00 00 00 00 00 00 00 63 00 00 00 00 00 00 00  p.......c.......

当t为null时,内存区域值如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[Local]-> t=null
null
[Local]-> console.log(hexdump(ptr(0x1a482928170)))
              0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
1a482928170  02 00 00 00 62 00 00 00 78 01 fe 80 00 00 00 00  ....b...x.......
1a482928180  00 01 6f 00 00 00 00 00 5b 22 66 72 69 64 61 3a  ..o.....["frida:
1a482928190  72 70 63 22 2c 20 32 34 2c 20 22 63 61 6c 6c 22  rpc", 24, "call"
1a4829281a0  2c 20 22 66 72 69 64 61 45 76 61 6c 75 61 74 65  , "fridaEvaluate
1a4829281b0  45 78 70 72 65 73 73 69 6f 6e 22 2c 20 5b 22 63  Expression", ["c
1a4829281c0  6f 6e 73 6f 6c 65 2e 6c 6f 67 28 68 65 78 64 75  onsole.log(hexdu
1a4829281d0  6d 70 28 70 74 72 28 30 78 31 61 34 38 32 39 32  mp(ptr(0x1a48292
1a4829281e0  38 31 37 30 29 29 29 22 5d 5d 00 00 00 00 00 00  8170)))"]]......
1a4829281f0  90 00 00 00 00 00 00 00 73 00 00 00 00 00 00 00  ........s.......
1a482928200  01 00 00 00 00 01 0c 00 00 00 00 00 00 00 00 00  ................
1a482928210  70 59 92 82 a4 01 00 00 20 c3 90 82 a4 01 00 00  pY...... .......
1a482928220  10 95 8f 82 a4 01 00 00 10 70 92 82 a4 01 00 00  .........p......
1a482928230  00 00 00 00 00 00 00 00 20 f7 8e 82 a4 01 00 00  ........ .......
1a482928240  88 b1 9a 5d ff 7f 00 00 01 01 00 00 a4 01 00 00  ...]............
1a482928250  70 82 92 82 a4 01 08 00 00 00 00 00 00 00 00 00  p...............
1a482928260  70 00 00 00 00 00 00 00 63 00 00 00 00 00 00 00  p.......c.......

Hook基操

  1. 目标进程,测试代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 10;
	int b = 0;
	scanf("%d", &b);
	printf("%d + %d = %d\r\n", a, b, add(a, b));

	a = 200;
	printf("%d + %d = %d\r\n", a, b, add(a, b));
	return 0;
}
  1. Frida修改函数参数以及返回值的代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var addPtr = Process.mainModule.base.add(0x16F920)

var addListener = Interceptor.attach(addPtr, {
  onEnter(args){
    //保存参数0原始值
    this.arg0 = args[0]
    //修改参数0值
    args[0] = ptr(100)
  },
  onLeave(retvalue){
    //打印参数0原始值
    //console.log("[onLeave] arg0Src:", this.arg0)
    //修改返回值
    retvalue.replace(1000)

    //取消Hook
    addListener.detach()
  }
})

远程调试

  1. 在目标机运行frida server
1
2
//假设目标机IP为 192.168.59.129
.\frida-server-Win64.exe -l 0.0.0.0:6666
  1. 在本地使用下述命令执行Frida脚本
1
frida -H 192.168.59.129:8899 -p 6280 -l .\luoHook.js

脚本调试

frida脚本调试示例如下:

  1. frida开启调试
1
2
3
frida -f "XXX.exe" -l .\luoHook.js --runtime v8 --debug --pause
//回显内容如下:
Chrome Inspector server listening on port 9229
  1. 打开chrome://inspect,点击Open dedicated DevTools for Node

chromeInspect

  1. 对代码进行下断

js代码下断

  1. frida执行%resume,发现成功断下

js成功断下

创建本地函数

Frida 提供了NativeFunctions API可以创建从JS层调用的辅助函数.如果我们想要调用导出函数或目标进程空间内的任意函数,这将十分有用.其语法如下:

 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
new NativeFunction(address, returnType, argTypes[, abi])

//TYPES 可选如下:
void
pointer
int
uint
long
ulong
char
uchar
size_t
ssize_t
float
double
int8
uint8
int16
uint16
int32
uint32
int64
uint64
bool

//ABIS 可选如下:
default
Windows 32-bit:
	sysv
	stdcall
	thiscall
	fastcall
	mscdecl
Windows 64-bit:
	win64
UNIX x86:
	sysv
	unix64
UNIX ARM:
	sysv
	vfp
  1. 目标进程,测试代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 10;
	int b = 0;
	scanf("%d", &b);
	printf("%d + %d = %d\r\n", a, b, add(a, b));
	return 0;
}
  1. Frida注入代码如下:
1
2
3
4
var addPtr = Process.mainModule.base.add(0x16F920)
const addFunc = new NativeFunction(addPtr, 'int', ['int', 'int']);

console.log("addFunc:",addFunc(10, 100))

函数替换

对于函数替换,Frida为我们提供了两种方法,即使用Interceptor.replace()或Memory.patchCode().

  1. 目标进程,测试代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 10;
	int b = 0;
	scanf("%d", &b);
	printf("%d + %d = %d\r\n", a, b, add(a, b));
	return 0;
}
  1. Frida注入代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var addPtr = Process.mainModule.base.add(0x16F920)
console.log(addPtr)

//方式一
Interceptor.replace(addPtr,
    new NativeCallback(function(a, b){
        return 1022;
    },
    'int', ['int', 'int']))

//方式二
// Memory.patchCode(addPtr, Process.pageSize, function (code) {
//   const cw = new X86Writer(code, { pc: addPtr });
//   cw.putMovRegU32('eax', 1234);
//   cw.putRet();
//   cw.flush();
// });

NativeCallback声明如下:

1
new NativeCallback(func, returnType, argTypes[, abi])

内存扫描

对于内存扫描,Frida提供了以下两个API接口:

1
2
3
//pattern的格式为由空格分隔的16进制字符串
Memory.scan(address, size, pattern, callbacks)
Memory.scanSync(address, size, pattern)
  1. 目标进程,测试代码如下:
1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
{
	printf("%s\r\n", "XiaLuoHun Hello!");
	printf("%s\r\n", "XiaLuoXXn Hello!");
	return 0;
}
  1. 在Frida中编写代码,匹配"XiaLuo__n"如下:
 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
var mainModule = Process.mainModule;
var pattern = "58 69 61 4C 75 6F ?? ?? 6E";

//方式一
// var matches  = Memory.scanSync(mainModule.base, mainModule.size, pattern)
// matches.forEach(match => {
//     console.log(match.address + ":" + match.address.readCString(match.size));
// });

//方式二
Memory.scan(mainModule.base, mainModule.size, pattern, {
  onMatch(address, size) {
    console.log(address + ":" + size);

    //获取当前内存保护属性
    var oldProtect = null;
    var ranges = Process.enumerateRangesSync({
      protection: "---",
      coalesce: false,
    });
    for (var i = 0; i < ranges.length; i++) {
      var range = ranges[i];
      if (address.compare(range.base) >= 0 && address.compare(range.base.add(range.size)) < 0) {
        oldProtect = range.protection;
        break;
      }
    }
    //console.log("oldProtect:", oldProtect);

    Memory.protect(address, size, "rwx");

    //Luo
    address.writeByteArray([0x4c, 0x75, 0x6f, 0x00]);

    Memory.protect(address, size, oldProtect);
  },
});

使用自定义库

  1. 使用下述测试代码进行动态库编译
1
2
3
int mySub(int a, int b) {
	return a - b;
}
  1. 在Frida中编写下述代码进行动态库功能调用
1
2
3
4
5
var luoDllMod = Module.load("E:\\VSWork\\luoDll\\x64\\Release\\luoDll.dll")
var mySubAddr = luoDllMod.findExportByName("mySub")
var mySub = new NativeFunction(ptr(mySubAddr), 'int', ['int', 'int']);

console.log("mySub:", mySub(100, 20))

寄存器读写

  1. 目标进程,测试代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>

int add(int a, int b) {
	return a + b;
}

int main(int argc, char** argv)
{
	printf("result: %d\r\n", add(10, 20));
	return 0;
}
  1. Frida注入代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var addPtr = Process.mainModule.base.add(0x16F8F0)

Interceptor.attach(addPtr, {
    onEnter (args) {
      // console.log('args0:' + args[0]);
      // console.log('args1:' + args[1]);

      console.log('args1:' + this.context.rcx.toInt32());
      console.log('args2:' + this.context.rdx.toInt32());
    }
});

//修改寄存器
// Memory.patchCode(addPtr, Process.pointerSize, function (code) {
//   const cw = new X86Writer(code, { pc: addPtr });
//   cw.putMovRegU32('eax', 1234);
//   cw.putRet();
//   cw.flush();
// });

结构体解析

以Windows平台的SYSTEM_INFO为例进行解析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//win64 结构体成员偏移如下
typedef struct _SYSTEM_INFO {
    union {
        DWORD dwOemId; //0
        struct {
            WORD wProcessorArchitecture;
            WORD wReserved;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;
    DWORD dwPageSize; //4
    LPVOID lpMinimumApplicationAddress; //8
    LPVOID lpMaximumApplicationAddress; //16
    DWORD_PTR dwActiveProcessorMask; //24
    DWORD dwNumberOfProcessors; //32
    DWORD dwProcessorType; //36
    DWORD dwAllocationGranularity; //40
    WORD wProcessorLevel; //44
    WORD wProcessorRevision; //46
} SYSTEM_INFO, *LPSYSTEM_INFO;
  1. 目标进程,测试代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <Windows.h>

int main(int argc, char** argv)
{
	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);

	//std::cout << "sysInfoSize:" << sizeof(sysInfo) << "\n";
	//std::cout << "Hardware information: \n";
	//std::cout << "  OEM ID: " << sysInfo.dwOemId << "\n";
	//std::cout << "  Page size: " << sysInfo.dwPageSize << " bytes\n";
	//std::cout << "  Minimum application address: " << sysInfo.lpMinimumApplicationAddress << "\n";
	//std::cout << "  Maximum application address: " << sysInfo.lpMaximumApplicationAddress << "\n";
	//std::cout << "  Active processor mask: " << sysInfo.dwActiveProcessorMask << "\n";
	//std::cout << "  Number of processors: " << sysInfo.dwNumberOfProcessors << "\n";
	//std::cout << "  Processor type: " << sysInfo.dwProcessorType << "\n";
	//std::cout << "  Allocation granularity: " << sysInfo.dwAllocationGranularity << " bytes\n";
	//std::cout << "  Processor level: " << sysInfo.wProcessorLevel << "\n";
	//std::cout << "  Processor revision: " << sysInfo.wProcessorRevision << "\n";
	return 0;
}
  1. 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
var getSystemInfoAddr = Module.getExportByName("kernelbase.dll", "GetSystemInfo")

var flag = 0
Interceptor.attach(getSystemInfoAddr, {
  onEnter(args) {
    this.arg0 = args[0]
    flag += 1
  },
  onLeave(retval) {
    if (flag == 1) {
      var sysInfoPtr = ptr(this.arg0)
      var dwOemId = sysInfoPtr.readInt()
      var dwPageSize = sysInfoPtr.add(4).readInt()
      var lpMinimumApplicationAddress = sysInfoPtr.add(8).readU64().toString(16)
      var lpMaximumApplicationAddress = sysInfoPtr.add(16).readU64().toString(16)
      var dwActiveProcessorMask = sysInfoPtr.add(24).readU64()
      var dwNumberOfProcessors = sysInfoPtr.add(32).readInt()
      var dwProcessorType = sysInfoPtr.add(36).readInt()
      var dwAllocationGranularity = sysInfoPtr.add(40).readInt()
      var wProcessorLevel = sysInfoPtr.add(44).readU16()
      var wProcessorRevision = sysInfoPtr.add(46).readU16()

      console.log("dwOemId:", dwOemId)
      console.log("dwPageSize:", dwPageSize)
      console.log("lpMinimumApplicationAddress:", lpMinimumApplicationAddress)
      console.log("lpMaximumApplicationAddress:", lpMaximumApplicationAddress)
      console.log("dwActiveProcessorMask:", dwActiveProcessorMask)
      console.log("dwNumberOfProcessors:", dwNumberOfProcessors)
      console.log("dwProcessorType:", dwProcessorType)
      console.log("dwAllocationGranularity:", dwAllocationGranularity)
      console.log("wProcessorLevel:", wProcessorLevel)
      console.log("wProcessorRevision:", wProcessorRevision)
    }
  }
})

CModule

通过CModule API,我们可以传递一串C代码,并在内存中将其编译为机器代码.但需要注意的是该功能在tinycc下编译,因此有一定的局限性.

CModule语法如下:

1
new CModule(code[, symbols, options])
  1. 目标进程,测试代码如下:
1
2
3
4
5
6
7
8
9
#include <iostream>
#include <Windows.h>

int main(int argc, char** argv)
{
	SYSTEMTIME localTime;
	GetLocalTime(&localTime);
	return 0;
}
  1. 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
const source = `
    #include <stdio.h>

    typedef unsigned short  WORD;
    typedef struct _SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;

  void PrintSystemTime(const SYSTEMTIME* pTime) {
	printf("Current local time is: %04d-%02d-%02d %02d:%02d:%02d.%03d\n",
		pTime->wYear,
		pTime->wMonth,
		pTime->wDay,
		pTime->wHour,
		pTime->wMinute,
		pTime->wSecond,
		pTime->wMilliseconds);
}
`

const cmMod = new CModule(source)
//console.log(JSON.stringify(cmMod))

var printSystemTimeAddr = cmMod.PrintSystemTime
var printSystemTimeFunc = new NativeFunction(ptr(printSystemTimeAddr), 'void', ['pointer']);

var getLocalTimeAddr = Module.getExportByName("kernelbase.dll", "GetLocalTime")
Interceptor.attach(getLocalTimeAddr, {
  onEnter(args) {
    this.arg0 = args[0]
  },
  onLeave(retval) {
    printSystemTimeFunc(ptr(this.arg0))
  }
})

Stalker

Stalker是一个代码跟踪引擎,可以跟踪线程并捕获调用的每个函数、代码块和指令.其语法如下:

1
Stalker.follow([threadId, options])
  • threadId:要跟踪的线程ID,可通过Process.enumerateThreadsSync()或函数内部的this.threadId获取

  • options:用于启用事件跟踪的选项

1
2
3
4
5
6
7
events: {
    call: true, // CALL instructions: yes please
    ret: false, // RET instructions
    exec: false, // all instructions
    block: false, // block executed: coarse execution trace
    compile: false // block compiled: useful for coverage
  }
  1. 目标进程,测试代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	FILE* file = fopen("code.dat", "r+");
	if (file == nullptr) {
		printf("File opening failed\r\n");
	}
	else
	{
		printf("File opened successfully\r\n");
	}
	return 0;
}
  1. 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
var mainPtr = Process.mainModule.base.add(0x16F960)
var modBase = Process.mainModule.base
//console.log("modBase:", modBase)

var moduleName = "luoDst.exe"
Interceptor.attach(mainPtr, {
  onEnter(args) {
    Stalker.follow(this.threadId, {
      events: {
        call: true,
        ret: false,
        exec: false,
        block: false,
        compile: false,
      },
      onReceive: function (events) {
        var eventsData = Stalker.parse(events, {
          annotate: true,
          stringify: true
        });
        for (var idx in eventsData) { // 调用的流程,地址1是哪里发生的调用,地址2是调用到了哪里
          var dataSp = eventsData[idx];
          var addr1 = dataSp[1];
          var addr2 = dataSp[2];
          try {
            var module1 = Process.getModuleByAddress(ptr(addr1));
            if (module1.name.indexOf(moduleName) != -1) {
              var module2 = Process.getModuleByAddress(ptr(addr2));

              console.log(module1.name + "!" + ptr(addr1).sub(module1.base) + " -> " + module2.name + "!" + ptr(addr2).sub(module2.base))
            }
          } catch (err) {
            //console.log(dataSp);
          }
        }
      },
      onCallSummary: function (summary) {
        //console.log(JSON.stringify(summary, null, 4));
      },
    })
  },
  onLeave(retval) {
    Stalker.unfollow(this.threadId)
  }
});

函数调用关系跟踪

  1. 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
var mainPtr = Process.mainModule.base.add(0x16F960)
var modBase = Process.mainModule.base
//console.log("modBase:", modBase)

var moduleName = "luoDst.exe"
var moduleStart = mainPtr;
var moduleEnd = modBase + 0x1000;
var preRegs = {}

function GetDiffRegs(context, preRegs) {
  //preRegs是调用之前的寄存器
  var diffRegs = {};
  // console.log(Object.keys(JSON.parse(JSON.stringify(context))));
  for (const [regName, regValue] of Object.entries(JSON.parse(JSON.stringify(context)))) {
    if (regName != "pc" && preRegs[regName] !== regValue) {
      preRegs[regName] = regValue;
      diffRegs[regName] = regValue;
    }
  }
  return diffRegs;
}

Interceptor.attach(mainPtr, {
  onEnter(args) {
    Stalker.follow(this.threadId, {
      events: {
        call: true,
        ret: false,
        exec: false,
        block: false,
        compile: false,
      },
      transform(iterator) {
        let instruction = iterator.next();
        do {
          const startAddress = instruction.address;
          const isModuleCode = startAddress.compare(moduleStart) >= 0 &&
            startAddress.compare(moduleEnd) === -1;
          //在模块起始地址才打印
          if (isModuleCode) {
            // console.log(startAddress, instruction);
            //指令执行之后调用,context寄存器上下文
            iterator.putCallout(function (context) {
              var pc = context.pc;
              var module = Process.findModuleByAddress(pc);
              if (module && module.name.indexOf(moduleName) != -1) {
                var diffRegs = GetDiffRegs(context, preRegs);

                console.log(module.name + "!" + ptr(pc).sub(module.base), Instruction.parse(ptr(pc)), JSON.stringify(diffRegs));
              }
            });
          }
          iterator.keep();
        } while ((instruction = iterator.next()) !== null);
      }
    })
  },
  onLeave(retval) {
    Stalker.unfollow(this.threadId)
  }
});

指令跟踪

参考链接

Frida HandBook

Frida JavaScript API


相关内容

0%