###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