[原创]2016 hctf ->fpwn
2016HCTF -->
https://github.com/zh-explorer/hctf2016-fheap
源码已公布,我们gcc 编译:
gcc main.c -pie -fpic -o pwn
执行程序,因为存在\n的原因,我们输入的字符必须少于一位

感觉程序的逻辑不复杂,算是 中规中矩的pwn吧
ida静态:
1 create:
首先刚进去就:
ptr = (char *)malloc(0x20uLL);
if ( nbytes <= 0x1000 ) //我们输入的size必须小于等于 0x1000
nbytesa = strlen(&buf);
if ( nbytesa > 0xF ) ;
当申请的size 大于0xf的时候:
dest = (char *)malloc(nbytesa);
为dest申请内存,然后:
strncpy(dest, &buf, nbytesa); //直接往dest重写东西
之后将dest的内存地址放到ptr的第一位
*(_QWORD *)ptr = dest;
ptr+3的位置放freeLong函数地址:
ptr+4的位置来存储str_size
*((_DWORD *)ptr + 4) = nbytesa;
接着:
if ( !*((_DWORD *)&Strings + 4 * i) )
{
*((_DWORD *)&Strings + 4 * i) = 1;
*((_QWORD *)&Strings + 2 * i + 1) = ptr;
printf("The string id is %d\n", (unsigned int)i);
break;
}
String全局变量,地址->.bss:00000000002020C0
String[i].inuse = 1
String[i].str = ptr <--存储的我们申请的string的写入地址空间
当申请的size小于0xF的时候:
strncpy(ptr, &buf, nbytesa);
ptr的第一位存储size
*((_QWORD *)ptr + 3) = freeShort;
ptr的第三位存储freeShort
总结:
1.当size大于0xf时:
string结构的结构:
struct string{
*buf; //1
freeLong; //3
size; //4
}
2.当size<=15时:
struct string{
*buf; //1
SIZE;
freeShort; //4
}
至此我们搞懂了这个程序的数据结构
看第二个delete:
*((_DWORD *)&Strings + 4 * v1) = 0; 将全局变量String里存储的inuse置为0,但是ptr却没有置为0,存在uaf
gdb调试一波:
当我建立三个string之后(前两个size->5 最后一个size->20)
溢出思路:
我们先申请两个个小于0xf的堆块,然后将其释放,之后申请0x20的堆块,这样子我们就可以在原来string1的位置开始写入数据了,首先覆盖掉free函数指针,改为我们的所需要控制的函数指针,便可以劫持程序执行流
但是因为程序开启保护pie,我们需先泄露程序基地址,然后才可以去的libc中的函数地址
因为低三位不变得原因,我们将低三位覆盖进去就行(是call puts函数得低三位):
readelf -a pwn | grep -E "free"
objdump -d -M intel pwn | grep puts
dbf: e8 9c fb ff ff call 960 <puts@plt>
objdump -d -M intel pwn | grep printf
beichen@ubuntu:~/桌面/19lanmao/uaf/2016hctf$ objdump -d -M intel pwn | grep printf
00000000000009a0 <printf@plt>:
d96: e8 05 fc ff ff call 9a0 <printf@plt>
df7: e8 a4 fb ff ff call 9a0 <printf@plt>
ef1: e8 aa fa ff ff call 9a0 <printf@plt>
f3d: e8 5e fa ff ff call 9a0 <printf@plt>
10db: e8 c0 f8 ff ff call 9a0 <printf@plt>
beichen@ubuntu:~/桌面/19lanmao/uaf/2016hctf$
用这个0xd96得就行
再确定printf函数得参数偏移时:

于此处下断,进去后:

hex(0x7ffdc6278cb8 - 0x7ffdc6278c38) / 8 + 6
>>> hex(0x7ffdc6278cb8 - 0x7ffdc6278c38)
'0x80'
>>> 0x80/8
16.0
>>> 16+6
22
为printf函数得第22个,那便为格式化串得第21个字符
exp:

执行:

