*CTF-2022

题目本身逻辑不难,主要偏向于基础能力的考察,时间在看汇编和调试的过程中慢慢溜走。还有就是淦RE时不要中途去玩儿别的方向,坚持才是胜利,一遍调试不行那就来N遍,当然合理的搜索和工具的使用往往会加速分析。🤔🤔🤔

Simple File System

tip:debug

签到题,虽然名字听起来有点吓人。

image-20220419173726329

生成文件进行的操作。

1
2
3
4
5
6
7
8
9
I implemented a very simple file system and buried my flag in it.

The image file are initiated as follows:
./simplefs image.flag 500
simplefs> format
simplefs> mount
simplefs> plantflag
simplefs> exit
And you cold run "help" to explore other commands.

主要关注plantflag操作,只有当sub_1E16("flag", v17, 1)才会写入加密后的flag,其余都是写入随机数。

image-20220419174534722

读写都以4k为单位,并且写入的数据长度为0x20,v4是从第一个数据块中取出的4Byte数据0xDEEDBEEF,之后将处理后的flag写入文件。

可以通过调试模拟生成文件的过程拿到v4,定位flag写的位置。通过010观察image.flag文件,4k一块,每块开头处便是写入的0x20Byte数据,直接遍历所有解根据flag的格式判断即可。

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
f=open(r'G:\game\X-CTF\RE\文件系统\image.flag','rb').read()
s=[]
tao=[0]*32
c=[]
for i in range(0,len(f),32):
t=list(f[i:i+32])
if t!=tao:
c.append(t)


k=[0xEF,0xBE,0xED,0xDE]
def ror(n,m):
return ((n>>m) | (n<<(8-m)))&0xff
def rol(n,m):
return (((n<<m)&0xff) | ((n>>(8-m))&0xff))
def dcd(s):
for i in range(len(s)):
s[i] = rol(s[i],5)
s[i] ^= k[3]
s[i] = rol(s[i], 4)
s[i] ^= k[2]
s[i] = rol(s[i], 3)
s[i] ^= k[1]
s[i] = rol(s[i], 2)
s[i] ^= k[0]
s[i] = rol(s[i],1)
try:
m=bytes(s).decode()
if '*CTF' in m or '*ctf' in m:
print(m)
except:
pass

for tb in c:
dcd(tb)
#*CTF{Gwed9VQpM4Lanf0kEj1oFJR6}

解密时异或的的key写成了ED ED,没有直接静态干掉,之后结合调试发现抄错了key。

NaCl

tip:Feistel/Xtea

r15模拟esp,自己维护的堆栈,所以IDA反编译的代码就比较乱,不过加密的逻辑块不是很多,调试加读汇编即可。=.= 硬逆

大🔥出题时间差不多,差一点点就摸到血了😭😭😭。

image-20220419171001916

Xtea的delta和轮数被魔改了比较坑,并且在之前还进行了Feistel 结构的加密。首先秘钥扩展,得到长为44的int数组,不过秘钥是固定的可以直接dump生成的扩展秘钥。

贴一下当时跟踪时记录的数据处理信息。

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
def rol(n,m):
return ((n<<m)&0xffffffff)|((n>>(32-m))&0xffffffff)
def ror(n,m):
return ((n>>m)&0xffffffff)|((n<<(32-m))&0xffffffff)
s=0x8090a0b
print(hex(ror(s,3)))
s=ror(s,3)

x=0x098BADCFF^0x4050607
print(hex(x))

print(hex(x^s))

b=0x10203
print(hex(x^s^b))

c=ror(0x8090a0b,4)
print(hex(c))

d=0xfdbff9ba^0xb08090a0
print(hex(d))

e=0x10203
print(hex(ror(e,1)))

print(hex(0x4d3f691a^0x80008101))
# 0x00000000080AFB70 : 0xcd3fe81b
print('---------------------------')

a=0x67452301EFCDAB89
print(hex(a>>1))
# 一共是0x2c 从4个扩展到了44个有点类似
tb=[0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B, 0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187, 0xF993CB81, 0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45, 0x306DF7F5, 0xAD300197, 0xAA86B056, 0x449263BA, 0x3FA4401B, 0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B, 0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C, 0x9B605BCB, 0x903B98B3, 0x24C2EEA3, 0x896E10A2, 0x2247F0C0, 0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C, 0x7E1CB50C, 0xFC27B826, 0x5FDDDFBC, 0xDE0FC404,0x0B2B30907]
print('---------------------------')
a=0x61616161
print(hex(rol(a,1)))
print(hex(rol(a,8)))
print(hex(0xc2c2c2c2&0x61616161))
print(hex(rol(a,2)))
print(hex(0x40404040^0x85858585))
b=0x62626262
print(hex(0xc5c5c5c5^b))
print(hex(0xa7a7a7a7^tb[0]))
print('---------------------------')
a=0x61616161
b=0x62626262
for i in range(44):
a,b=(rol(a,1)&rol(a,8))^rol(a,2)^b^tb[i],a
print(hex(a),hex(b))
a,b=b,a
print(hex(a),hex(b))
#0x668a1861 0xacbe01cb
#之后跟XTea

print('---------------------------')
c=[0x0F35310C, 0xD2CFADF5]
#0xacbe01cb 0x668a1861 Xtea结果正确

exp

魔改Xtea

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
#include<iostream>
#define ut32 unsigned int
#define delta 0x10325476

void XTea_Decrypt(ut32* enc, ut32* k,ut32 r) {
ut32 sum = delta * r;
ut32 v0 = enc[0];
ut32 v1 = enc[1];
for (int i = 0; i < r; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]);
}
enc[0] = v0;
enc[1] = v1;
}

int main() {
ut32 m[8] = { 0xFDF5C266, 0x7A328286, 0xCE944004, 0x5DE08ADC, 0xA6E4BD0A, 0x16CAADDC, 0x13CD6F0C, 0x1A75D936 };
ut32 k[4] = { 0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C };
for (int i = 0; i < 8; i += 2) {
XTea_Decrypt(m + i, k,2<<(i/2)); //多调试几遍看轮数是如何生成的
}
for (int i = 0; i < 8; i++) {
printf("0x%x ,", m[i]);
}
return 0;
}

RE-Feistel

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
tb=[0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B, 0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187, 0xF993CB81, 0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45, 0x306DF7F5, 0xAD300197, 0xAA86B056, 0x449263BA, 0x3FA4401B, 0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B, 0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C, 0x9B605BCB, 0x903B98B3, 0x24C2EEA3, 0x896E10A2, 0x2247F0C0, 0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C, 0x7E1CB50C, 0xFC27B826, 0x5FDDDFBC, 0xDE0FC404,0x0B2B30907]

def rol(n,m):
return ((n<<m)&0xffffffff)|((n>>(32-m))&0xffffffff)
def ror(n,m):
return ((n>>m)&0xffffffff)|((n<<(32-m))&0xffffffff)

def encode(a,b):
for i in range(44):
a,b=(rol(a,1)&rol(a,8))^rol(a,2)^b^tb[i],a
print(hex(a), hex(b))
a,b=b,a
return a,b

def decode(a,b):
a,b=b,a
for i in range(43,-1,-1):
a,b=b,a^(rol(b,1)&rol(b,8))^rol(b,2)^tb[i]
return a,b

c=[0xe71f5179 ,0xb55f9204 ,0x722d4a3a ,0x238e8b65 ,0x4385e0f2 ,0x6703757a ,0xaabe9be3 ,0x4de4253b]
for i in range(0,len(c),2):
a=c[i]
b=c[i+1]
a,b=decode(a,b)
print((int.to_bytes(a,4,'big')+int.to_bytes(b,4,'big')).decode(),end='')

#*CTF{mM7pJIobsCTQPO6R0g-L8kFExhYuivBN}

出题人: 很抱歉所有玩家通过对原始汇编代码的动态调试和静态分析解决了这个挑战。因为我的隐藏逻辑代码很短。太糟了。

预期: 首先,转储段SFI和SFI_data。然后,您通过脚本代码修改二进制文件。您需要恢复堆栈、寄存器和指令调用、ret 等。

这,确实自动化逆向工具和ipython的运用还不到位,暂时不会写。😔😔😔

jump

tip:setjmp/longjmp

硬逆

逆出流程的时候有点晚了,没看懂逻辑,肝不动了。。。

无符号,控制流不清晰,逻辑比较乱的,看一下数据段的信息准备动调。

image-20220418163622494

密文是34位,并且第一个函数就是输入。

1
input="01234567abcdefghijklmnopqrstuvwxyz" //测试输入的长度有问题 结合下文分析应为32位

之后进入进入sub_46a4c0函数,a1明显是一个结构体,可以通过调试信息定义一下。

image-20220418164403230

原本猜测为虚拟机,但后来发现这是保存上下文的操作,结合调试信息定义结构体存放数据,若了解setjmp/longjmp直接改参数为jmpbuf结构体即可。

直接按照setjmp函数的参数进行修复,大致如下。

image-20220418172303587

1
2
3
4
5
6
7
//env 结构体是全局变量
Int setjmp(jmp_buf env);
//返回值:若直接调用则返回0,若从longjmp调用返回则返回longjmp中的val值
Void longjmp(jmp_buf env,int val);
//调用此函数则返回到语句setjmp所在的地方,其中env 就是setjmp中的 env,而val 则是使setjmp的返回值变为val。
//跳到call setjmp之后的汇编语句,处理eax中的返回值之前。
//可用此来实现异常处理

那么sub_401D45函数则是longjmp

在函数sub_402689中实现加密操作。

image-20220418181345932

其中longjmp与外层函数的交互,依次将处理后输入字符循环左移。

image-20220418181815197

内存中如下

image-20220418182740692

1
2
3
4
5
6
var=0//2 '01234567abcdefghijklmnopqrstuvwxyz'3
var=1//'01234567abcdefghijklmnopqrstuvwxyz' 3 2
var=2//'1234567abcdefghijklmnopqrstuvwxyz' 3 2 '0'
var=3//'234567abcdefghijklmnopqrstuvwxyz' 3 2 '01'
...
var=34 //'z' 3 2 '01234567abcdefghijklmnopqrstuvwxy'

生成了一系列的tmp_str数组,接着进入sub_401F62函数进行处理。

image-20220419101201529

该函数是对str_arr中的字符串组进行升序的快速排序,=.= 当时没认出来。第一个字符从小到大。

之后又按照类似操作循环取字符,每次取str_arr[i][0x21]。

image-20220418204032667

内层通过jmp_buf变量env来保持上下文。

image-20220418210002030

循环0x22次获取enc,最后longjmp(env,0x23)调到check函数的比对处,相等则返回并输出,加密算法和测试数据如下。

1
2
3
4
5
6
7
8
9
10
11
testinput='01234567abcdefghijklmnopqrstuvwxyz'
testoutput=[0x79, 0x7A, 0x03, 0x02, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76]
s=list('\x02'+'01234567abcdefghijklmnopqrstuvwxyz'+'\x03')
s=[ord(i) for i in s]
str_arr=[]
for i in range(0x22):
str_arr.append(s[i:]+s[0:i])
#qsort(str_arr) 有快速排序 不过本次输入体现不出 因为本身就是有序的
for i in range(0x22):
print(hex(str_arr[i][0x21]),end=' ')
#如果输入是34位那么补充后变成36位会丢位,所以正确输入应该是32位,移位排序后取出最后一位。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
import copy
enc=[0x03, 0x6A, 0x6D, 0x47, 0x6E, 0x5F, 0x3D, 0x75, 0x61, 0x53, 0x5A, 0x4C, 0x76, 0x4E, 0x34, 0x77, 0x46, 0x78, 0x45, 0x36, 0x52, 0x2B, 0x70, 0x02, 0x44, 0x32, 0x71, 0x56, 0x31, 0x43, 0x42, 0x54, 0x63, 0x6B]
c=copy.deepcopy(enc)
c.sort()#排序就能获得第一列数据,已知最后一列数据
#因为是 2和3中间包含输入 所以如果排序后首位为3 那么末尾为他前一个数据,从右向左复原数据。
m=[0]*34
m[0]=2
m[-1]=3
for i in range(0x20,-1,-1):
p=c.index(m[i+1])
m[i]=enc[p]
print(bytes(m))
#*CTF{cwNG1paBu=6Vn2kxSCqm+_4LETvFRZDj}

这次的测试输入和算法识别上失误有点大,去符号猜的函数有点偏差,C的基础确实薄弱了点,没识别setjmp和longjmp。

符号恢复

可以使用Finger - 符号恢复-github来恢复库函数符号,真脚本小子的福利,基本上常用库函数都被复原了,分析直接上高速。

image-20220419163311164

恢复了一些库函数的符号,增加了程序的可读性,同时调试时也可以快速判断一些处理。

end

唯有自信和坚持,才能得到自己想要的结果。👏👏👏

zsky师傅的blog-*CTF - RE

setjmp和longjmp函数使用详解 - CSDN

setjmp/longjmp非局部跳转函数分析 - jmp_buf结构体