攻防世界赛题分析
大概花了六周左右的时间,刷了攻防世界的pwn题,刷到了第二名(有图为证),做的有几道题flag都拿到了,提交入口给关了,交上去的话应该第一了,本来想接着刷的,但连续遇到几次libc和赛题平台的问题,加上这个平台主要偏新手入门,而且我后面还有别的任务,种种原因,后面应该不会再在上面刷pwn了,要刷也到别的地方了(CTFCollege、BUUCTF)
排名截图(截止2022.07.21)
1000levels
-
函数返回会恢复战帧,但是不会去破坏栈上内容,导致system函数地址仍然留在栈上
-
并列函数的EBP可能是相同的
-
提示我们如果源码看不出利用门道,可以看更细粒度的汇编
-
利用vsyscall绕过PIE保护
-
调用int 0x80/syscall+调用号时,为了保证数据的隔离,需要把当前的上下文(寄存器状态等)保存好,然后切换到内核态运行内核函数,然后将内核函数返回的结果放置到对应的寄存器和内存中,再恢复上下文,切换到用户模式,这一过程需要耗费一定的性能。对于某些系统调用,如gettimeofday来说,由于经常被调用,为了提高效率,系统把几个常用的无参内核调用从内核中映射到用户空间中,这就是vsyscall
-
Vsyscall用于系统调用,它的地址固定在0xffffffffff600000-0xffffffffff601000,在内核中实现
-
几个常用的vsyscall:
gettimeofday: 0xffffffffff600000 time: 0xffffffffff600000 getcpu: 0xffffffffff600000
-
如果直接调用vsyscall中的syscall时,会提示段错误,因为vsyscall执行时会进行检查,如果不是从函数开头执行的话就会出错
-
4-ReeHY-main-100
- 整形溢出知识
-
计算机底层指令是不区分有符号和无符号的,数据都是以二进制形式存在 (编译器的层面才对有符号和无符号进行区分,产生不同的汇编指令)
-
整形溢出主要发生
- 类型转换的地方:包括范围大和范围小的转化,无符号和有符号的转换
- 未限制范围的加法减法:上界溢出分为0x7fff和0xffff,下界溢出分为0x0000和0x8000
-
常见参数返回值类型:atoi返回int,read和write的参数为size_t(无符号最大整数)
-
漏洞一:整形溢出导致栈溢出
- atoi返回int类型的size,输入-1(0xffff),在read的size这隐式转化为无符号类型,变成很大的一个数,可以输入0xffff个字节,造成栈溢出,布置pop_rdi调用puts来泄露地址,再返回主函数pop_rdi调用system_bin_sh
- 注意由于read后有memcpy,栈溢出如果使用'aaaa'覆盖指针会崩溃,因此需要在溢出时将memcpy的size设为0,避免崩溃
-
漏洞二:堆溢出
- delete时没有对id做限制,可以输入负数id,从而可以释放装有size的堆块,重新malloc便可获得写入权限,改写堆块size
- 改写堆块size后,可以溢出下一个堆块,修改presize和in_use位,构造unlink
- unlink后使得堆指针指向-0x24的位置,修改堆表,改写free的got为puts和system
-
漏洞三:double free
- free后没有清空指针,能够再次free,构造unlink
- 释放chunk1和chunk2,合并到topchunk,重新malloc返回chunk3
- 编辑chunk3,改写chunk2的pre_size和in_use,再次释放chunk2,触发chunk1的unlink,改写堆表,改写free的got为puts和system
format2
- 程序一般通过esp来寻址栈上变量
- 思路有两个:栈溢出和直接验证,直接验证不可能,那只有栈溢出
- 虽然checksec开了canary,但是关键的几个函数没有压栈canary
- memcpy(&a1, &a2, c),a1是int类型,最大占8字节,c最大12字节,刚好可以溢出4字节覆盖ebp
- 将ebp覆盖为bss上的input变量地址,两层函数返回后eip指向&input+4处,如果里面写入后门函数地址,即可get_shell
- payload为‘aaaa+p64(get_shell_addr)+p64(input_addr),注意get_shell要取后门函数中绕过check以后的地址
Aul
- lua是脚本语言
- os.execute('/bin/sh')
Noleak
-
思路
- got表不可写,没有show选项,不能泄露函数地址,NX未开启,考虑修改malloc_hook指向shellcode
- 关键在于写malloc_hook地址、写shellcode和其地址
- 由于没有leak,考虑unsorted bins attack写main_arena地址
-
漏洞
- edit时未限制size,可堆溢出
- free时未清空指针,可double free和UAF
-
利用:
- 首先构造unsorted bins unlink,获取向bss段的写指针,最后一步再写防止被覆盖
- 然后构造unsorted bins attack,向bss段上写入bins地址
- 利用第一步的指针在bss段上写shellcode,并通过partitial write将bins地址最低字节改为
\x10
即malloc_hook的地址 - 通过edit堆指针,将malloc_hook视作堆指针向其中写入shellcode地址
- 调用malloc
-
知识点:
-
main_arena
是malloc_state结构体的实例,在libc/malloc函数中实现,保存了fastbinsY、bins等关键变量 -
获取libc和main_arena的偏移:
- IDA查看malloc_trim函数,查找偏移地址
-
* 通过malloc_hook地址计算
```
main_arena_offset = ELF("libc.so.6").symbols["__malloc_hook"] + 0x10
```
-
unsorted bin attack
- 最后一个bin取出来时会执行
BK-fd=unsorted_bins(av)
,如果往BK写入targetaddr-16
,取出bin后会向target_addr写入unsorted_bin的值 - fd不影响可随便写,攻击完可能破坏Unsorted bins的结构
- 用来写入一个大地址,泄露libc,或者改循环值,或者改global_max_fast
- 最后一个bin取出来时会执行
-
unlink
- 条件:可以修改fd、bk,可以修改下一个堆块的presize/size_inuse,有一个指向堆头的指针
- 实现:
fd=target-0x18; bk=target-0x10
,target指针指向堆头,本堆块的presize=0; size_inuse=1
,下一个堆块的presize=size; size_inuse=0
- 效果:target指向target-24
-
NX:数据不可执行,堆栈bss没有执行权限
-
部分写:libc中某些字段位于同一内存页,相对偏移不变,只需要修改最低几个字节,调试可知
hacknote
-
UAF overlaps两种典型利用:新申请堆块的数据区和被释放堆块的控制区重叠;新申请堆块的控制区和被释放堆块的数据区重叠
-
变量名也是地址,指针也是地址,变量名也是指针
-
函数指针是关键利用点,要看清楚程序给的函数和参数是什么,篡改后要如何布置
-
linux符号:||(有一个执行对就停下)、|(管道符)、&&(有一个执行错就停下)、&(不管对错全部执行)
* system("system_addr||sh")
babyfengshui
-
如果程序用了alarm反调试,通过改写二进制禁用alarm:
sed -i s/alarm/isnan/g ./target
-
一定要注意32和64位!!!
-
泄露的地址本身是字节流,可以通过printf直接打印出字符串(字节流本身可以通过字符串输出),再用u64将字节流转化为数字
-
如果本地可以打但远程大不了:可能是给的libc不对,也可能是shell命令的问题
-
printf字符串指针替换,可以用来泄露got,堆块里的函数指针数据指针都是好东西,首先考虑能不能篡改
echo_back
-
思路
- printf格式化字符串漏洞,可泄露main、libc地址以及main函数返回地址的地址
- 考虑利用格式化字符串写返回地址,但printf限制了只能输入7字节的变量,不够写入地址
- 为了绕过7字节限制,考虑利用stdin FILE缓冲区往返回地址写内容
- 由于缓冲区字符数和用户读走的字符数不同,IO_read_ptr检测不通过,导致无法下一次无法正常调用scanf,需要利用getchar调整IO_read_ptr
-
利用
- 利用格式化字符串漏洞打印出libc_start_main的地址,计算libc基址、system地址、/bin/sh字符串地址、IO_stdin地址和stdin中IO_buf_base的真实地址
- 泄露当前函数返回地址即main函数地址,计算elf的基地址和pop_rdi的真实地址
- 通过泄露当前函数rbp的值,加上0x8得到main函数的返回地址
- 修改IO_buf_base:先通过read将buf_base的地址写到栈上,利用scanf读入格式化字符串,再利用printf将buf_base最低字节写为\x00,使buf_base指向IO_write_base
- 再次scanf时,会向IO_write_base往下写入内容,将IO_bus_base覆盖为main函数的返回地址,将IO_buf_end覆盖为main函数的返回地址+ROP的字节数,printf利用换行符跳过
- 循环调用getchar函数直至将IO_read_ptr改为同IO_read_end相等,至此可以正常使用scanf函数
- 调用scanf函数,往main函数返回地址处写入ROP
-
知识点
-
stdin/stdout/stderr三个文件流指针位于libc.so data段,指向的FILE结构体IO_stdin也位于libc.so中,即
_IO_FILE *stdin = (**FILE** *) &_IO_2_1_stdin_;
,通过libc获取地址stdin_libc=libc.symbols['_IO_2_1_stdin_']
-
IO_stdin结构体:
-
* 其中IO_buf_base指向缓冲区首地址,IO_buf_end指向缓冲区尾地址
* 读缓冲区时用IO_read\_\*,写时用IO_write\_\*
* _IO_read_base始终指向缓冲区的开始
_IO_read_end始终指向已从内核读入缓冲区的字符的下一个
_IO_read_ptr始终指向缓冲区中已被用户读走的字符的下一个
* 对于IO_read_ptr指针的检查:
```
/* 将_IO_read_end加上成功读取的字节个数 */
fp->_IO_read_end += count;
/* 如果现在指针中还有数据,下次调用scanf就直接返回 */
if (fp->_IO_read_ptr < fp->_IO_read_end)
return *(unsigned char *) fp->_IO_read_ptr;
```
-
get_char直接从缓冲区中读一个字符,如果缓冲区已有数据则不用输入字符,并使得fp->IO_read_ptr加1
-
scanf、printf是行缓冲的,读入字节数不够可以考虑打IO_stdin
-
如果程序开了PIE,不光需要通过libc_start_main等函数泄露libc基址
libc_start_main_libc=libc.symbols['__libc_start_main']
还需要通过返回地址的值获取main函数地址,泄露elf基址
-
通过ebp可以泄露栈上地址,往栈上特定位置写值
-
one_gadget不是通用的,对栈上有要求:
RCalc
-
思路:只有一开始固定申请堆块,并且没有free,不太可能是堆利用,在scanf处有栈溢出,并且实现了类似canary的保护机制(通过生成随机数保存在堆上,和栈上的做比较,出错则退出),因此需要通过堆溢出修改随机数值,从而栈溢出getshell
-
利用:
- 堆溢出修改canary
- 泄露函数地址,由于溢出输入函数是scanf,因此不能输入0x20,puts等got都用不了,只能用libc_start_main的got),同理plt也只能用printf,还有在调用printf函数时eax需要等于零(即canary置为0)
-
getshell
-
知识点:
- gets、scanf函数都不限制读入字符数
- scanf读到空格、回车、跳格键才会结束,因此需要用sendline
- for循环中read函数需要EOF符号才能退出
- 在调用printf函数时eax需要等于零,不然会报错(可以在ida中发现在调用函数之前大部分都将eax清零了),因为在调用完函数之后返回值会存放在eax
- 分析数字字符串转化生成、算法函数时,有时候只需弄懂输入输出就行
HMI跑马灯
-
知识点
- timeval结构体
struct timeval { __time_t tv_sec; */\* Seconds. \*/* __suseconds_t tv_usec; */\* Microseconds. \*/* };
- gettimeofday函数
gettimeofday(struct timeval *tv, struct timezone * tz) 把目前的时间用tv结构体返回,当地时区的信息则放到tz所指的结构中 时间是距Epoch的标准时间
-
for循环内部仅形式不同:当i++循环和++i循环在for循环内部,虽然形式上明显不同,但输出结果是一样的
-
如果远程题目设置了alarm,并且会对利用产生不利影响,可以通过rop再次调用alarm,并把时间设为0,达到取消alarm的效果
supermarket
- 思路
- realloc释放堆块后,没有重新写回指针,UAF
- 堆块内有指针,通过复用泄露和改写got
- 知识点
- 中括号表示以一定偏移访问内存
- 关于realloc函数
- 如果newSz=0,直接释放原来的指针
- 如果newSz<oldSz,切割堆块,剩余部分扔到对应bins,返回原指针
- 如果newSz>oldSz,释放原指针,并重新申请堆块,返回新指针
- 使用realloc一定要将返回的新指针覆盖原指针,否则可能导致漏洞
- 需要考虑使用fastbins还是unsorted bins,本题由于fastbins输入字节不够,所以使用unsorted bins来切割返回一个0x1c大小的堆块
- 需要注意write时不要把bins的fd和bk写错,不然下次malloc/free可能报错
easyformat
-
思路
- 本题没有用return返回,用的exit退出,考虑格式化字符串改写got
- 只有一次read和printf的机会,考虑覆盖exit got为txt段,可以循环执行txt代码
- exit got初始值和txt段代码初始值只有最低两个字节不同,因此可用hn部分覆盖
-
知识点
- 由于printf遇到'\x00'会截断,所以要把exit_got放后面,加上16字符(两个偏移)总共偏移为10,如下:
* %082c%10$hn.ljust(16, 'a') + p64(exit_got)
-
BYTE PTR叫属性修饰符,就是操作数为字节;相应的,WORD PTR操作数为字
mov eax, byte ptr [ebp-0x10]; #取ebp上方0x10处的一个字节值 jmp dword ptr ebx; #已ebx里的值作为指针,跳到指针指向值处执行
-
printf的参数只是一个字符串指针,利用要考虑实际位于栈上的字符串,计算字符串相对第一个参数的偏移
-
为了防止system指针覆盖多余值,最好使用
/bin/sh\x00
-
got不仅可以被覆盖为外部函数地址,还可以使用txt段函数地址
-
格式化字符串漏洞任意地址写时,如果写的值过大,可以将地址拆分为多部分,多次写,要注意取各个部分地址值、调整偏移、减去已写字节数
num1 = system_addr&0xff #先取最低1个字节 payload = '%' + str(num1) + 'c%14$hhn' num2 = ((system_addr&0xffffff)>>8) - num1 #再取2个字节 payload += '%' + str(num2) + 'c%15$hn' payload = payload.ljust(32, 'a') + p64(printf_got) + p64(printf_got + 1) p.sendafter('slogan: ', payload)
nobug
-
思路
- 关了NX和PIE,可以直接把shellcode写到bss或栈上执行,免去泄露函数地址
- 有个复杂的加密函数,推测为base64解密
- 主函数中没发现漏洞函数,但发现一个貌似未被调用的函数有snprintf格式化字符串漏洞
反汇编显示没调用,但看汇编发现虽然没通过call调用,但实际上通过修改esp加ret来调用了该函数,类似于ROP
字符串被保存到了堆上,因此这是一题非栈上的格式化字符串漏洞利用
- 利用
- 调用snprintf时,泄露偏移4处的主调函数的ebp值,将该值加4,得到上上个函数的返回地址
- 再次调用snprintf时,将计算得到的返回地址写到偏移4处的主调函数的ebp处,即在上上个函数的ebp处写入了返回地址,效果相当于将上上个函数的ebp指向了ebp+4处
- 两次函数返回后,rip由原来的ebp+4处指向了ebp+8处,而ebp+8处正好存放着shellcode的地址,再次返回后getshell
- 整个流程相当于将rip指向了rip+4处保存临时指针的地方
- 知识点
- 不要过于相信IDA反汇编,不要完全依赖主函数,没思路可以点点看其他函数,或者看看汇编
- 非栈上的格式化字符串漏洞利用:泄露rbp指针,给rbp加减偏移,通过改写rbp指针,借助leave ret改变控制流,调用栈上指针
250
-
系统调用
-
64位系统调用传参同普通函数,并且不再使用int 0x80来进行系统调用,取而代之的是syscall指令
-
32位系统调用通过ax传递调用号,参数传递顺序是:ebx,ecx,edx,esi,edi
-
-
get_shell
- 方式
system(“/bin/sh”) system(“sh”) execve("/bin/sh",NULL,NULL) 第一种是使用/bin下的sh,第二种是通过环境变量找sh,第三种是使用系统调用,还可以使用shellcode
- system是glibc中的函数,用shell来调用程序=fork+exec+waitpid
-
\x00截断
- 相较于 strcpy() 函数,memcpy函数遇到 \x00 将会继续复制,不发生 00 截断
-
获取/bin/sh的方式
- 程序自带
- libc自带
- 通过程序输入函数输入
- 通过构造read函数输入
house_of_grey
-
思路
-
read函数写buff变量溢出,可以覆盖下面的指针,造成任意地址读和任意地址写
-
保护机制全开,不能写got,只能考虑覆盖返回地址
-
execve调用被禁,只能考虑ROP直接读取flag文件
-
利用mmap申请了一块内存,并通过clone函数作为子进程的栈空间
-
提供了读取系统文件,设置偏移和打印的功能,可以利用
/proc/self/maps
文件泄露elf基址和mmap地址,利用/proc/self/mem
文件搜索标记泄露栈地址
-
-
利用
- 首先打印
/proc/self/maps
内存映射段地址,获取elf加载基址和mmap申请空间[addr1, addr2] - 利用
/proc/self/mem
获取内存值,lseek设置好偏移,在[addr2-offset, addr2]栈空间内搜索/proc/self/mem
字符串,获取字符串对应的栈上地址,加上一个偏移获得read的返回地址 - 溢出buf变量,覆盖指针为read返回地址
- 构造open->read->write(已打开文件描述符为6),向read返回地址处写入rop链
- 首先打印
-
知识点
- clone函数
int clone(int (*func)(void*),void *child_stack,int flags,void *func_arg,....); 用于创建子进程,第一个参数为子进程执行函数,第二个参数是为子进程函数分配的栈空间(即子进程esp指向该值)
-
mmap函数
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize); 参数start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。 参数length:代表将文件中多大的部分映射到内存。 参数fd:要映射到内存中的文件描述符 参数offset:文件映射的偏移量 返回值:若映射成功则返回映射区的内存起始地址
- 内存映射,就是将内核空间的一段内存区域映射到用户空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的
- mmap有两种用法:一种是文件映射,就是把一个文件的内容在内存里面做一个映像,可以像读取内存一样读取文件;另一种是匿名映射,单纯的分配一块内存
- mmap映射的内存空间在堆上面,libc下面,程序堆空间初始化也是通过mmap映射的
-
64位直接读flag的ROP,需要知道打开flag文件的描述符
rop = p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(pop_rsi) + p64(0) + p64(0) + p64(open_addr) rop += p64(pop_rdi) + p64(6) + p64(pop_rsi) + p64(read_ret + 15 * 8) + p64(0) + p64(read_addr) rop += p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(puts_addr) rop += '/home/ctf/flag\x00'
-
seccomp沙箱保护
-
seccomp(全称 secure computing mode)是linux kernel从2.6.23版本开始所支持的一种安全机制,利用prctl和seccomp沙箱来禁用系统调用
-
可以使用seccomp-tools来查询禁用
seccomp-tools dump ./file
-
如图被禁用execve,不能getshell,考虑直接通过ROP来cat flag
EasyPwn
- snprintf每次只处理一个字符,每次都去读取当前的格式化字符串
- main函数地址作为参数传入libc_start_main,在libc_start_main中调用了main函数,所以main函数的返回地址是libc_start_main的libc地址加上0xF0(调试确定偏移)
- 格式化字符串漏洞泄露libc_start_main位于栈上的地址,从而获取libc基址
cyberpeace{644aef611824c5295691726464c9ad32}
babyheap
-
思路
- 没有编辑功能,不能unlink攻击,考虑fastbin attack
- 由于没有编辑功能,要实现溢出只能先释放再malloc回来,同时写溢出
- 需要泄露地址,考虑泄露unsorted bin
- null off by one漏洞,可以溢出到下一个堆块的pre_size字段,置0伪造空闲堆块
-
利用
- 利用链:null off by one溢出preuse->伪造被释放堆块,UAF泄露main_arena->fastbin attack分配malloc_hook地址->realloc调整堆栈->one gadget弹shell
- 申请5个chunk,其中第1、2、5个是unsorted bin大小,3、4个是fastbin大小,释放1(目的是写入fd和bk,用于脱链),释放3(目的是后面构造fastbin attack),释放4(目的是重新申请回来可以溢出第5个堆块的preuse)
- 将第5个堆块的presuse置为0,并通过复用更改presize包含前面4个堆块,释放5,1堆块unlink脱链,5个堆块合并到unsorted bin
- 切割大堆块,刚好切割剩下的堆块2成为unsorted bin,此时fd和bk写入到了2,由于没有真正删除2,故可以调用show泄露fd指针
- 重新申请堆块,往3处写入malloc_hook-0x23地址,fastbin attack获取malloc_hook堆块
- 由于堆栈偏移问题,不能直接调用one_gadget,需要realloc来调整,往malloc_hook处写入realloc地址,往realloc_hook处写入one_gadget
-
知识点
-
堆块free和重新malloc后内容都不会清空
-
read函数不会\x00截止
-
通过移位和按位与可以获取每个字节
-
fd和bk都指向堆头
-
fastbin是一个范围,不是准确值
-
关于伪造释放unsorted bin,构造unlink:
- 可以将两个相隔较远的unsorted bin,通过unlink合并,这样中间的堆块或者fastbin都被置入了大的unsorted bin,造成堆块复用
- 可以切割大堆块,将fd、bk写入新堆块,泄露main_arena
- 可以覆盖中间的fastbin,造成fastbin attack
- unlink时会通过presize和size检查前后堆块是否空闲,需要绕过检查,但是不会检查presize和上一个堆块的size是否相等
-
realloc_hook、malloc_hook、main_arena差不多都挨在一块
-
关于realloc调整堆栈,构造one_gadget条件:
- realloc函数开始和结束分别push和pop,次数相同,若push次数减少则会压低堆栈,改变栈环境,这时one_gadget就会可以使用
- 将malloc_hook写为realloc_addr+2,将realloc_hook写为one_gadget
-
magic
-
思路
- 数组下标向负数越界的漏洞,可以使得IO_write_ptr每次减50,使其指向IO_FILE结构体,修改结构体实现任意地址读写
-
利用
- IO_write_ptr多次减50,使其指向IO_read_ptr,覆盖指针为atoi_got,实现地址泄露
- 考虑通过IO_write_ptr直接覆盖IO_write_ptr,实际上不能这么操作,只能通过IO_read_ptr来修改IO_write_ptr
- 控制IO_read_ptr指针,指向log_file,泄露IO_FILE结构体地址
- 控制IO_write_ptr指针,覆写IO_write_end,使其大于IO_FILE结构体某一偏移,保证下次能够写IO_FILE结构体
- 控制IO_write_ptr指针,使得
IO_read_ptr=IO_read_end
,使得IO_buf_base和IO_buf_end指向atoi_got - 再调用一次fread,将所有指针指向atoi_got,再次给IO_write_ptr-50
- 再次write时,覆盖atoi_got为system_addr
-
知识点
- fread最终会调用
_IO_new_file_underflow
,如果IO_read_ptr不小于IO_read_end,则会执行如下代码
fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base; fp->_IO_read_end = fp->_IO_buf_base; fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base;
- 想要改写IO_read_ptr,则控制IO_write_ptr,调用fwrite
- 想要改写IO_write_ptr,则控制IO_read_ptr>=IO_read_end,置IO_buf_base=target_addr
- 只有IO_write_ptr<IO_write_end时,才能成功写,每写一个字节,IO_write_ptr+1
- fread最终会调用
shell
- 溢出修改文件指针,指向/lib/ld-linux.so.2,从中读取
username:password
类似字符串,校验后get_shell
play
-
思路
- 条件竞争漏洞,多进程可以映射同一文件,可同时修改文件内容,相当于多进程可同时访问同一块内存
-
利用
- 开两个remote进程,当一个进程阻塞在读输入时,另一进程进入修改内存内容,相当于叠加两种攻击方式,可以赢得游戏
-
知识点
- 对于程序中使用文件时,应该加类似于锁的东西,使得当前程序获得文件的所有权后,其他的不能在那个程序运行期间获得文件的所有权
- 可以system+str_bin_sh拿shell,也可以gets+puts拿shell
puts_plt + pop_ret + puts_got + gets_plt + pop_ret + puts_got + pop_ret + puts_got+4
shaxian
- fastbinattack,考虑分配libc的main_arena,或者bss段的堆表
secret_holder
- 思路
- 申请三种固定大小堆块,0x61a80字节大小的堆块通过mmap分配内存,若要分配到堆区,需要释放后重新申请
- 释放后未清空指针,并且有bss段堆表,可编辑,考虑堆块重叠造成unlink,任意地址写
- 利用
- 申请0x61a80大堆块后释放
- 申请小、中堆块后释放,合并到topchunk,并留下两个指针
- 重新申请大堆块,切割topchunk分配到堆区,内部包含两个堆指针
- 在大堆块内伪造freed_chunk
- delete中堆块,触发unlink,控制堆表指针
- 改写free_got为puts_plt,调用delete,打印puts_got
- 改写free_got为system_addr,改写堆表指针2为binsh_addr,调用delete(2),get shell
- 知识点
- unlink只会检查其他堆块是否被释放,不会检查大小(甚至可以和fastbin合并)
- 如果没有泄露函数,则改写free_got指向puts_plt
- 看到堆表,编辑函数,就要想到unlink,控制堆表,就能控制指针,任意地址读写、任意参数调用
- UAF、Double Free要想到直接编辑、堆块重叠、堆块内部伪造等利用手段
- 小于128K的堆块调用brk扩大topchunk,超过的调用mmap在libc上映射,释放后会重置修改128K的分配阈值
houseoforange
-
思路
- read函数输入没有以\x00结尾,可以造成地址泄露
- 编辑功能对输入没有限制,可以造成堆块溢出
- 没有free函数,只能用houseoforange来释放topchunk
- 只能edit和show当前堆块
-
利用
- 修改topchunk size,申请大堆块,将topchunk放入unsorted bins,fd处写入main_arena+0x58
- 申请新堆块,unsorted bins被整理到large bin,fd->nextsize处写入当前堆块地址
- show新堆块,打印fd和fd->nextsize,泄露main_arena和堆地址
- 对切割后剩下的unsorted bin进行伪造,布置成IO_file_plus,一并写入vtable指针和system函数
- 利用unsortedbin attack,修改IO_list_all指向main_arena+0x58
- unsortedbin attack size检查报错,触发IO_all_flush_lock,对每个IO_file调用IO_overflow
- 对main_arena+0x58处的第一个IO_file,不满足write_ptr>write_base,跳过
- 第一个IO_file的chain指针指向下一个IO_file(此处算好small bin index在main_arena的偏移同chain偏移),即伪造的堆块
-
伪造IO_file满足条件,按照虚表指针找到IO_overflow函数,此时已被修改为system,第一个参数"/bin/sh"已被写入IO_file头部
-
知识
-
house of orange来释放topchunk的条件:
- 伪造的 size 必须要对齐到内存页(old_top+size要对齐0x1000)
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE(0x10)
- size 的 prev inuse 位必须为 1
-
unsorted bin检查流程
- 先取出来unsorted bins,如果不能精确匹配,则整理到largebin或smallbin
- 整理到largebin,如果是该idx的第一个堆块,则会将该堆的地址写入到fd->nextsize,从而泄露堆地址
- 然后再从largebin切割返回,剩下的部分扔到unsorted bins
-
smallbin和fastbin有交集
- 切割后的部分以及unsortedbin轮询后不会放到fastbin,只会到smallbin
-
查看IO_file结构体
p (struct _IO_FILE_plus)*
-
劫持_IO_list_all + FSOP
-