前言
之前 De1CTF 的 web 扩展题,当时下载扩展 IDA 打开,然后看着反编译代码就懵了,不知道是个什么逻辑。
后来才知道题目是加入了简单的花指令来干扰 IDA 的分析,看了 writeup 之后打算自己动手解一下。
分析
分析工具:IDA 7.0
下载扩展:https://raw.githubusercontent.com/De1ta-team/De1CTF2020/master/writeup/web/mixtrue/docker.zip
用 IDA 打开,按照开发 PHP 扩展的经验直接定位到 zif_Minclude 函数,F5 反编译一下,结果看到:
void __fastcall zif_Minclude(zend_execute_data *execute_data, zval *return_value)
{
((void (__fastcall *)(zend_execute_data *, zval *, zend_execute_data *))l2)(execute_data, return_value, execute_data);
JUMPOUT(*(_QWORD *)&byte_1245);
}
这反编译出来的代码跟印象中的 PHP 扩展代码完全不一样,回去看反编译前的指令,看到一个红色错误:
zif_Minclude endp ; sp-analysis failed
说明这里可能存在花指令或者其他东西影响了 IDA 的分析,可以看到 IDA 看到 call l2,将 l2 当作了一个函数,然后认为函数返回之后的下一条指令是:
db 0EAh
这样一来反编译出来的代码就完全错误了,而机器执行的代码还是正常的,所以这里的花指令可以自己看汇编代码理清流程来去除,像我这样对汇编、栈和函数调用不是很熟悉的人就只能用 gdb 调试 php 扩展来看了。
修改 php.ini 加载扩展,然后 gdb 调试,要提一点的是 php 加载 so 扩展是运行时加载,所以给扩展里的函数要下断点需要在调用该函数之前让 php 停下来,比如在前面加个 var_dump 然后给 php_var_dump 下个断点,再在运行触发断点的时候给该函数下断点。
看一遍就会发现,根源就在于 call 之后的地方:
pop rax
add rax, 8
push rax
这里 pop 出来的其实是 call l2 之后的返回地址,即 0x245,而修改之后再入栈则相当于将返回地址改为了 0x24D,而 IDA 静态分析不会发现返回地址被更改了,所以还是 0x245 的错误地址。
所以要去除这里的花指令,就是将 0x22E 到 0x24D 的指令都 nop 掉,然后 undefined,重新创建函数。
这时会发现另一个问题:
.text:00000000000012E2: The function has undefined instruction/data at the specified address.
定位过去看看,是一段无法识别的跳转指令,因为它的操作数需要四个字节的数据作为跳转的地址,而后面的数据已经被识别为了 next 标签的指令,这里我们将它直接 nop 掉,然后再创建。
然后发现,另外一个地址出现了同样的错误,阅读汇编代码之后会发现是个相同的花指令,去除之后再创建,可以发现创建成功了,再 F5 反编译:
void __fastcall zif_Minclude(zend_execute_data *execute_data, zval *return_value)
{
zval *v2; // r12
unsigned __int64 v3; // rsi
FILE *v4; // rbx
__int64 v5; // rax
void *src; // [rsp+0h] [rbp-98h]
size_t n; // [rsp+8h] [rbp-90h]
char dest; // [rsp+10h] [rbp-88h]
int v9; // [rsp+70h] [rbp-28h]
char *v10; // [rsp+74h] [rbp-24h]
v2 = return_value;
memset(&dest, 0, 0x60uLL);
v9 = 0;
v10 = &dest;
if ( (unsigned int)zend_parse_parameters(execute_data->This.u2.next, "s", &src, &n) != -1 )
{
memcpy(&dest, src, n);
php_printf("%s", &dest);
php_printf("<br>", &dest);
v3 = (unsigned __int64)"rb";
v4 = fopen(&dest, "rb");
if ( v4 )
{
while ( !feof(v4) )
{
v3 = (unsigned int)fgetc(v4);
php_printf("%c", v3);
}
php_printf("\n", v3);
}
else
{
php_printf("no file\n", "rb");
}
v5 = zend_strpprintf(0LL, "True");
v2->value.lval = v5;
v2->u1.type_info = (*(_BYTE *)(v5 + 5) & 2u) < 1 ? 5126 : 6;
}
}
可以看到已经是正常的代码了。
参考文章:
https://wizardforcel.gitbooks.io/re-for-beginners/content/Part-III/Chapter-50.html
https://www.52pojie.cn/thread-1068444-1-1.html
https://www.anquanke.com/post/id/208682
https://xuanxuanblingbling.github.io/ctf/pwn/2020/05/05/mixture/