对应文件poc_leak.py。需要注意的是,此http post请求有5个参数,每个参数都有自己的偏移,但是一次poc_leak只能确定4个偏移,因为要用一个参数来存储格式化串来泄露信息。所以要想完整地获取五个偏移,得再运行一次poc_leak,但是需要先修改poc_leak.py,让另一个参数来存储格式化串。
导出sslmgr的core dump文件,能够知道sslmgr的进程内存布局:scp export core-file management-plane from * to root@11.11.11.11:/tmp
。
有了偏移,我们就可以读写进程的任意内存(前提是可读可写)。输入的是地址值,读用”%s”,写用”%n”。但不幸的是输入是有限制的。因为需要用http请求发送payload,所以不仅要处理’\x00’这个坏字符,还要处理’\x25’,’\x26’这两个坏字符。关于这三个坏字符的处理,大神作者的博客中给出了详细的说明:
需要说明的是,’\x00’和’\x26’这两个坏字符的处理是用格式化串来代替,分别对应’%10$c’、’%170$c’。10,170这两个数字不是随机生成的。10代表的是格式化串下面第10个偏移处,c代表只取最后一个字节。在栈上,’\x00’很容易找,但是’\x26’就没那么容易,一个最稳妥的方式就是自己制造一个’\x26’出来。大神作者就是自己在栈上写入了一个’\x26’,具体做法就是在栈上找到一个指向栈的指针,用’%n’写入’\x26\x26’,这样就可以用”%170$c”这种方式替换掉坏字符了,170是指针指向的内容在栈上与格式化串之间的偏移。
但是,我这个环境是x86_64,而且系统版本是8.0.5,反复调试分析发现,无法在栈上找到一个指向栈的指针。因此就无法用上面的方式替换掉’\x26’这个坏字符,造成的影响就是,只要地址中含有’\x26’这个坏字符,就无法泄露出来它的内容(如果’%s’能够跳过这个地址,是可以泄露它的内容)。
因此,我修改了一下大神作者的脚本,把对’\x26’坏字符的处理部分给去掉了。要不然,dump出来的内存根本不可用。
dump出来内存之后,可以通过readelf查看:
可以看到,能够识别elf的头部,以及program header,无法识别出来各个section。这很正常,因为elf此时是执行视图,而我们需要的是elf的链接视图。链接视图和执行视图分别用来反映ELF文件在磁盘中和被加载到内存中的情况。
在执行视图下,section table是可选的,而我们要找的plt,got都是section。经过分析,我们并不需要知道elf所有的section,理论上只需要知道plt的结构就行,可以通过plt来得知对应函数的got地址。但是实际上只知道plt是不行的,因为没法知道各个plt条目对应哪个函数。而函数名称的解析跟symtab、strtab两个section有关。
总的来说,我们只需要知道三个section就可以得到所有函数的plt地址、got地址,这三个section就是symtab、strtab、plt。三个section都在binary的头部,而且特征比较明显,可以通过对比正常的x64 binary,将三个section“抠”出来。
resolve_symbol_table.py用来完成解析三个section,得到各个函数的plt address和got address。其中需要特别说明的是symtab,这个section存储了一个Elf64_Sym数组,说明了这个symbol的所有信息。利用其中的st_info可以过滤掉一些不在plt中的symbol。
struct Elf64_Sym
{
Elf64_Word st_name; /* 4 Symbol name (string tbl index) */
unsigned char st_info; /* 1 Symbol type and binding */
unsigned char st_other; /* 1 Symbol visibility */
Elf64_Section st_shndx; /* 2 Section index */
Elf64_Addr st_value; /* 8 Symbol value */
Elf64_Xword st_size; /* 8 Symbol size */
};
通过分析binary可以得到strlen_GOT和system_PLT的值(这里的system不叫system,而叫pan_sys_system)。现在就把strlen_GOT的内容改为system_PLT的值,这样当调用strlen的时候,实际会调用system函数,能够执行任意命令。
具体过程如下:
因为目标进程是小端序,因此先将strlen_GOT的高地址处写入0(strlen_GOT+3)。之后将system_PLT的值,三个字节分为两次写入。完成之后就可以执行任意命令。
可以用sleep命令来验证是否执行了命令:python CVE-2019-1579_8.0.5_x64.py -i 192.168.1.88 -c "sleep 10"
完整的代码:https://github.com/longuan/CVE-2019-1579