补档:Hitcon20 & Hxp20 & 上海大学生20 三道简单题的writeup
本文为之前参加过的ctf比赛的writeup,迁移到这个博客来吧,两道简答题,重在参与
RE
EXCELlent
- 艹,这个z3有毒,怎么排列不按名字来的;
- 下载下来是个excel,界面输入一个类似序列号的东西,后面第三行会动态地根据序列号进行变化,然后第二行看到了会根据输入的不同改变颜色,并且一开始什么都没有输的时候就有一格是绿色,如果不知道可能会以为这是装饰;
- 第一感觉是用公式弄的,但是首先选中输入框,发现没有公式(也很正常,毕竟是输入),然后在变化的输出框发现有一个拼接字符串的公式CONCATENATE,发现后面应该是跟着另外的sheet的名字,然后跟着行和列信息,eg.Tablele4!D4;
- 右键下面唯一的sheet,出现了取消隐藏,共有其他四个表,发现后三个表东西很多,搜了一下怎么跟踪公式:
操作 | 作用 |
---|---|
公式->追踪引用单元格 | 可以把这个公式依赖的格子给显示一下,但是实际上还没有把光标放到公式去上色好用,不过在看对应哪个格子还是有点用的 |
公式->追踪从属单元格 | 看看这个格子被哪些其他格子使用 |
- 对然后就是发现结果的字符串是由后面三个表生成的,根本搞不懂是什么加密;
- 回想起那个绿条,发现这个绿条应该是类似提示的东西,可能全绿了表示输入正确,然后后面三个表相当于是一个解密器,解密器干了啥其实不重要,只要关心输入对不对就成;
- 搜了下怎么自动变颜色,发现是开始->条件格式中可以设置规则,发现绿条的规则是和第一个隐藏表绑定的,回到第一个表发现是对输入进行了某种验证,那这样就开始了z3约束求解之旅;
- 有一个要注意的地方是发现有一个格子有两个条件约束,但是约束不可能同时满足两个,所以有一个是错误的约束;
- 约束应该没什么好说的,求到倒数第二个的时候就有答案了,可能以后应该从简单的约束开始写可以比较快一点出答案也说不定;
- 哦还有一个排列不按顺序来的,反正按着数组的顺序输出就行了,至于为什么不按顺序来就真的搞不懂orz;
- hxp{excellence_c0mes_fr0m_excel}
Hitcon 2020
RE
1101
- 运行的时候报libcrypto.so.1.1库找不到,找了一些解决方法,终于先解决了一波;
- ldd 文件名看链接,发现就是报错中的libcrypto.so.1.1找不到,搜了一下要直接装这个库的话要把一个库的信息rpm加到/etc/apt/sources.list里面,但是ubuntu16没有装rpm,所以用不了;
- 于是看到有人是去升级openssl到1.1.1,所以先下载然后升级:
1 | wget https://www.openssl.org/source/openssl-1.1.0g.tar.gz |
- 然后就是链接改一下,如果存在就rm掉:
1 | sudo ln -s /usr/local/lib/libssl.so.1.1 /usr/lib/libssl.so.1.1 |
第一关是把输入&一个数,应该是用来限制输入的范围,后面爆破应该需要用到;
看来以后这种题应该就是构造z3约束的题了,看了ctftime上的两个wp,perfectblue的有点看不懂,对比两个wp学一下z3怎么写的;
- 首先就是我自己也能看出来的提供一个mask然后约束:
1 | # wp1 |
- 初始化学着点,然后这个比较也没什么好说,比较朴素;
- 第二个是个移位,看起来也比较简单
1 | for index in range(20): |
- perfectblue的看不懂啊,好像是扩展成了bit的操作,跳过了跳过了;
- 第三个也简单,合到上面去了;
- 第四个看懂了,其实就是统计1的数量为10:
1 | def popcount(bvec): |
解释下上面几个z3的api干了什么,Extract从目标bitvec里面拿出指定的bits,注意第一个参数是高位,并且应该是左右都是闭合区间;ZeroExt表示往高位补0,第一个参数就是补多少个0,那么为什么要这么写呢,因为log之后如果所有位都是1的话这样加起来也不会超过最大值;
后面好像都差不多了,这一个是判断全不等的api:Distinct:
1 | s.add(Distinct(inputBuffer)) |
上海大学生 2020
RE
真正的RE
- 程序里有很多nop,有点奇怪,但是不影响IDA反编译;
- 调整了最后的堆栈,让F5能用;
- 这里貌似是反调试,检查这里的时钟周期是否在一个很短的时间区间内,有两个方法,要么每次运行到这里直接跳到下一句,要么就patch掉;
1
2
3
4
5
6
7BYTE1(v15) &= v15;
v4 = __rdtsc();
v12 = v4;
v5 = __rdtsc();
v11 = v5;
if ( (unsigned int)(v5 - v12) >= 0xFF )
exit(0); - 跳到下一句是可以的;咦第二次来的时候就跳不住了,那还是先patch一下;
- 看起来是对输入进行操作然后和一个固定的数组比较了;
- 感觉是一个生成器把控制流给生成出来,然后对输入进行加密;
- 有一个常数组的密钥;
- 猜一下他是可逆的,输入结果试一下,并不是直接还原;
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
68unsigned char target[] =
{
0x1F, 0x18, 0x0F, 0xFA, 0xB8, 0x63, 0x64, 0x89, 0x18, 0x68,
0x7C, 0x19, 0x14, 0x2D, 0x7D, 0x58, 0x00, 0x1E, 0x54, 0x6A,
0x41, 0x3C, 0x36, 0x3E, 0x56, 0x13, 0x04, 0x3B, 0x2E, 0x4B,
0x79, 0x43, 0x7A, 0x22, 0x45, 0x6E, 0x3A, 0x75, 0x26, 0xC8,
0xC0, 0x8E
};
unsigned char ida_chars[] =
{
0x12, 0x3F, 0x2E, 0xD6, 0xD4, 0x39, 0x3A, 0x3A, 0x32, 0x32,
0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E
};
unsigned char ida_chars[] =
{
0x12, 0x01, 0x7C, 0x60, 0xD6, 0xD5, 0x3A, 0x3A, 0x32, 0x32,
0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E,
0xF9, 0xF7
};
//1
unsigned char ida_chars[] =
{
0x31, 0xDF, 0xCF, 0x3C, 0x38, 0x39, 0x3A, 0x3A, 0x32, 0x32,
0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E,
0xC1, 0xDC
};
//2
unsigned char ida_chars[] =
{
0x32, 0xDF, 0xCF, 0x3C, 0x38, 0x39, 0x3A, 0x3A, 0x32, 0x32,
0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E,
0xC0, 0xDE
};
//11
unsigned char ida_chars[] =
{
0x21, 0x50, 0xD9, 0xD0, 0x38, 0x39, 0x3A, 0x3A, 0x32, 0x32,
0x3C
};
//111
unsigned char ida_chars[] =
{
0x1A, 0x38, 0x2C, 0xD6, 0xD4, 0x39, 0x3A, 0x3A, 0x32, 0x32,
0x3C, 0x3C, 0x3A, 0x33, 0x3E
};
//1111
unsigned char ida_chars[] =
{
0x1A, 0x03, 0x26, 0x2D, 0xD6, 0xD5, 0x3A, 0x3A, 0x32, 0x32,
0x3C, 0x3C, 0x3A
};
//22 - 看起来输入会循环影响后面的四个byte,但是前两个应该可以定下来
- 三个DWORD为一组,第一个DWORD中的1的位置决定操作;
- 根据第一个DWORD有两种情况,交叉着来,有一个会取一个输入继续重复的异或,有一个只会取两个数然后异或直接结束;
- 明白了,看来是个完全无冲突的变换;
- 随便搞个脚本就完事了,还是太弱了,不够大胆,这种题应该搞快点;
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
53optflow=[] # 原始数据流就不贴了
def testloop():
allmap=dict()
for i in range(2,15):
coset=dict()
for j in range(0,0x100):
a=i
b=j
while a:
x=a^b
a=a&b
a=(a<<1)&0xff
b=x
if b in coset.keys():
print('error')
print(i,j)
exit()
else:
coset[b]=j
# print(1)
allmap[i]=coset
return allmap
import struct
def flowgen2():
global optflow
optlist=list()
for i in range(0,len(optflow),12):
curpp,firstind,secondind=struct.unpack('<III',bytes(optflow[i:i+12]))
optlist.append((curpp,firstind,secondind))
return optlist[:-1]
target=[0x1F, 0x18, 0x0F, 0xFA, 0xB8, 0x63, 0x64, 0x89, 0x18, 0x68,
0x7C, 0x19, 0x14, 0x2D, 0x7D, 0x58, 0x00, 0x1E, 0x54, 0x6A,
0x41, 0x3C, 0x36, 0x3E, 0x56, 0x13, 0x04, 0x3B, 0x2E, 0x4B,
0x79, 0x43, 0x7A, 0x22, 0x45, 0x6E, 0x3A, 0x75, 0x26, 0xC8,
0xC0, 0x8E]
def reback():
global target
optlist=flowgen2()
maplist=testloop()
for opt in optlist[::-1]:
op,fi,ni=opt
if op %2:
target[fi%64]=maplist[ni][target[fi%64]]
else:
target[fi%64]=target[fi%64]^target[ni%64]
for i in target:
print(chr(i),end='')
if __name__=='__main__':
reback()
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Exp!