nofile
题目简述
题目开了canary保护,有一个栈溢出的漏洞,有一个printf的格式化字符串是%s,可以泄露地址。另外发现有一个函数vulfunc,可以读文件,题目描述也说了有一个叫flag的文件,但是题目在开始时限制了进程可打开的最大文件描述符的数量为0:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16unsigned __int64 init()
{
struct rlimit rlimits; // [rsp+0h] [rbp-20h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
if ( !getrlimit(RLIMIT_NOFILE, &rlimits) )
{
rlimits.rlim_cur = 0LL;
setrlimit(RLIMIT_NOFILE, &rlimits);
}
return __readfsqword(0x28u) ^ v2;
}
因此在跳转到该文件之前需要将文件描述符的限制去掉。可以再调用一次setrlimit函数修改文件描述符数量。rlimit结构体如下:1
2
3
4struct rlimit {
rlim_t rlim_cur; //soft limit
rlim_t rlim_max; //hard limit
};
因为题目开了canary,还需要泄露canary,跳转到某个函数还需要泄露程序加载基址。
另外因为发现程序没有”pop rdi;retn”的rop,read函数需要三个参数,但是程序本身就有一个输入的函数readstr,只需要两个参数。
总结利用思路如下:1
2
3
4利用格式化字符串漏洞泄露canary和程序基址。
利用readstr将需要传给setrlimit的rlimit结构体和需要传给vulfunc的参数“./flag”写到bss段上。
调用setrlimit函数去除文件描述符限制。
调用vulfunc读取flag文件。
exp
1 | from pwn import * |
babytcache
题目简述
这道题类似于pwnable.tw上的Tcache Tear,不同的是去掉了show函数无法通过正常的输出泄露libc地址,另外开了PIE,在不泄露程序基址的条件下无法在bss段上伪造堆。但是在IDA里可以看到程序在一开始在0xABCDA000处mmap了一段大小为0x2000的空间,并接受0x200的输入。
1 | ssize_t sub_96A() |
另外程序最大申请0x110的chunk,申请的次数没有限制,但是每次释放的chunk均为最后一次申请的chunk,释放的次数最多为8次。libc版本为2.27,有了tcache,所以可以用double free。
利用思路
(1) 伪造chunk:因为有了tcache,所以释放的chunk会先进入tcache中,按照原来Tcache Tear的思路,可以在0xABCDA000地址处伪造chunk,因为泄露libc要将其释放到unsorted bin中,所以要大于tcache中chunk的大小,因此伪造的chunk要至少为0x420,首先尝试了这种方法,但是发现泄露后delete的次数全部用完了,后面就没法再用tcache double free来将free_hook的地址修改为one_gadget。
但是在调试的时候发现在某一次申请chunk时某条tcache链的数量被覆写为255,其实是因为tcache在分配时没有检查数量,只根据操作来加减数字,当tcache的数量为0时,再从tcache里分配一个chunk,tcache的数量就变成了0xff(255)。此时再释放一个相同大小的chunk就不会进入tcache了。那就可以在第一次输入时就在0xABCDA000处伪造chunk并布置好对应位置的size越过检查,这样就省了两次delete。
1 | gdb-peda$ heapinfo |
(2)泄露libc:
没有show函数,可以利用_IO_2_1_stdout_来泄露,之前在hitcon2018的baby_tcache和国赛的bms出现过,具体可参考以下链接:
1 | http://myhackerworld.top/2018/11/20/tcache%E6%9C%BA%E5%88%B6%E7%9A%84%E5%87%A0%E9%81%93pwn%E9%A2%98/ |
总结一下利用思路:
1 | 在0xABCDA000处伪造大小为0x110的chunk,利用double free移植堆空间至该处。 |
exp
1 | #coding=utf-8 |
silent
题目简述
一个栈溢出的题目,没有libc,想到之前做过32位的利用return to dl resolve将某个函数的got表改为system,然后来get shell,但这次是64位,函数参数需要用rdi、rsi、rdx等寄存器来传参,另外重定位表.rel.plt、符号表.dynsym和.synstr字符串表的大小由0x10变成了0x18。除了正常在bss段上构造这些表外,如果伪造的r_info的值较大时,会导致无法读取内存引发错误,而绕过的方法是link_map+0x1c8处的值为0,而在函数传参时link_map就是GOT[1]的地址,程序一开始有一个赋值的操作,已经为我们构造好了这个条件,因此只要构造好rop就可以了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf; // [rsp+0h] [rbp-20h]
char v5; // [rsp+7h] [rbp-19h]
__int64 v6; // [rsp+10h] [rbp-10h]
__int64 *v7; // [rsp+18h] [rbp-8h]
read(0, &buf, 7uLL);
v5 = 0;
v7 = &qword_601008;
v6 = qword_601008;
*(_QWORD *)(qword_601008 + 0x1C8) = 0LL; //[GOT[1]+0x1c8] = 0
sub_400526();
return 0LL;
}
另外在构造时开始是在bss+0x800处构造的,但调试时会报一个错误,没有找到解决方法,后来又太高了栈顶,在bss+0x1000处构造的,然后就会成功。
总结一下利用思路:
1 | 劫持程序控制流至read函数,同时将system的参数“/bin/sh”pop给edx,最后返回到bss+0x1000处。 |
exp
1 | from pwn import * |