函数
我们接下来会展示如何使用Frida来追踪函数,修改参数,在目标进程内调用函数。
配置实验环境
创建文件 hello.c
:
使用下面的命令进行编译:
启动程序,然后记录下函数 f()
的地址 (本例子中地址为 0x400544
):
拦截函数
下面的脚本展示了如何在目标进程内拦截函数,并将函数的参数返回。创建一个文件
hook.py
:
把前面你获得的地址作为参数,运行该脚本 (本例中为 0x400544
):
该脚本会每秒向你发送一个新的信息,类似下面这样:
修改函数参数
接下来: 我们想修改目标进程中函数接收到的参数。创建一个文件
modify.py
:
运行该脚本来注入hello
进程 (应该还在运行):
此时,运行 hello process
的终端会停止计数,并一直输出
1337
, 直到你输入 Ctrl-D
来解除注入.
函数调用
我们可以使用Frida来调用目标进程中的函数。创建文件
call.py
:
运行该脚本:
然后仔细观察运行hello
的终端:
实验 2 - 字符串注入和函数调用
我们不仅能注入整形数,还可以注入字符串。而且如果你需要的话,你可以注入任何类型的对象。
先创建一个新文件 hi.c
:
和前面类似,我们先创建一个脚本 stringhook.py
,
该脚本使用 Frida 将字符串注入内存,然后以下面的方式来调用 f() 函数:
仔细观察 hi
进程的输出, 你会看到类似下面的输出:
使用类似的方法,比如 Memory.alloc()
和 Memory.protect()
来修改
进程内存。 使用 python 的 ctypes
库来实现注入, 以及其他内存对象,比如可以创建
structs
, 将其作为字节数组加载进内存,将后将其地址以指针的方式传递给函数。
恶意内存注入 - 案例: sockaddr_in 结构
任何一个做过网络编程编程的工程师都知道,C 中最常用的数据类型是
struct
.
下面是一个原生的程序,该程序创建一个网络套接字,通过 5000 端口来连接服务器,然后通过建立的连接发送字符串
"Hello there!"
这是个相当标准的代码,该代码将 IP 地址作为第一个参数传入。
如果你在一个终端内运行命令 nc -lp 5000
并在另一个终端内运行
./client 127.0.0.1
, 然后,你应该在 netcat 内看到信息出现,而且可以在
client
内返回信息。
现在,我们可以做一些有趣的事情。我们前面介绍了字符串注入和指针注入。这里我们同样可以做类似的事情来修改结构
sockaddr_in
,程序运行时会将该结构分割成若干部分:
有很多的在线资源介绍了 struct 的内存结构,如果你不熟悉,你可以自行查阅。
本例中,重要的比特是字节 0x1388
,
也就是 10进制的 5000. 而这正是我们的端口号(也就是十六进制下IP地址后的4个字节)。
如果我们将该值修改为 0x1389
,
我们就可以将客户端重定向到另一个端口。如果我们再修改接下来的4个字节,我们甚至可以完全的修改客户端访问的地址!
这里的脚本向内存内注入了恶意结构,然后劫持了
libc.so
内的
connect()
函数。
创建文件 struct_mod.py
:
请注意,该脚本意在展示 Module.getExportByName()
接口可以在目标进程内
,通过函数名来查询任何导出函数。如果我们提供了具体的模块,那么查找的速度会更快,尤其是在大型二进制文件上。不过这个不是本例子的重点。
现在运行 ./client 127.0.0.1
, 在另一个终端里运行 nc -lp 5001
,
然后在第三个终端里运行 ./struct_mod.py
.
一旦我们的脚本运行起来,在运行 client
的终端里按下 ENTER,
然后 netcat 应该会显示 client 发送的字符串。
目前为止,我们已经成功劫持了最原生的网络连接。我们用 Frida 先注入了我们自己的数据对象,然后拦截了目标进程,以及使用
Interceptor
来修改函数。
本例子展示了 Frida 的强大:不需要修补操作,复杂的逆向,也不需要花费无尽的时间在汇编分析上。
这里有一个视频,展示了以上的代码例子:
https://www.youtube.com/watch?v=cTcM7R872Ls