前言

之前 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/


CTF Re

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

MySQL JDBC 客户端反序列化
安装vld扩展查看opcode