五一期间参加的angstromctf,大多数题目还是挺有意思,挺典型的。特别是最后一道weebhunter2花了2天时间才做出来,但是感觉很值得,学到了不少东西。
uninspired
一道有趣的数学题
检测逻辑:
输入十个0-9的整数array[0-9],需要满足:
1 | array[i]=sumj(array[j]=i) |
从0的个数开始从大到小枚举就行,最终得到如下的输入:
6210001000满足上述条件

flag:actf{ten_digit_numbers_are_very_inspiring}
dny
又是我们的老朋友rust:)
检验逻辑: 变换输入字符串,然后compare
首先是获取输入

格式要求 actf{xxx}
然后交换一下字符串的顺序

比较简单
最后进行比较

脚本如下:
1 | compare=b"_ynourtsd_tet_eh2_bfiasl7cedbda7" |
flag:actf{rusty_on_the_details_2fbdb7ac7de}
Flatland
- 控制流平坦化了,分析起来比较棘手
- 检测逻辑如下:
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__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rbx
__int64 v4; // r15
int v5; // eax
int v6; // ebp
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rcx
__int64 v10; // rax
__int64 v11; // rdx
int inputchar; // eax
bool v13; // zf
unsigned int v14; // ebx
const char *v15; // rdi
__int64 v17[51205]; // [rsp+0h] [rbp-64028h] BYREF
v5 = 0;
v6 = 0;
while ( 2 )
{
switch ( v5 )
{
case 0:
puts(
"I call our world Flatland, not because we call it so, but to make its nature clearer to you, my happy solvers,"
" who are privileged to have control flow.");
v7 = (__int64)v6 << 12;
*(_OWORD *)((char *)&v17[2] + v7) = 0LL;
*(_OWORD *)((char *)&v17[4] + v7) = 0LL;
*(_OWORD *)((char *)&v17[6] + v7) = 0LL;
*(_OWORD *)((char *)&v17[8] + v7) = 0LL;
*(_OWORD *)((char *)&v17[10] + v7) = 0LL;
*(_OWORD *)((char *)&v17[12] + v7) = 0LL;
*(_OWORD *)((char *)&v17[14] + v7) = 0LL;
*(_OWORD *)((char *)&v17[16] + v7) = 0LL;
*(_OWORD *)((char *)&v17[18] + v7) = 0LL;
*(_OWORD *)((char *)&v17[20] + v7) = 0LL;
*(_OWORD *)((char *)&v17[22] + v7) = 0LL;
*(_OWORD *)((char *)&v17[24] + v7) = 0LL;
*(_OWORD *)((char *)&v17[26] + v7) = 0LL;
*(_OWORD *)((char *)&v17[28] + v7) = 0LL;
*(_OWORD *)((char *)&v17[30] + v7) = 0LL;
*(_OWORD *)((char *)&v17[32] + v7) = 0LL;
++v6;
v4 = 1LL;
v5 = 13;
continue;
case 1:
v10 = (__int64)v6 << 12;
*(__int64 *)((char *)v17 + v10) = v3;
*((_BYTE *)&v17[2] + v10 + v3) = 1;
*(__int64 *)((char *)&v17[1] + v10) = 1LL;
v5 = 4;
continue;
case 2:
v8 = (__int64)v6 << 12;
*(__int64 *)((char *)v17 + v8) = v3;
*((_BYTE *)&v17[2] + v8 + v3) = 1;
v9 = *(__int64 *)((char *)&v17[1] + v8) + 1;// 记录读取数据的次数
*(__int64 *)((char *)&v17[1] + v8) = v9;
v5 = 2 * (v9 == 24) + 4; // 输入长度为24
continue;
case 3: // error information
v14 = 1;
v15 = "All the substantial binaries of Flatland itself appear no better than the offspring of a diseased imaginat"
"ion, or the baseless instructions of a CPU.";
break;
case 4:
++v6;
v4 = 5LL;
v5 = 13;
continue;
case 5:
v11 = v17[512 * (__int64)v6];
if ( v3 == dword_402090[v11] // 四项至少成立一项
|| v11 == dword_402090[v3]
|| v3 == dword_4020F0[v11]
|| (v5 = 3, v11 == dword_4020F0[v3]) )
{
v5 = 3 - (*((_BYTE *)&v17[512 * (__int64)v6 + 2] + v3) == 0);// 后一项等式必须成立,下标不能相同
}
continue;
case 6: // right
v14 = 0;
v15 = "Now you have truly understood the secrets of Flatland.";
break;
case 7:
v17[512 * (__int64)v6] = 0LL; // 循环变量 i
v5 = 8;
continue;
case 8:
v5 = 2 * (v17[512 * (__int64)v6] != 24) + 9;// i不能为24
continue;
case 9: // goto error
--v6;
v3 = -1LL;
v5 = v4;
continue;
case 10:
++v17[512 * (__int64)v6];
v5 = 8;
continue;
case 11:
v5 = 2 * (v3 == aNftrcd1ontrw4M[v17[512 * (__int64)v6]]) + 10;
continue;
case 12:
v3 = v17[512 * (__int64)v6--]; // v3=index,v6--
v5 = v4;
continue;
case 13:
inputchar = getc(stdin); // 获取输入
v17[512 * (__int64)v6] = inputchar;
v13 = inputchar == -1;
v5 = 14;
if ( v13 )
v5 = 3;
continue;
case 14:
v17[512 * (__int64)v6 + 1] = v4;
v3 = v17[512 * (__int64)v6++];
v4 = 15LL;
v5 = 7;
continue;
case 15:
v5 = 3;
if ( v3 != -1 )
v5 = v17[512 * (__int64)v6 + 1];
--v6;
continue;
default:
continue;
}
break;
}
puts(v15);
return v14;
}成功的提示对应case6,失败对应case3,case5是对输入的下标的判断,必须满足数组直接的关系
所有输入的字符串都储存在aNftrcd1ontrw4M中
aNftrcd1ontrw4M=”NfTRcD1ontrw}4{mFl_Ad0ua”
逻辑流分析的过程大致如下
大致思路就是调整上面的字符串的顺序,case5中的两个数组来检验下标顺序是否正确
用python写了一个回溯算法的脚本
1 | string = b"NfTRcD1ontrw}4{mFl_Ad0ua" |
flag:actf{Fl4TmAn_rouNdw0R1D}
Beam
被迫学习一波Erlang语法:(
首先二进制文件为beam类型,是由Erlang语言编译得到的
所以需要进行反编译再分析
最终找到一个类似的分析样例:https://github.com/pwning/public-writeup/blob/master/9447ctf2014/reversing/hellomike/hellomike.md
基本解题步骤: (需要现配一个erl环境)
反编译
1
io:format("~p~n",[beam_disasm:file("test.beam")]).

分析关键函数check

基本逻辑就是先获取输入,然后加密,最后compare
compare字符为”gjsfxpslt”
- 加密函数 check-fun-0

就是对输入的字符加1
解题脚本
1 | # io:format("~p~n",[beam_disasm:file("test.beam")]). |

flag:actf{elixir_is_awesome}
算法不难,但是寻找相关资料、现学语法让人头大
Weeb Hunters 2
基本介绍
- 很好的一道题,在CTF中打怪兽
- 逻辑检查难度中等,爆破比较麻烦
- 感谢魏神帮忙逆向rand函数和运行爆破脚本 👍
基本思路
获得武器,升级武器,与boss战斗
源代码一角
解题核心
解题最核心的部分
1 | v5 = rand(); // 5F10AFD5 |
- 这里v8,v9用来判断各种情况,如获得武器,升级武器,打怪等等,所以控制v8,v9的变化就控制了逻辑流的走向
- 而v8,v9是通过随机数生成函数rand来改变的,我们唯一能控制的就是它的seed,通过输入改变seed,产生特定随机数,然后控制v8,v9
- 有一个比较关键的点,v8,v9仅当上传的随机数为整数(即v10<0x8000,v11<0x8000)时才会修改,于是在爆破的时候,定点选择其中一个随机数的后16位,另一个大于0x7fff即可
下面就需要攻破rand函数,给出指定的输出
逆向随机函数
这里要感谢一下魏神,成功逆向了linux库里的rand函数,然后我魔改了一个python版本的:
python 版本的rand函数
1 | def myrand(seed_value): |
借助z3的Bitvec处理整数溢出
计算“密码”
由于进入不同的“副本”需要不同的密码,下面提供z3脚本求解副本需要的密码
获得和升级武器的密码求解脚本
1 | from z3 import * |
爆破种子的脚本
1 | def get_input(wtype): |
这里展示的只是获得武器的爆破脚本,升级武器的脚本可自行仿照魔改
获取输入
利用爆破脚本求解出能够使得v8,v9靠近密码的输入,并实时更新seed和v8,v9的状态,计算出所有的输入
1 | ''' |
第一个输入为用户名,可随意指定
最后使用如下脚本向服务器输入
pwn提交输入的脚本
1 |
|

flag:actf{sorry_guys_no_double_free_this_time}
