我正在开始我的第一次逆向工程冒险。我想我选择了一块相当密集的牛排开始。
我有三个 C 程序,它们使用未知的 IPC 协议通过 TCP 套接字进行通信。
可能被描述为“目标”的 Linux armel 二进制文件。
一个 Linux x86 二进制文件,提供“shell”或 REPL,将标准输入上的文本命令转换为针对目标的 IPC I/O。这是一个很小的 50KB 二进制文件(可能是因为它是用
-O3;D编译然后剥离的)。第二个 Linux armel 二进制文件,向目标呈现前端/UI,并包含 REPL 中没有的其他 IPC 相关功能。
我的第一个目标是通过反汇编 shell/REPL 控制程序来了解 IPC 线格式的基础知识。控制计划的规模很小,令人鼓舞。
完成此操作后,我想深入研究仅在前端程序中可用的其他 IPC 相关功能,该功能稍大一些(并且在 UI 代码的形式中存在干扰)。
我有一个非常有希望的启动环境:我已经在 QEMU 中运行了两个 armel 程序(目标,很容易;控制程序,经过很多努力)。x86 二进制文件可以在我的主机上轻松运行。
我还编写了一个不稳定但功能强大的脚本的仓鼠舞,这些脚本在两个 armel 程序(顺便运行在单独的 QEMU 虚拟机中,为了操作简单!)之间路由流量,以便我可以随时拦截流量。
所以我......看了一下协议,不知道如何进行:
21:37:21.839965 UI->目标 08 00 00 00 01 00 00 01 00 00 00 00 03 00 00 00 .... .... .... ....
UI->目标 01 00 00 00 ....
21:37:21.892845 目标-> UI 04 00 00 00 01 00 00 01 00 00 00 00 3a 03 00 00 .... .... .... :...
21:37:21.930144 UI->目标 08 00 00 00 06 00 00 01 00 00 00 00 6c 70 70 61 .... .... .... lppa
UI->目标 00 00 00 00 ....
21:37:21.991124 目标-> UI d0 02 00 00 06 00 00 01 00 00 00 00 .... .... ....
我顺便知道“lppa”是什么意思——但是这个小片段取自一个 4,000 行的协议转储,它清楚地表明其他字节在重要方面发生了变化,但我没有经验从这些变化中辨别出模式或重要性.
我立即想到的唯一一件事是怀疑我正在查看直接转储到网络上的 C 结构。对于该程序的过去和所做的事情,这很有意义(出于所有意图和目的,它是一个不需要特别有弹性的内部 IPC 协议)。
所以,我启动了 Ghidra,这是我不久前了解到的。最后,IDA Pro 有一些竞争:)
我花了一些时间在反编译器中漫无目的地闲逛,并很快找到了以下有趣的函数,它可以同时执行recv()和send().
但是这个函数的工作方式既有趣又令人困惑。
我在下面突出显示了一些行,这些行buffer_length确实让我头疼。
当我第一次看到周围的代码时,buffer_length我碰巧在看第二次send()调用,因此重命名该变量以描述“长度”感觉很自然。
但后来我看了第一个send(),发现它buf被指向了......是的,不,那不起作用。
所以我现在想知道这是否真的是一个union。耶。
(结构体定义见下文)
uint 也许_perform_ipc(astruct *something_struct) {
字符 *缓冲;
uint return_code;
ssize_t 字节数;
size_t message_length;
字节数 = 0;
返回代码 = 0x30040000;
something_struct->only_used_once_and_set_to_256 = 0x100;
消息长度 = 12;
buf = (char *)&something_struct->buffer_length;
尽管 (
消息长度 > 0 &&
(bytecount = send(something_struct->socket, buf, message_length, MSG_NOSIGNAL)) > 0
){
message_length -= 字节数;
buf += 字节数;
}
如果(字节数> -1){
if (something_struct->buffer_length != 0) {
message_length = something_struct->buffer_length;
buf = (char *)something_struct->buffer;
尽管 (
消息长度 > 0 &&
(bytecount = send(something_struct->socket, buf, message_length, MSG_NOSIGNAL)) > 0
){
message_length -= 字节数;
buf += 字节数;
}
if (bytecount buf = (char *)&something_struct->buffer_length;
尽管 (
消息长度 > 0 &&
(bytecount = recv(something_struct->socket, buf, message_length, 0)) > 0
){
message_length -= 字节数;
buf = buf + 字节数;
}
if (bytecount > -1 && message_length may_status_code == 0) {
my_free_then_realloc((astruct_4 *)something_struct, something_struct->buffer_length);
如果(something_struct->buffer_length != 0){
message_length = something_struct->buffer_length;
buf = (char *)something_struct->buffer;
尽管 (
消息长度 > 0 &&
(bytecount = recv(something_struct->socket, buf, message_length, 0)) > 0
){
message_length -= 字节数;
buf = buf + 字节数;
}
如果(字节数maybe_status_code | 0x30060000;
}
}
}
返回返回代码;
}
偏移长度类型名称 0x0 0x4 int可能_signature 0x4 0x1 未定义 0x5 0x1 未定义 0x6 0x1 未定义 0x7 0x1 未定义 0x8 0x1 未定义 0x9 0x1 未定义 0xa 0x1 未定义 0xb 0x1 未定义 0xc 0x4 int 套接字 0x10 0x4 uint buffer_length 0x14 0x2 undefined2 may_function_code 0x16 0x2 undefined2 only_used_once_and_set_to_256 0x18 0x2 ushort 也许_status_code 0x1a 0x1 未定义 0x1b 0x1 未定义 0x1c 0x4 单位 0x20 0x4 无效 * 缓冲区 0x24 0x4 undefined4 may_pid
我不知道如何让 Ghidra 将上述内容导出为结构体,所以我只是在 Ghidra 展示时将它转储到这里,因为它的可读性很好(我不知道如何将其重写为C 结构我自己,实际上:/)。
看看buffer_length,如果我了解联合如何正常工作,功能代码和状态代码(从其他地方猜测识别)将是您想要从 C 结构中转储到网络数据包中的那种东西。
另外,如果您返回并查看设置message_length为 12的代码,则范围从buffer_length向下延伸到undefined当前位于 下方的两个s maybe_status_code。
不幸的是,我不知道如何告诉 Ghidra“去找到我使用这两个定义的地方!” 来自结构编辑器,我不知道如何告诉 Ghidra“在反编译代码中搜索”field_0x1a和field_0x1b,所以这两个现在仍然是个谜。(我也尝试将范围转换为子结构,但随后它在结构编辑器中消失了。)
无论如何,我的观点,我的问题是......
我应该朝哪个方向前进,这样我才能在这个剖析中取得进展并获得足够的背景信息来理解我所看到的?
静态分析被证明非常酷和有趣,但这只是因为我可以反编译为 C。Ghidra 不提供调试器集成 :'(,所以我不能启动 shell 程序并看着它滴答作响,除非我想盯着在 GDB 中组装。
我并不是不愿意这样做,我只是觉得 asm 真的很迷惑。我必须承认我一直在尝试捡起它大约......哇,我想现在已经 15 年了,呵呵。(我发现的所有教程要么非常严格(以至于我无法将细节拼凑在一起),假设我是植物人(...8086?呃....),或者尝试教我开始处理 6 个球来玩杂耍。)
现在已经很晚了,所以我现在将这个问题留在这里,稍后可能会添加/澄清细节。
一个关键点:我正在逆向工程的程序是专有的,但它的状态是“如果你知道要看什么石头,它就会在互联网上悄悄地漂浮”,所以我可以私下分享更多细节(顺便说一下,我的电子邮件在我的简历中) .