angstromctf rev


五一期间参加的angstromctf,大多数题目还是挺有意思,挺典型的。特别是最后一道weebhunter2花了2天时间才做出来,但是感觉很值得,学到了不少东西。

uninspired

一道有趣的数学题
检测逻辑:

输入十个0-9的整数array[0-9],需要满足:

1
2
array[i]=sumj(array[j]=i)
# 比如array[0]=9,表示 array中有9个的数值为0,而array[9]>=1,因为arr

从0的个数开始从大到小枚举就行,最终得到如下的输入:
6210001000满足上述条件


flag:actf{ten_digit_numbers_are_very_inspiring}

dny

又是我们的老朋友rust:)
检验逻辑: 变换输入字符串,然后compare

  1. 首先是获取输入

    格式要求 actf{xxx}

  2. 然后交换一下字符串的顺序

    比较简单

  3. 最后进行比较

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
compare=b"_ynourtsd_tet_eh2_bfiasl7cedbda7"
test1="ABCDEFGHIJKLMNOPQRSTUVWXYZ012345" # 测试输入
test2=list("FEHGBADCNMPOJILKVUXWRQTS3254ZY10") # 变换后的输入

seq=[0 for _ in range(32)]
for i in range(32):
seq[i]=test1.index(test2[i])
print(seq)

res=[0 for _ in range(32)]
for i in range(32):
res[i]=compare[seq[i]]

flag=b'actf{'+bytes(res)+b'}'
print(flag)


# actf{rusty_on_the_details_2fbdb7ac7de}


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
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
string = b"NfTRcD1ontrw}4{mFl_Ad0ua"

print(len(string))


# 0x401140

dword_402090 = [20,10,0,22,14,4,5,22,15,15,18,7,10,2,19,19,9,13,8,17,11,12,0,4]
dword_4020F0= [17,14,15,6,9,12,5,10,18,1,7,21,16,14,16,2,17,0,10,8,22,3,2,19]
flag=False
res=[23,4,9,1,14]

def backtrack(current):
global flag

if len(current)==24 and current[-1]==12:
flag=True
return True
if flag:
return True

# 最多四个方向
choices=[dword_402090[current[-1]]]
if dword_4020F0[current[-1]] not in choices:
choices.append(dword_4020F0[current[-1]])
if current[-1] in dword_402090 and dword_402090.index(current[-1]) not in choices:
choices.append(dword_402090.index(current[-1]))
if current[-1] in dword_4020F0 and dword_4020F0.index(current[-1]) not in choices:
choices.append(dword_4020F0.index(current[-1]))
copy=current
for i in choices:
if (i not in current):
if i!=12 or len(current)==23:
current.append(i) # 做选择
if backtrack(current): # 判断选择是否正确
flag=True
return True
else:
current.pop() # 撤销选择

flag=False
return False

if backtrack(res):
print(res)
# print(res)
flag=[]
for i in range(24):
flag.append(string[res[i]])
print(bytes(flag))
# actf{Fl4TmAn_rouNdw0R1D}


flag:actf{Fl4TmAn_rouNdw0R1D}

Beam

被迫学习一波Erlang语法:(
首先二进制文件为beam类型,是由Erlang语言编译得到的

所以需要进行反编译再分析
最终找到一个类似的分析样例:https://github.com/pwning/public-writeup/blob/master/9447ctf2014/reversing/hellomike/hellomike.md

基本解题步骤: (需要现配一个erl环境)

  1. 反编译

    1
    io:format("~p~n",[beam_disasm:file("test.beam")]).

  2. 分析关键函数check


基本逻辑就是先获取输入,然后加密,最后compare
compare字符为”gjsfxpslt”

  1. 加密函数 check-fun-0

    就是对输入的字符加1

解题脚本

1
2
3
4
5
# io:format("~p~n",[beam_disasm:file("test.beam")]).   
pawd=b"gjsfxpslt"
res=[m-1 for m in pawd]
print(bytes(res))
# fireworks


flag:actf{elixir_is_awesome}

算法不难,但是寻找相关资料、现学语法让人头大

Weeb Hunters 2

基本介绍

  • 很好的一道题,在CTF中打怪兽
  • 逻辑检查难度中等,爆破比较麻烦
  • 感谢魏神帮忙逆向rand函数和运行爆破脚本 👍

基本思路

获得武器,升级武器,与boss战斗

源代码一角

解题核心

解题最核心的部分

1
2
3
4
5
6
7
8
v5 = rand();                            // 5F10AFD5
srand(*(_DWORD *)s1 ^ v5); // 根据输入获取一个种子可控
v10 = rand();
v11 = rand();
if ( v10 > 0 )
v8 += v10;
if ( v11 > 0 )
v9 += v11;
  • 这里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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def myrand(seed_value):
seed=seed_value
res=[]
r=[0 for _ in range(360)]
r[0]=seed
for i in range(1,31):
temp=(16807 * r[i-1]) % 2147483647
r[i]=BitVecVal(temp,32).as_long()
for i in range(31,34):
r[i]=r[i-31]
for i in range(34,344):
temp= r[i-31] + r[i-3]
r[i]=BitVecVal(temp,32).as_long()

for i in range(344,344+2):
temp = r[i-31] + r[i-3]
r[i]=BitVecVal(temp,32).as_long()
res.append(r[i]>>1)
return res

借助z3的Bitvec处理整数溢出

计算“密码”

由于进入不同的“副本”需要不同的密码,下面提供z3脚本求解副本需要的密码

获得和升级武器的密码求解脚本

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
from z3 import *

currentx=0xdead
currenty=0xbeef
currentrand=0x5F10AFD5
# 升级武器的密码
v24 = 0xD3385C606D740964 # 医生小屋 [y = 14920, x = 13100] 0x332c,0x3a48
v24 = 0x33C458487662651A # 铁匠屋副本 [y = 28076, x = 2230] 0x8b6,0x6dac 0
v24 = 0x5B3E7B48765369A3 # 巫师之塔副本 [y = 18171, x = 12120] 0x2f58,0x46fb 1
v24 = 0xDBE762ED608E186C # 工程师小屋 [y = 48213, x = 42041] 0xa439,0xbc55 2


# 根据参数,求出对应的v8,v9
def updateweapon(v24):
x=BitVec('x',16)
y=BitVec('y',16)
s=Solver()

one=(v24>>48)&0xffff
two=(v24>>32)&0xffff
three=(v24>>16)&0xffff
four=(v24)&0xffff
s.add((x*y)&0xffff==two)
s.add((x^y)&0xffff==four)
s.add(((x&y)+(x|y))&0xffff==three)
s.add((((x&y)+(x|y))+(x*y)+(x^y)&0xffff==one))
s.check()
print(s.model())


def getweapon(wtype):
x=BitVec('x',16)
y=BitVec('y',16)
s=Solver()
s.add((x^y)&7==1)
s.add(((x^y)>>3)&3==wtype)
s.add(x>10000,y>10000)
s.check()
# print(s.model())
m=s.model()
x_value=m[x].as_long()
y_value=m[y].as_long()
return x_value,y_value

爆破种子的脚本

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
def get_input(wtype):
for i1 in range(0x21,0x7f):
for i2 in range(0x21,0x7f):
for i3 in range(0x21,0x7f):
for i4 in range(0x21,0x7f):
input_value=(i1<<24)|(i2<<16)|(i3<<8)|i4
seed=input_value^currentrand
rand1,rand2=myrand(input_value^currentrand)
if rand1&0xffff<0x8000 and rand2&0xffff<0x8000:
realx=(currentx+rand1)&0xffff
realy=(currenty+rand2)&0xffff

andorxy=((realx & realy)+(realx | realy))&0xffff
xorvy=(realx ^ realy)&0xffff
imuxy=(realx * realy)&0xffff
sumxy=(andorxy + xorvy + imuxy)&0xffff

if (sumxy>>15)or (andorxy>>15) or(xorvy>>15) or(sumxy>>15):
continue
if((realx^realy)&7) and ((realx^realy)%8==1):
if(((realx^realy)>>3)&3==wtype):
print(hex(input_value))
print(chr(i4)+chr(i3)+chr(i2)+chr(i1)+"/")
print(hex(seed),hex(realx),hex(realy),hex(rand1),hex(rand2))
return 0

return -1

这里展示的只是获得武器的爆破脚本,升级武器的脚本可自行仿照魔改

获取输入

利用爆破脚本求解出能够使得v8,v9靠近密码的输入,并实时更新seed和v8,v9的状态,计算出所有的输入

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
'''
song

"/!!
Y0!!
}#!!
eC!!
R\#!
1:(!
m&!!
54fcfbc921e8b735
-!1$
OK!!
PF$P
>X(!
F#7!
NF$1
ra!P
54fcfbc921e8b735
bO!0
54fcfbc921e8b735
a12caf3cba1e5f13
54fcfbc921e8b735
111c7ab93a8cbcae
a8cdae3240310bdd
fist
'''

第一个输入为用户名,可随意指定

最后使用如下脚本向服务器输入

pwn提交输入的脚本

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

from pwn import *
context.log_level = 'debug'
io=remote("challs.actf.co", 31600) # nc challs.actf.co 31600
# io.interactive()

def go(code):
io.recvuntil(b'> ')
io.sendline(code)


def attack(code):
io.recvuntil(b'Choose your action: ')
io.sendline(code)



codes=[rb'"/!!',rb'Y0!!',rb'}#!!',rb'eC!!',rb'R\#!',rb'1:(!',rb'm&!!',rb'-!1$',rb'OK!!',rb'PF$P',rb'>X(!',rb'F#7!',rb'NF$1',rb'ra!P',rb'bO!0',rb'a12caf3cba1e5f13']
attacks=[rb'54fcfbc921e8b735',rb'111c7ab93a8cbcae',rb'a8cdae3240310bdd',rb'fist']


# io.recvuntil(b'Enter your name: ')
io.sendline(b'song')
for i in range(7):
go(codes[i])
attack(attacks[0])
for i in range(7,14):
go(codes[i])

attack(attacks[0])
go(codes[14])
attack(attacks[0])
go(codes[15])
for i in range(4):
attack(attacks[i])
# io.recv(1024,5)

io.recvuntil(b'!')


flag:actf{sorry_guys_no_double_free_this_time}


文章作者: Smile Song
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Smile Song !
  目录