高校战役CTF部分writeup

本文最后更新于:2020年3月20日 晚上

废话

打了两天的比赛,小做了几道题,不可谓不充实。虽然找不到工作,也要继续学习。

MISC

简单的misc

用010editor打开photo.jpg,在最底部看到zip格式,

image-20200311151648233

直接改后缀解压,得到摩斯密码,解密是flag.zip的密码,输入后得到base64加密的flag。

解密即可。flag{Th1s_is_FlaG_you_aRE_rigHT}

隐藏的信息

拿到一个残缺的二维码,看这个二维码很别扭,用Stegsolve.jar翻转了颜色之后,看上去舒服多了,然后补定位符,奈何手残,总是补不好,于是放弃,然后我再压缩包右击了一下7z解压,无意发现这个压缩包是假密码,拿到了一个wav文件,使用百度找了好多类似的题,用Audacity分析频谱发现开始和结束有问题,把音量调到最大,开始的部分还是听不到,但是最后可以听出是拨号音,(看过柯南,一下就猜出这是要猜电话号码),结合百度,才发现和DTFM有关。这里给出一篇参考文章:https://hebin.me/2017/09/10/%E8%A5%BF%E6%99%AEctf-beyond/

image-20200311153008620

然后可以得到一串数字187485618521,但是提交不对,感觉还是和那个二维码有关,

strings命令找到:image-20200311153154593

看来要base64加密,最后得出flag{MTg3NDg1NjE4NTIx}

ez_mem&usb

从来不刷杂项题,所以都是靠百度做,拿到一个数据pcap文件,分析流量,过滤http协议,找到上传的文件。

image-20200311153716210

发现一个40M的数据包,那肯定传文件了,把文件dump出来,直接解压,得到一个vmem文件,百度一波,这是要内存取证,萌新参考:https://www.cnblogs.com/0x4D75/p/11161822.html

思路是,filescan+grep找到和flag字符匹配的文件,还真有一个flag.img,将flag.img dump出来,然后直接7z解压,需要密码,于是找到内存镜像中的cmd命令,有密码给出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@redhat:/mnt/hgfs/P/高校战疫/misc/usb取证# volatility -f data.vmem --profile=WinXPSP3x86 filescan | grep flag
Volatility Foundation Volatility Framework 2.6
0x0000000001155f90 1 0 R--rwd \Device\HarddiskVolume1\Documents and Settings\Administrator\flag.img

root@redhat:/mnt/hgfs/P/高校战疫/misc/usb取证# volatility -f data.vmem --profile=WinXPSP3x86 dumpfiles -Q 0x1155f90 -D ./output
Volatility Foundation Volatility Framework 2.6
DataSectionObject 0x01155f90 None \Device\HarddiskVolume1\Documents and Settings\Administrator\flag.img

root@redhat:/mnt/hgfs/P/高校战疫/misc/usb取证# volatility -f data.vmem --profile=WinXPSP3x86 cmdscan
Volatility Foundation Volatility Framework 2.6
**************************************************
CommandProcess: csrss.exe Pid: 464
CommandHistory: 0x556bb8 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 2 LastAdded: 1 LastDisplayed: 1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x504
Cmd #0 @ 0x3609ea0: passwd:weak_auth_top100
Cmd #1 @ 0x5576d0: start wireshark
Cmd #13 @ 0x9f009f: ??
Cmd #41 @ 0x9f003f: ?\?????????

压缩包里是一个usbdata,查了一下这个脚本,解密即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
usb_codes = {
0x04:"aA", 0x05:"bB", 0x06:"cC", 0x07:"dD", 0x08:"eE", 0x09:"fF",
0x0A:"gG", 0x0B:"hH", 0x0C:"iI", 0x0D:"jJ", 0x0E:"kK", 0x0F:"lL",
0x10:"mM", 0x11:"nN", 0x12:"oO", 0x13:"pP", 0x14:"qQ", 0x15:"rR",
0x16:"sS", 0x17:"tT", 0x18:"uU", 0x19:"vV", 0x1A:"wW", 0x1B:"xX",
0x1C:"yY", 0x1D:"zZ", 0x1E:"1!", 0x1F:"2@", 0x20:"3#", 0x21:"4$",
0x22:"5%", 0x23:"6^", 0x24:"7&", 0x25:"8*", 0x26:"9(", 0x27:"0)",
0x2C:" ", 0x2D:"-_", 0x2E:"=+", 0x2F:"[{", 0x30:"]}", 0x32:"#~",
0x33:";:", 0x34:"'\"", 0x36:",<", 0x37:".>", 0x4f:">", 0x50:"<"
}

key = [0x09,0x0F,0x04,0x0A,0x2F,0x23,0x26,0x1F,0x27,0x27,0x25,0x20,0x22,0x24,0x25,0x21,0x08,0x06,0x20,0x08,0x07,0x25,0x07,0x1F,0x04,0x23,0x21,0x08,0x24,0x20,0x09,0x08,0x26,0x1E,0x20,0x06,0x27,0x30]
flag = ''
for i in range(len(key)):
flag += usb_codes[key[i]][0]
print(flag)

flag{69200835784ec3ed8d2a64e73fe913c0}

武汉加油

010editor打开图片,这可真是个好东西啊,两种不同的文件格式还分颜色。

image-20200319111152938

直接改后缀rar解压,

然后使用一个工具爆破出隐藏的flag.txt文件。

1
2
3
4
5
6
flag.txt文件隐藏到flag.jpg中:
steghide embed -cf flag.jpg -ef flag.txt -p 123456

flag.jpg解出flag.txt:
steghide extract -sf flag.jpg -p 123456

shell脚本爆破密码:

…..没成功,算了,不贴了。。

Reverse

天津垓

上来先运行程序,提示缺少cygwin1.dll文件,下载了之后运行还是报错,算了吧,静态分析看看。

找到了一个f函数,代码很简单,就一些简单的运算操作。

image-20200315160945833

感觉有戏,直接写脚本。

1
2
3
4
5
6
7
8
9
s1 = [0x52, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x48, 0x6f, 0x70, 0x70, 0x65, 0x72, 0x21]
s2 = [17, 8, 6, 10, 15, 20, 42, 59, 47, 3, 47, 4, 16, 72, 62, 0, 7, 16]
flag = ''
for i in range(18):
for j in range(255):
if s2[i] == (~(j & s1[i % 14])) & (j | s1[i % 14]):
flag += chr(j)
break
print(flag)

脚本跑出来是Caucasus@s_ability,提交也不对,我把这个函数出现的字符串都搜了一遍,知道了这是个假面骑士……没看过,我还把跑出来的字符串发给一个假面迷,我真是够了。看了看别的函数,发现有反调试特征:

image-20200315161644169

那肯定要动态调试的啊,一想又运行不了。我把函数都看一遍看看有什么信息。

image-20200315162052676

发现这里的Str和f函数中的Str是一个,分析了一下这个函数,是一段SMC(自修改代码)进行动态解密的。

使用IDAPython:

1
2
3
4
5
6
7
flag = "Caucasus@s_ability"
start = 0x10040164d
end = start + 1045
j = 0
for i in range(start, end):
ida_bytes.patch_byte(i, ida_bytes.get_byte(i)^ ord(flag[j % 18]))
j += 1

image-20200315162525217

也不是很复杂。。。。

1
2
3
4
5
6
7
8
9
10
11
s = [2007666,2125764,1909251,2027349,2421009,1653372,2047032,2184813,2302911,2263545,1909251,2165130,1968300,2243862,2066715,2322594,1987983,2243862,1869885,2066715,2263545,1869885,964467,944784,944784,944784,728271,1869885,2263545,2283228,2243862,2184813,2165130,2027349,1987983,2243862,1869885,2283228,2047032,1909251,2165130,1869885,2401326,1987983,2243862,2184813,885735,2184813,2165130,1987983,2460375]
print(len(s))
v61 = 19683
v62 = 0x8000000B
flag = ''
for i in range(51):
for j in range(255):
if s[i] == v61 * j % v62:
flag+=chr(j)
break
print(flag)

flag{Thousandriver_is_1000%_stronger_than_zero-one}

cycle_graph

描述是图算法,估计凉了,数据结构菜的一笔。

找到关键函数:

image-20200315163844983

这里是对v1进行初始化,假设v1是个二维数组,每组有三个数据,根据代码可分析出有32组。

image-20200315164605229

这里就是主要算法。

然后最后判断flag:

image-20200315195856725

刚开始没注意v7的限制,直接写的脚本跑,

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 string

arr_1 = [0x00000034, 0x00000002, 0x0000002C, 0x0000002A, 0x00000006, 0x0000002A, 0x0000002F, 0x0000002A, 0x00000033, 0x00000003, 0x00000002, 0x00000032, 0x00000032, 0x00000032, 0x00000030, 0x00000003, 0x00000001, 0x00000032, 0x0000002B, 0x00000002, 0x0000002E, 0x00000001, 0x00000002, 0x0000002D, 0x00000032, 0x00000004, 0x0000002D, 0x00000030, 0x00000031, 0x0000002F, 0x00000033]
print(len(arr_1))
arr_2 = [0x0000001F, 0x00000002, 0x00000002, 0x00000001, 0x00000012, 0x00000007, 0x00000002, 0x0000001A, 0x0000000D, 0x00000004, 0x0000000A, 0x00000004, 0x00000015, 0x0000000E, 0x00000001, 0x00000000, 0x0000000E, 0x00000005, 0x00000007, 0x0000001C, 0x0000000C, 0x0000001C, 0x0000000F, 0x0000000F, 0x00000002, 0x00000010, 0x00000017, 0x0000001E, 0x00000017, 0x00000013, 0x00000009, 0x00000016]
print(len(arr_2))
arr_3 = [0x00000005, 0x00000001, 0x00000008, 0x00000007, 0x00000017, 0x00000009, 0x00000013, 0x0000001F, 0x00000017, 0x00000009, 0x0000000D, 0x0000000C, 0x0000001D, 0x0000000A, 0x00000018, 0x00000009, 0x00000018, 0x00000019, 0x00000009, 0x0000001A, 0x00000003, 0x00000016, 0x00000006, 0x00000011, 0x0000000D, 0x00000007, 0x0000000F, 0x00000014, 0x00000001, 0x00000010, 0x00000004, 0x0000000B]

v1 = [[0] * 3 for i in range(31)]

for i in range(31):
v1[i][0] = arr_1[i]
v1[i][1] = arr_2[i+1]
v1[i][2] = arr_3[i+1]

print(v1)

str1 = string.printable
print(str1)

v5 = 48
flag = ''
ii = 0
for i in range(16):
for j in str1:
if v1[ii][0] + v5 == ord(j):
flag += j
ii = v1[ii][1]
v5 = ord(j)
print(j)
break
elif v5 - v1[ii][0] == ord(j):
flag += j
ii = v1[ii][2]
v5 = ord(j)
print(j)
break
else:
continue
print(flag)

跑出来d8b0bae8jh52db/2,提交当然不对,后来看到了v7的限制。为什么v7会出现差异呢? 因为有时候加v5和减v5都满足,不知道走哪条路径。估计得用某个算法解,比完赛再学一下算法,所以我手动正着加倒着,硬是给写出来了。

image-20200315170104295

image-20200315170118029

满满的菜啊…….

此处可用广度优先搜索算法,算法学习具体参考https://blog.csdn.net/raphealguo/article/details/7523411

easyparser

这个题,我可是特别肝的,知道是虚拟机,但是我也没遇到过这种题,直接动态调试,我一步一步跟出来的程序逻辑,还好不是特别复杂,也就调了6个小时左右吧。。。。先给出解密脚本,然后再学习一下正确的解法。

1
2
3
4
5
s = [144, 332, 28, 240, 132, 60, 24, 64, 64, 240, 208, 88, 44, 8, 52, 240, 276, 240, 128, 44, 40, 52, 8, 240, 144, 68, 48, 80, 92, 44, 264, 240]
flag = ''
for i in range(0x20):
flag += chr((s[i] >> 2) ^ 0x63)
print("flag{%s}" % flag)

flag{G0d_Bless_Wuhan_&China_Growth!}

fxck!

太累了,所以这道题随便看了一下,看到base58特征,直接解码,发现不对,一想其他题都不简单,这个应该也不会这么简单吧,所以就没考虑换表。比完赛看了看,还真是换表解密,我哭了。。。。

image-20200316152415784

这段是求余,我调了一下,发现不是直接把输入base58加密,还加了点东西,说实话,仔细分析,我没看懂这段伪代码,求得余数和我跑程序求得不一样,自闭了。

image-20200316152611658

换表和加密,不难。

然后用了brainfuck代码,第一次遇见,根本不知道是什么东西,但是最后的比较字符串可以直接动调dump出来。因为是复现,也简单学习了一下这个加密。

image-20200316152754240

image-20200316152823755

注释中给出了这个加密的运算符。

既然又碰到了base58,就熟悉一下吧,go语言写脚本,参考:https://blog.csdn.net/qq_45828877/article/details/103997621

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
package main

import (
"bytes"
"fmt"
"math/big"
)

var base58_table []byte

func Encrypt(input []byte) {
bInt := big.NewInt(0).SetBytes(input)
fmt.Printf("输入字节的16进制表达:%x\n", bInt)
base := big.NewInt(58)
zero := big.NewInt(0)
mod := &big.Int{}
var result []byte
for bInt.Cmp(zero) != 0 {
bInt.DivMod(bInt, base, mod)
fmt.Printf("%x\n", mod)
result = append(result, base58_table[mod.Int64()])
}
ReverseByte(result)
fmt.Printf("base58加密结果:%s\n", result)
}
func Decrypt(input []byte) {
result := big.NewInt(0)
for _, b := range input {
index := bytes.IndexByte(base58_table, b)
result.Mul(result, big.NewInt(58))
result.Add(result, big.NewInt(int64(index)))
}
decodeByte := result.Bytes()
if input[0] == base58_table[0] {
decodeByte = append([]byte{0x00}, decodeByte...)
}
fmt.Println(decodeByte)
fmt.Printf("base58解密结果:%s", decodeByte)
}
func ReverseByte(input []byte) {
for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
input[i], input[j] = input[j], input[i]
}
}
func main() {
base58_table = []byte("ABCDEFGHJKLMNPQRSTUVWXYZ123456789abcdefghijkmnopqrstuvwxyz")
Encrypt([]byte("\x06" + "flag{63510cf7-2b80-45e1-a186-21234897e5cd}"))
Decrypt([]byte("4VyhuTqRfYFnQ85Bcw5XcDr3ScNBjf5CzwUdWKVM7SSVqBrkvYGt7SSUJe"))
}

image-20200316153024371

跑出来,发现第一个字节是个\x6……..

clock

比赛的时候分析出了源码,但是不知道是什么加密,无从下手。

后得知是LFSR(线性反馈移位寄存器),可参考:

LFSR具体参考:

https://www.anquanke.com/post/id/181811

https://xz.aliyun.com/t/3682

https://zhuanlan.zhihu.com/p/33920501

writeup和脚本参考:

http://ctf.njupt.edu.cn/382.html#clock

贴出逆向的源码(python3)和爆破脚本(go语言)吧。

程序主要源码:

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
v4 = xxx
v3 = xxx
v2 = xxx
result = []
for ii in range(0x100000):
v5 = 0
for jj in range(8):
v7 = v4 & 0x17FA06
i = 0
while v7:
i ^= v7 & 1
v7 >>= 1
v4 = (i ^ 2 * v4) & 0x1FFFFF
v9 = v3 & 0x2A9A0D
j = 0
while v9:
j ^= v9 & 1
v9 >>= 1
v3 = (j ^ 2 * v3) & 0x3FFFFF
v11 = v2 & 0x5E5E6A
k = 0
while v11:
k ^= v11 & 1
v11 >>= 1
v2 = (k ^ 2 * v2) & 0x7FFFFF
v13 = 2 * v5
v14 = v3
if v4 & 1:
v14 = v2
v5 = v14 & 1 ^ v13
print(hex(v5))
result.append(v5)
print(result)

爆破脚本:

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
package main

import "fmt"

func lfsr(R, mask1, mask2 int64, result *[100]uint8, len int) {
for x := 0; x < len; x++ {
for j := 0; j < 8; j++ {
i := R & mask1
lastbit := uint8(0)
for i != 0 {
lastbit ^= uint8(i & 1)
i >>= 1
}
R = (R<<1 ^ int64(lastbit)) & mask2
result[x] = result[x]<<1 ^ lastbit
}
}
}

var output = [100]uint8{95, 83, 107, 255, 209, 96, 188, 166, 230, 219, 223, 72, 150, 155, 169,
138, 126, 0, 91, 20, 19, 109, 82, 12, 249, 91, 39, 107, 104, 55, 207,
65, 155, 197, 204, 81, 76, 22, 83, 208, 215, 13, 254, 14, 43, 87, 29,
42, 161, 92, 2, 109, 110, 232, 201, 147, 19, 53, 216, 82, 144, 169,
34, 193, 106, 0, 253, 224, 7, 46, 24, 16, 226, 127, 164, 162, 54, 98,
144, 141, 182, 174, 252, 64, 130, 19, 163, 242, 176, 78, 79, 3, 19, 11,
160, 121, 149, 44, 53, 17}

func equalNumSum(rResult *[100]uint8, output [100]uint8, len int) (sum int) {
sum = 0
for x := 0; x < len; x++ {
for j := 0; j < 8; j++ {
if (rResult[x] & 1) == (output[x] & 1) {
sum++
}
rResult[x] >>= 1
output[x] >>= 1
}
}
//fmt.Println(sum)
return
}
func guessR(mask1, mask2 int64, restrict int) int64 {
var result = [100]uint8{}
len := 100
maxLen := int64(1 << restrict)
cmpMax := 0
rr := int64(0)
for r := int64(0); r < maxLen; r++ {
lfsr(r, mask1, mask2, &result, len)
equalNumSum := equalNumSum(&result, output, len)
if equalNumSum > cmpMax {
cmpMax = equalNumSum
rr = r
fmt.Println(rr, cmpMax)
}
}
fmt.Println(rr, cmpMax)
return rr
}
func brute_r1(r2, r3 [100]uint8) int64 {
var r1 [100]uint8
len := 1 << 21
var c uint8
for r := 0; r < len; r++ {
lfsr(int64(r), 0x17fa06, 0x1fffff, &r1, 100)
for i := 0; i < 100; i++ {
for k := 7; k >= 0; k-- {
s1 := (r1[i] >> k & 1)
s2 := (r2[i] >> k & 1)
s3 := (r3[i] >> k & 1)
z := s2
if s1 == 1 {
z = s3
}
c = c<<1 ^ z
}
if c != output[i] {
break
}
if i == 99 {
return int64(r)
}
}
fmt.Println(r)
}
return 0
}

func main() {
var r2Result = [100]uint8{}
var r3Result = [100]uint8{}
//r2 := guessR(0x2a9a0d, 0x3fffff, 22) //3324079
//r3 := guessR(0x5E5E6A, 0x7fffff, 23) //4958299
r2 := 3324079
r3 := 4958299
lfsr(3324079, 0x2a9a0d, 0x3fffff, &r2Result, 100)
lfsr(4958299, 0x5E5E6A, 0x7fffff, &r3Result, 100)
fmt.Println(r2Result)
fmt.Println(r3Result)
r1 := brute_r1(r2Result, r3Result)
fmt.Printf("flag{%x%x%x}", r1, r2, r3)
}

Mobile

GetFlag

比赛的时候觉得快出了,但是不会通信就很难受,复现一遍吧。

扔到JEB里看看。

image-20200319114048514

往一个文件里写了flag,这样的方式创建的文件会在应用的私有目录下。我们安装软件到手机上,使用adb shell查看。

image-20200319120728403

服务端监听8080端口。

image-20200319114813854

通过输入流得的传给它的数据。

image-20200319120127116

还有接收数据的方法。

image-20200319120203664

通过输出流返回一个随机数。然后对输入的数据进行了一些操作。

image-20200319120307536

主要就是checkpayload方法。

image-20200319120349230

验证mssage和check,check是随机数作为密钥的HmacSha1加密。

验证通过即可执行wget message。

没有环境了,搭了几个小时,还是没有成功。。。。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!