###0x00 前言
被混淆PHP代码见的很多了,但以前比较懒总是用扩展直接decode掉,最近就想手动操作下看看不使用扩展怎么进行解密。
0x01 原理 一般来说PHP的混淆都会通过多次eval来还原并执行php代码,所以我们可以通过hook PHP的eval函数来打印其参数来解密代码。
0x02 环境配置 OS: ubuntu 18.04
PHP: 5.6.36
首先要安装PHP,此处安装的版本为5.6.36
1.安装相关的依赖库
1 2 sudo apt-get update sudo apt-get install libxml2-dev build-essential openssl libssl-dev make curl  libcurl4-gnutls-dev libjpeg-dev libpng-dev libtool-bin bison php-fpm 
2.编译安装libiconv
在libiconv官网 下载压缩包,放到/usr/local/src下,解压,编译安装,这里下载的是libiconv-1.15.tar.gz
1 2 3 4 5 6 sudo tar zxvf libiconv-1.15.tar.gz cd  libiconv-1.15sudo ./configure --prefix=/usr/local  sudo make sudo make install sudo ldconfig // 刷新动态链接库缓存 
注意,这里是将libiconv安装到了系统默认的lib目录下,安装路径/usr/local不可随意更改,否则后面会出现编译错误。另外,执行sudo make之后会有如下warning
1 warning: remember too run 'libtool --finish /usr/local/lib' 
按照warning的提示执行一下
1 libtool --finish /usr/local/lib 
3.编译安装PHP
去PHP官网 下载php5.6.36压缩包放到/usr/local/src下,解压,编译安装
1 2 3 4 5 6 sudo tar zxvf php-5.6.36.tar.gz cd  php-5.6.36sudo ./configure CFLAGS="-g"  CXXFLAGS="-g"  sudo make ZEND_EXTRA_LIBS='-liconv'  -j16 sudo make install 
4.补充下扩展的编译安装
在此处以zlib的编译安装为例,由于我们的PHP是编译安装的,所以在路径php-5.6.36/ext/zlib/下就有所需要的文件。如果要安装的扩展在php源码ext目录中没有,可以从PECL 上搜索你需要的扩展进行编译安装。
1.在对应的扩展目录运行phpize命令(如果没有提示autoconf记得apt安装)
1 2 3 4 5 zeroyu@ubuntu:~/Desktop/php-5.6.36/ext/zlib$ phpize  Configuring for : PHP Api Version:         20131106 Zend Module Api No:      20131226 Zend Extension Api No:   220131226 
2.运行configure命令
1 2 3 zeroyu@ubuntu:~/Desktop/php-5.6.36/ext/zlib$ ./configure checking for  grep that handles long lines and -e... /bin/grep checking for  egrep... /bin/grep -E 
3.运行make命令(之后可以运行也可以不运行make test)
4.运行make install命令
5.配置ini文件
通过运行 php --ini查找php.ini文件位置,然后在文件中添加extension=zlib.so
通过编译安装的PHP是没有php.ini文件的,但是可以通过php --ini查看配置文件的路径,例如:
1 2 3 4 5 zeroyu@ubuntu:~/Desktop/php-5.6.36/ext/zlib$ php --ini Configuration File (php.ini) Path: /usr/local /lib Loaded Configuration File:         (none) Scan for  additional .ini files in : (none) Additional .ini files parsed:      (none) 
可以看到此时Configuration File显示的是none。这种情况你只需要将之前在PHP官网下载的PHP文件中的php.ini拷贝一份到Configuration File (php.ini) Path路径下就可以了。操作完之后再执行php --ini可以看到如下显示:
1 2 3 4 5 zeroyu@ubuntu:~/Desktop/php-5.6.36/ext/zlib$ php --ini Configuration File (php.ini) Path: /usr/local/lib Loaded Configuration File:         /usr/local/lib/php.ini Scan for additional .ini files in: (none) Additional .ini files parsed:      (none) 
0x03 开始Hook PHP中的eval函数在Zend里需要调用zend_compile_string函数,我们先看下zend_compile_string函数的位置。
1 2 3 4 5 6 7 8 zeroyu@ubuntu:~/Desktop/php-5.6.36/Zend$ grep -rn "zend_compile_string"  * Binary file zend_alloc.o matches Binary file zend_API.o matches Binary file zend_ast.o matches Binary file zend_builtin_functions.o matches zend.c:693:	zend_compile_string = compile_string; Binary file zend_closures.o matches zend_compile.c:98:ZEND_API zend_op_array *(*zend_compile_string)(zval *source_string, char *filename TSRMLS_DC); 
我们发现zend_compile_string函数其实就是compile_string函数。所以我在这儿测试一个前几天偶然间得到的被混淆的PHP代码,如下所示可以看到在compile_string中已经获取到eval参数的值。
我们可以看到程序断下来后,compile_string的第一个参数source_string为php代码中eval函数的参数在Zend中的结构——即zval_struct。source_string.value.str.val即为参数的字符串形式。所以之后修改修改compile_string函数来打印eval的参数就可以得到解密后的代码了。
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 62 63 64 65 66 67 68 69 70 71 zeroyu@ubuntu:~/Desktop$ gdb php gdb-peda$ set  args a.php  gdb-peda$ b compile_string Breakpoint 1 at 0x46e73f: file Zend/zend_language_scanner.l, line 716. gdb-peda$ r Starting program: /usr/local /bin/php a.php  [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1" . [----------------------------------registers-----------------------------------] RAX: 0x5555559c2726 (<compile_string>:	push   rbp) RBX: 0x555555a68767 (<execute_ex>:	push   rbp) RCX: 0x7ffff7e10e48 ("/home/zeroyu/Desktop/a.php(1) : assert code" ) RDX: 0x7fffffffa030 --> 0x7ffff7fd22d0 ("return $\307\365㣋\217= eval(base64_decode($\240\202\206\345\215\331ׁ\307));;" ) RSI: 0x7ffff7e10e48 ("/home/zeroyu/Desktop/a.php(1) : assert code" ) RDI: 0x7fffffffa030 --> 0x7ffff7fd22d0 ("return $\307\365㣋\217= eval(base64_decode($\240\202\206\345\215\331ׁ\307));;" ) RBP: 0x7fffffff9fa0 --> 0x7fffffffa120 --> 0x7fffffffa270 --> 0x7fffffffa420 --> 0x7fffffffa440 --> 0x7fffffffa560 (--> ...) RSP: 0x7fffffff9e80 --> 0x7ffff7e10e48 ("/home/zeroyu/Desktop/a.php(1) : assert code" ) RIP: 0x5555559c273f (<compile_string+25>:	mov    rax,QWORD PTR fs:0x28) R8 : 0x7fffffff96c0 --> 0xb ('\x0b' ) R9 : 0x555555f2cd22 ("assert code" ) R10: 0x7  R11: 0xa ('\n' ) R12: 0x555555623210 (<_start>:	xor    ebp,ebp) R13: 0x7fffffffdfb0 --> 0x2  R14: 0x0  R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap  INTERRUPT direction overflow) [-------------------------------------code-------------------------------------]    0x5555559c272a <compile_string+4>:	sub    rsp,0x120    0x5555559c2731 <compile_string+11>:	mov    QWORD PTR [rbp-0x118],rdi    0x5555559c2738 <compile_string+18>:	mov    QWORD PTR [rbp-0x120],rsi => 0x5555559c273f <compile_string+25>:	mov    rax,QWORD PTR fs:0x28    0x5555559c2748 <compile_string+34>:	mov    QWORD PTR [rbp-0x8],rax    0x5555559c274c <compile_string+38>:	xor    eax,eax    0x5555559c274e <compile_string+40>:	mov    edi,0xf8    0x5555559c2753 <compile_string+45>:	call   0x5555559e67f3 <_emalloc> [------------------------------------stack-------------------------------------] 0000| 0x7fffffff9e80 --> 0x7ffff7e10e48 ("/home/zeroyu/Desktop/a.php(1) : assert code" ) 0008| 0x7fffffff9e88 --> 0x7fffffffa030 --> 0x7ffff7fd22d0 ("return $\307\365㣋\217= eval(base64_decode($\240\202\206\345\215\331ׁ\307));;" ) 0016| 0x7fffffff9e90 --> 0x8  0024| 0x7fffffff9e98 --> 0x555556280608 --> 0x0  0032| 0x7fffffff9ea0 --> 0x7fffffff9ed0 --> 0x7fffffff9f70 --> 0x7fffffffa180 --> 0x7ffff7fd6d08 --> 0x3d8f8ba3e3f5c724  0040| 0x7fffffff9ea8 --> 0x3d559e6d1a  0048| 0x7fffffff9eb0 --> 0x7fffffff9ed0 --> 0x7fffffff9f70 --> 0x7fffffffa180 --> 0x7ffff7fd6d08 --> 0x3d8f8ba3e3f5c724  0056| 0x7fffffff9eb8 --> 0x5555559e6891 (<_efree+78>:	leave) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, compile_string (source_string=0x7fffffffa030, filename=0x7ffff7e10e48 "/home/zeroyu/Desktop/a.php(1) : assert code" ) at Zend/zend_language_scanner.l:716 716	{ gdb-peda$ p *source_string $1  = {  value = {     lval = 0x7ffff7fd22d0,      dval = 6.953349167324743e-310,      str = {       val = 0x7ffff7fd22d0 "return $\307\365㣋\217= eval(base64_decode($\240\202\206\345\215\331ׁ\307));;" ,        len = 0x31     },      ht = 0x7ffff7fd22d0,      obj = {       handle = 0xf7fd22d0,        handlers = 0x31     },      ast = 0x7ffff7fd22d0   },    refcount__gc = 0xffffa060,    type  = 0x6,    is_ref__gc = 0x7f } 
0x04 小结     混淆代码的解密就是类似于代码执行。最终还是要执行PHP代码,而执行PHP代码的方法很多,除了eval函数还有assert、call_user_func、call_user_func_array、create_function等。这些函数的底层也是调用了zend_compile_string,所以也可以利用hook eval来还原混淆后的加密代码。
    这篇也就是对这个解密操作了一下记了个笔记,环境搭建还是比较繁琐的事,所以写的详细了点。了解归了解,现实中为了效率还是使用扩展比较方便,使用的扩展可以参考P牛 的,或者这个外国老哥 的。如果扩展需要编译的话,方法在上面说过了,编译后不想加入php.ini的话,可以在每次用的时候执行php -d extension=evalhook.so a.php。
0x05 参考 解密混淆的PHP程序–逢魔安全实验室 
phpjiami 数种解密方法—phithon 
PHPDecode 在线解密工具—Medici.Yan