0x00 前言
通过三道格式化字符串的题目学习巩固格式化字符串漏洞的利用
这是[Hackme.inndy]的题目,在BUUOJ上也能找得到。
做这三道题目用了不少时间,仔细想想,还是因为最开始学格式化字符串的时候没学扎实,漏了挺多细节,导致题目打不通。
不过虽然用了不少时间,但是收获还是非常多的。
0x01 echo
这是三道题目里面最简单的一道。
分析
1 | Arch: i386-32-little |
很明显存在格式化字符串漏洞。
思路
由于是32位程序,可以直接利用pwntools的fmtstr_payload
模块构造payload,覆盖printf_got
为system_plt
,然后sendline('/bin/sh')
即可
EXP
1 | from pwn import * |
0x02 echo2
这道题相比第一道要有些难度了。
分析
1 | Arch: amd64-64-little |
可以看到程序开启了PIE且是64位程序。
所以要先泄露程序真正的加载地址,然后再覆盖printf_got
为system_plt
。
不过这里不能再用pwntools的fmtstr_payload
模块了,因为64位程序的地址容易出现’\x00’造成截断。
Do it!
泄露程序加载地址
可以在IDA中看到call printf
的地址为0x984,这是相对偏移,gdb下断在此处然后查看栈的情况如下:
能够看到0x7fffffffde48
存放着main+74
,0x7fffffffde58
存放着__libc_start_main+240
调试可以知道这两处分别对应%41$p
、%43$p
通过pie命令可以知道程序加载基址为0x555555554000
,这和我们的main+74正好相差0xa03,由此可以泄露出程序加载基址。
1 | payload = '%41$p---' + '%43$p...' |
实际上只需要泄露出elfbase即可,但是大多数wp都同时泄露了__libc_start_main进而泄露libcbase,然后通过libcbase+libc.dump(‘system’)来得到system地址
根据注释应该能看出来这是两种方法,通过测试这两种方法都是可以打通的
构造payload&getshell
这算是第一次接触64位的格式化字符串漏洞吧,之前打32位的时候一直用的fmtstr_payload
,现在没法用它,感觉少了些什么。
第一次打的时候是手动构造的payload,但是懒惰成为了第一生产力,我在网上搜了两个64位的fmtstr_payload
,其中一个完全不能用,还有一个这道题能用,但是别的题好像就不行了。放一下链接
无奈只能仿照着这俩自己写了一个,虽然也是有一定的局限性,还可能有bug,但我测了好几组样例,都正常,当然还有一些地方可以优化,但是太懒了,就懒得鼓捣了。
1 | def fmtstr_payload64(offset, src, data,type = 'byte'): |
然后覆盖就行了。
1 | payload = fmtstr_payload64(offset,printf,system) |
EXP
1 | #coding:utf-8 |
0x03 echo3
这道题的远程快给我打吐了。
应该是BUU上面环境没配置好,远程怎么都打不通,各种exp都试了也不行,但是远程打题目原平台hackme.inndy却能打通。。。
分析
第一次接触这类Fmt_bss
类型的格式化字符串题目。和前两道题目相比,这道题目
- buf在bss段
- main函数有一处调用了alloca,alloca的参数是个随机值,然后
sub esp, eax
使得esp被减一个随机的值,导致栈地址比较随机。
这两点我都是第一次接触,做起来用了挺长时间。
查阅了大量WP以后,得知这类型(Fmt_bss)的题目有一个套路,就是找跳板
,然后覆盖printf_got
为system
。再传入/bin/sh
找跳板的原因?
找跳板的原因?:对于%ac$xn
a为个数,x是偏移,如果此处地址里面存放的依旧是个指针(地址),那么会向里面存放的指针所指向的地方写入数据。
即 A->B->C,A和B都是地址,则最后数据会写到C处。
Do it!
写在前面
在开始之前,有一点需要了解:在打本地的时候,程序默认加载的是本地默认libc,但是打远程的时候,程序则加载远程默认libc。这道题目原题作者给了libc-2.23.so.i386
这个libc和我们正常的libc-2.23.so.i386
不太一样。但是使用不同的libc运行时,栈结构有差异,而我们找跳板正是利用了栈结构的一些特性,所以在本地调试的时候就需要通过指定libc,产生与远程相同的栈结构
。
遇到的困难 (未解决)//3月19号已解决。
非常不幸
,尽管我已经下载下来了作者提供的libc-2.23.so.i386
,但是我依旧没办法让程序优先加载这个libc,试了许多方法,包括设置LD_PRELOAD
、process('./echo3',env = {"LD_PRELOAD": "./libc-2.23.so.i386"})
、软链接
、(这么做之前一定要备份原有的libc)删掉/lib/i386-linux-gnu/libc-2.23.so和libc.so.6,然后用作者给的libc替换libc-2.23.so,然后再用软链接搞出来libc.so.6
这些方法都会导致各种各样的报错,比如段错误
、不能加载libc-2.23.so.i386
之类的。初步推测是这个libc-2.23.so.i386
有点问题?
如果能在本地通过加载作者给的libc来调试,就能得到准确的偏移,远程应该也就能打得通了。
但是重在学习fmt_bss类题目的打法,拿不拿得到flag就不是那么重要了,本文将写本地打法
思路
程序本身没有system
函数,所以需要先泄露libcbase,然后得到system函数。
同时也需要泄露栈地址,以便我们找跳板。
通过跳板修改printf_got
为system
传入/bin/sh
来getshell
泄露栈地址和libcbase
由于程序存在alloca
并且sub esp, eax
,会导致栈地址的变动。不过有一点非常有意思。
写个程序运算一下,可以发现alloca
的参数只有如下可能
['0x10', '0x20', '0x30', '0x40', '0x50', '0x1010', '0x1020', '0x1030', '0x1040', '0x1050', '0x2010', '0x2020', '0x2030', '0x2040', '0x2050', '0x3010', '0x3020', '0x3030', '0x3040', '0x3050']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 >0x20 4096 0.0330749354005
>0x1010 2048 0.0165374677003
>0x1050 2048 0.0165374677003
>0x1030 4096 0.0330749354005
>0x2010 2048 0.0165374677003
>0x30 4096 0.0330749354005
>0x50 2048 0.0165374677003
>0x40 4096 0.0330749354005
>0x3050 2047 0.0165293927649
>0x3010 2048 0.0165374677003
>0x2020 4096 0.0330749354005
>0x1040 4096 0.0330749354005
>0x1020 4096 0.0330749354005
>0x3020 4096 0.0330749354005
>0x10 2048 0.0165374677003
>0x2040 4096 0.0330749354005
>0x3030 4096 0.0330749354005
>0x3040 4096 0.0330749354005
>0x2050 2048 0.0165374677003
>0x2030 4096 0.0330749354005
>
从左到右依次是 16进制表示、10进制表示、出现概率。
我们可以通过多次尝试或者直接在exp里加入控制语句来让这个栈的减少值固定下来。
在此之前,先来调试一下,看看此时栈有什么特征,以便在exp加入控制语句。
gdb调试
1 | 08048774 sub esp, eax |
在这两处位置下断,执行到第一个断点的时候set $eax = 0x20
,使得esp=0x20,当然也可以选取别的值。
接着执行,到call printf
的时候stack 100
查看栈情况。
能够看到__libc_start_main + 247
,此时其偏移为43 可通过%43$p
来打印出来。可以发现其特征为后三位为637
,选取后三位是因为这三位是不变的,如果虚拟机开了ASLR,除了后三位以外,其他位置都有可能变。
通过这个能泄露出libcbase,但还需要泄露出栈地址,这里选择偏移为30的这一处
1 | from pwn import * |
找跳板
原理利用了之前讲到的A->B->C
由于刚才已经找到了位于30偏移的栈地址,其地址里存的是位于87偏移的地址,87偏移的地址里面存的是一些其他东西。
这道题比较巧,栈里面存在_GLOBAL_OFFSET_TABLE_
可以覆盖其低位字节来让其指向printf_got
。图中这两处偏移分别为20和21
所以跳板流程可以如下表示,为了看起来更舒服,这里用偏移来代替地址。。
这里_GLOBAL_OFFSET_TABLE_
表示的是存放_GLOBAL_OFFSET_TABLE_
的栈地址。。。
1 | -----第一步,利用30------- |
可以看出,每一步,对应A->B->C
中A和B位置的都是指针。
然而,这样由于printf反馈的数据太多了,大概会运行好几分钟。。。而如果打远程的话,恐怕没这么多时间,而且考虑网络因素,这样就算payload正确也很难打通吧
因此有了改进的payload,每次修改2字节,这样会大大降低数据量。
1 | ----第一步----借助30和31 |
第二步挺巧妙的
1 | 14:0050│ 0xffffcfd0 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f10 (_DYNAMIC) ◂— 0x1 |
可以看到0xffffcfd0和0xffffcfd0+4所存的地址都是0x804axxx
而正好_GLOBAL_OFFSET_TABLE_
为0x804a000
,不同函数got值前两字节是不变的,改变的只是后两字节
,因此我们才有了每次修改两字节的方法。
这里还有一个细节,我们来看system。
[+] system = 0xf7e3fda0
可以看出 system的前两字节0xf7e3
要小于后两字节0xfda0
,再想想我们fmt_payload的构造方法,就能得知要先修改值小的前两字节,然后再修改后两字节。
所以得到代码如下:
1 | got_table = addr-0x10c |
完整EXP
1 | #coding:utf-8 |
3月19日凌晨针对echo3的新更新
请教了一下大佬,关于之前为什么我没法指定题目给的libc来运行echo3,原来我姿势不对。
这里贴一下陈大佬的看雪文章陈老板的看雪文章
里面介绍了如何利用patchelf
这个工具来更换程序libc。
主要命令就是两条,以这道题目的echo3
和libc-2.23.so.i386
为例来说,我们还需要一个ld-2.23.so
!!!注意这个ld-2.23.so不能直接在/lib/i386-linux-gnu/ld-2.23.so取,这个会一直报段错误。。。被这个困扰了好长时间!多亏陈大佬提醒要去glibc all in one 里下载
然后我们通过patchelf来更换程序的libc
patchelf --set-interpreter ./ld-2.23.so ./echo3
patchelf --replace-needed libc.so.6 ./libc-2.23.so.i386 ./echo3
第一条指令是用来修改ld的,第二条指令是用来修改libc的。
改完以后是这样的。
本地又调了调,调好本地能打通了,但这在BUU上依旧没法打通,不过在hackme.inndy.tw上面打得通
只能说BUU上面的libc有些。。奇葩吧,拿不拿得到flag其实也就那样了,关键是通过这道题目学了好多东西,这要比拿再多的flag都要有意义。
和之前的exp相比,就改了一下改printf_got为system的顺序,因为换了libc以后,system的前两个字节要大于后两个字节。
exp:
1 | #coding:utf-8 |
3月19号下午针对echo3的新更新……
晚上做梦梦到好像有人告诉我这道题应该用LibcSearcher来弄libc…试了一下…果然可以!
1 | #coding:utf-8 |
libc选的这个
0x04 参考链接
和指定libc有关的参考链接