来论坛也一年有余,一直是从各位师傅处学习。深觉自己能力浅薄不应随意发表误人子弟,但近来发现有时即使是不那么正确的内容,对于初学者来说有就比没有好。这里把自己一月以来刷题的记录总结一下,一方面是部分题难以找到 WriteUp,在这里记载也希望能让后来的人发觉一线思路,抛砖引玉,另一方面也是鞭策我自己,让大家看看我做题的过程中是否有一些想当然的错误。废话就说到这儿,以下是正文,所有题目均可以在 BUUCTF 上找到,大部分分数处于 90-100 之间
QCTF2018 - asong
提前说明我做的时候命名有问题, 实际的密码cipher应该是content里面存的, 而我命名的cipher是密文.一开始命名错了就懒得改了
请原谅大懒蛋我甚至因为懒得传图而没放图
题目给了三个文件, 一个明文一个密文一个ELF
老样子, 丢IDA,F12找到一个文件名一个QCTF{, 直接定位关键函数
函数0x400936 :
把读入的flag第i位按下表转换为cur,并读取文件内容的第cur位赋值给v5的第i位, 这是步骤I
但是总感觉Symbol缺了一些东西,IDA的F5没有成功分析出来,打算第二步再看
0-9 |
a-zA-Z |
Symbols |
0-9 |
10-35 |
略 |
第一个函数是步骤II, 一个轮换,大概意思是v5[i] = v5[table1[i]], i = table1[i]
直接把v2看成i即可
while ( table1[i] ) {
cipher1[i] = cipher1[table1[i]];
i = table1[i];
}
result = cipher[0];
cipher1[i] = v2[0];
table1的值是这样的
unsigned char table1[] =
{
0x16, 0x00, 0x06,
0x02, 0x1E,
0x18, 0x09, 0x01,
0x15, 0x07,
0x12, 0x0A, 0x08,
0x0C, 0x11,
0x17, 0x0D, 0x04,
0x03, 0x0E,
0x13, 0x0B, 0x14,
0x10, 0x0F,
0x05, 0x19, 0x24,
0x1B, 0x1C,
0x1D, 0x25, 0x1F,
0x20, 0x21,
0x1A, 0x22, 0x23
};
table1 = [
0x16, 0x00, 0x06,
0x02, 0x1E,
0x18, 0x09, 0x01,
0x15, 0x07,
0x12, 0x0A, 0x08,
0x0C, 0x11,
0x17, 0x0D, 0x04,
0x03, 0x0E,
0x13, 0x0B, 0x14,
0x10, 0x0F,
0x05, 0x19, 0x24,
0x1B, 0x1C,
0x1D, 0x25, 0x1F,
0x20, 0x21,
0x1A, 0x22, 0x23
]
cur = 1
for i in range(len(table1)) :
# i 在 table1 中的下标的值就是前一个 i
print(cur, end=",")
cur = table1.index(cur)
'''
1,7,9,6,2,
3,18,10,11,21,
8,12,13,16,23,
15,24,5,25,26,
35,37,31,32,33,
34,36,27,28,29,
30,4,17,14,19,
20,22,0
'''
把table1的轮换逆掉
第二个函数是把v5[i]变成v5[i]的低5位拼v5[i+1]的高3位,前者在高位后者在低位,最后把最后一位拼成v5[len]的低五位和v5[0]的高三位, 步骤III
3 = *cipher >> 5;
for ( i = 0; flaglen - 1 > i; ++i )
cipher[i] = (8 * cipher[i]) | (cipher[i + 1] >> 5);
result = &cipher[i];
*result = (8 * *result) | v3;
第三个函数是输出
这种移位加密的脚本还是用C比较好写
先把III转回来
#include <cstdio>
unsigned char des[] = {
0xEC, 0x29, 0xE3, 0x41, 0xE1,
0xF7, 0xAA, 0x1D, 0x29, 0xED,
0x29, 0x99, 0x39, 0xF3, 0xB7,
0xA9, 0xE7, 0xAC, 0x2B, 0xB7,
0xAB, 0x40, 0x9F, 0xA9, 0x31,
0x35, 0x2C, 0x29, 0xEF, 0xA8,
0x3D, 0x4B, 0xB0, 0xE9, 0xE1,
0x68, 0x7B, 0x41
};
int main() {
char temp = des[37];
for(int i = 37; i > 0; i -- ) {
des[i] = (des[i] >> 3) | (des[i-1] << 5);
}
des[0] = (des[0] >> 3) | (temp << 5);
for(int i = 0; i < 38; i ++ ) {
printf("0x%x, ", des[i]);
}
}
/*
0x3d, 0x85, 0x3c, 0x68, 0x3c,
0x3e, 0xf5, 0x43, 0xa5, 0x3d,
0xa5, 0x33, 0x27, 0x3e, 0x76,
0xf5, 0x3c, 0xf5, 0x85, 0x76,
0xf5, 0x68, 0x13, 0xf5, 0x26,
0x26, 0xa5, 0x85, 0x3d, 0xf5,
0x07, 0xa9, 0x76, 0x1d, 0x3c,
0x2d, 0x0f, 0x68
*/
我略微疑惑了一下, 步骤II是轮换,那这里出现的字符应该还是全可见字符,但是里面的0xf5\0xf什么的明显不是可见字符, 不过先做下去再说.
瞎翻的过程中发现readin函数也调用过那个全篇Switch的函数..晕, 太不细心了,这说明 Thatgirl原文被改过.
不过先把III和II转到I吧
table = [
1,7,9,6,2,3,18,10,11,21,8,12,13,16,23,15,24,5,25,26,35,37,31,32,33,34,36,27,28,29,30,4,17,14,19,20,22,0,1
]
Src = [
0x3d, 0x85, 0x3c, 0x68, 0x3c,
0x3e, 0xf5, 0x43, 0xa5, 0x3d,
0xa5, 0x33, 0x27, 0x3e, 0x76,
0xf5, 0x3c, 0xf5, 0x85, 0x76,
0xf5, 0x68, 0x13, 0xf5, 0x26,
0x26, 0xa5, 0x85, 0x3d, 0xf5,
0x07, 0xa9, 0x76, 0x1d, 0x3c,
0x2d, 0x0f, 0x68
]
Dst = [0] * 40
print(len(table))
print(len(Src))
for i in range(0, len(table)-1) :
Dst[table[i]] = Src[table[i+1]]
print(Dst)
'''
[133, 67, 104, 133, 245,
38, 60, 61, 39, 245,
51, 104, 62, 60, 118,
38, 245, 118, 165, 245,
19, 165, 61, 245, 62,
165, 45, 61, 245, 7,
60, 118, 29, 60, 15,
104, 133, 169]
'''
最后差400936, 这个要和读入结合起来看
content[4*switch(buf)] ++
cipher[i] = content[4*switch(flag[i])]
switch分析过了,实在是懒得动调出第一步,
switch就像一个离散化函数,content就是一个词频统计桶
0-9根本不用计,只需要计azAZ和_即可,_是46
这样之前的疑惑也迎刃而解,根本没有什么可不可见字符的事情,只是一词频统计的数字.
#include <cstdio>
int bukit[100];
int main() {
freopen("that_girl", "r", stdin);
char buf;
while((buf = getchar()) != EOF) {
if(buf == '_') bukit[26] ++;
else {
if(buf >= 'a' && buf <= 'z') bukit[buf-'a'] ++;
else bukit[buf-'A'] ++;
}
}
for(int i = 0; i < 26; i ++ ) {
printf("%c : %d\n", i+'a', bukit[i]);
}
printf("_ : %d\n", bukit[26]);
}
a : 104
b : 30
c : 15
d : 29
e : 169
f : 19
g : 38
h : 67
i : 60
j : 0
k : 20
l : 39
m : 28
n : 118
o : 165
p : 26
q : 0
r : 61
s : 51
t : 133
u : 45
v : 7
w : 34
x : 0
y : 62
z : 0
_ : 245
最后懒得写脚本了,直接对着翻译好了
flag{that_girl_saying_no_for_your_vindicate}
[CTF/Reverse] [FlareOn5] FLEEGO
下载下来是一堆32位exe, 随便打开一个
读一个wchar数组,扔在941240里,需要和word_944380比对结果一致,会给一张png
长这样
这样的话肯定要把所有exe的这部分都解出来
检查对比数组的交叉引用,发现用的是resource,可以直接010 editor看之
在2Ab0处,用python直接提取了每个执行一次, 注意win的resource中间隔了一个0x0, 需要去掉
import os
import subprocess
base_dir = r"E:\ctf\work\FLEGGO"
file_name = os.listdir(base_dir)
for name in file_name :
if name[-3:] != "exe" :
continue
key = ""
with open(name, "rb") as f:
key = f.read()[0x2ab0:0x2ad0:2]
file_dir = base_dir+"\\"+name
p = subprocess.Popen([file_dir], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.stdin.write(key)
p.stdin.close()
print(p.stdout.read().decode())
p.stdout.close()
这样我们就获得了48张图片,每张图片上有个数字,每个图片对应一个字母
What is the password?
Everything is awesome!
65141174.png => w
What is the password?
Everything is awesome!
85934406.png => m
What is the password?
Everything is awesome!
67782682.png => m
What is the password?
Everything is awesome!
75072258.png => r
What is the password?
Everything is awesome!
16544936.png => e
What is the password?
Everything is awesome!
67322218.png => _
What is the password?
Everything is awesome!
58770751.png => o
What is the password?
Everything is awesome!
64915798.png => 3
What is the password?
Everything is awesome!
88763595.png => e
What is the password?
Everything is awesome!
18376743.png => _
What is the password?
Everything is awesome!
36870498.png => m
What is the password?
Everything is awesome!
72501159.png => c
What is the password?
Everything is awesome!
47619326.png => p
What is the password?
Everything is awesome!
70037217.png => m
What is the password?
Everything is awesome!
18309310.png => @
What is the password?
Everything is awesome!
15566524.png => e
What is the password?
Everything is awesome!
82100368.png => m
What is the password?
Everything is awesome!
60075496.png => s
What is the password?
Everything is awesome!
71290032.png => a
What is the password?
Everything is awesome!
33718379.png => .
What is the password?
Everything is awesome!
42255131.png => t
What is the password?
Everything is awesome!
16295588.png => a
What is the password?
Everything is awesome!
61333226.png => f
What is the password?
Everything is awesome!
13147895.png => w
What is the password?
Everything is awesome!
16785906.png => 4
What is the password?
Everything is awesome!
80333569.png => o
What is the password?
Everything is awesome!
37723511.png => n
What is the password?
Everything is awesome!
44958449.png => _
What is the password?
Everything is awesome!
30171375.png => s
What is the password?
Everything is awesome!
72263993.png => h
What is the password?
Everything is awesome!
82236857.png => e
What is the password?
Everything is awesome!
33098947.png => _
What is the password?
Everything is awesome!
33662866.png => r
What is the password?
Everything is awesome!
47893007.png => _
What is the password?
Everything is awesome!
61006829.png => l
What is the password?
Everything is awesome!
89295012.png => 0
What is the password?
Everything is awesome!
87730986.png => 0
What is the password?
Everything is awesome!
65626704.png => 3
What is the password?
Everything is awesome!
72562746.png => -
What is the password?
Everything is awesome!
36494753.png => 0
What is the password?
Everything is awesome!
79545849.png => s
What is the password?
Everything is awesome!
63223880.png => a
What is the password?
Everything is awesome!
51227743.png => a
What is the password?
Everything is awesome!
73903128.png => u
What is the password?
Everything is awesome!
52817899.png => n
What is the password?
Everything is awesome!
19343964.png => o
What is the password?
Everything is awesome!
12268605.png => s
What is the password?
Everything is awesome!
47202222.png => n
应该是要自动化一次的, 识别一下数字,但是没错, 我懒了, 决定做更多的手工动作
mor3_awes0m3_th4n_an_awes0me_p0ssum@flare-on.com
[CTF/Reverse] [X-NUCA2018] Code_Interpreter
看名字就知道是VM题,打开关键函数分析OPCode
0x0 -> HALT
0x1 -> PUSH(EXPAND([CS:op+4],[CS:op+3],[CS:op+2],[CS:op+1])) OP+=4
0x2 -> POP
0x3 -> [DS:OP+1] += [DS:OP+2] OP += 2
0x4 -> -=
0x5 -> *=
0x6 -> >>=
0x7 -> MOV
0x8 -> MOV [DS:OP+1], SP+[CS:OP+2] OP += 2
0x9 -> ^=
0xA -> |=
先把code转16进制然后用Python反编译一下
#include <cstdio>
int main() {
freopen("code", "rb", stdin);
int ch;
while(~scanf("%c", &ch)) {
printf("0x%02x,", ch);
}
}
opcode = [
0x09,0x04,0x04,0x09,0x00,0x00,0x08,0x01,0x00,0x08,0x02,0x01,0x08,0x03,0x02,0x06,0x01,0x04,0x05,0x01,0x15,0x07,0x00,0x01,0x04,0x00,0x03,0x01,0x6b,0xcc,0x7e,0x1d,0x08,0x01,0x03,0x04,0x00,0x01,0x02,0x0a,0x04,0x00,0x09,0x00,0x00,0x08,0x01,0x00,0x08,0x02,0x01,0x08,0x03,0x02,0x06,0x03,0x08,0x05,0x03,0x03,0x07,0x00,0x03,0x03,0x00,0x02,0x01,0x7c,0x79,0x79,0x60,0x08,0x01,0x03,0x04,0x00,0x01,0x02,0x0a,0x04,0x00,0x09,0x00,0x00,0x08,0x01,0x00,0x08,0x02,0x01,0x08,0x03,0x02,0x06,0x01,0x08,0x07,0x00,0x01,0x03,0x00,0x02,0x01,0xbd,0xbd,0xbc,0x5f,0x08,0x01,0x03,0x04,0x00,0x01,0x02,0x0a,0x04,0x00,0x00
]
vStack = [0] * 10
ip, flag, top = 0, 1, 2
while flag != 0 :
if opcode[ip] == 0x0:
flag = 0
print("HALT")
elif opcode[ip] == 0x1 :
temp = "( " + str(opcode[ip+4]) + " << 24 )+"
temp += "( " + str(opcode[ip+3]) + " << 16 )+"
temp += "( " + str(opcode[ip+2]) + " << 8 )+"
temp += str(opcode[ip+1])
print("PUSH", temp)
ip+=4
elif opcode[ip] == 0x2 :
print("POP")
elif opcode[ip] == 0x3 :
print("ADD [", opcode[ip+1], "], [", opcode[ip+2], "]")
ip+=2
elif opcode[ip] == 0x4 :
print("SUB [", opcode[ip+1], "], [", opcode[ip+2], "]")
ip+=2
elif opcode[ip] == 0x5 :
print("MUL [", opcode[ip+1], "],", opcode[ip+2])
ip+=2
elif opcode[ip] == 0x6 :
print("SHR(U) [", opcode[ip+1], "], ", opcode[ip+2])
ip+=2
elif opcode[ip] == 0x7 :
print("MOV [", opcode[ip+1], "], [", opcode[ip+2], "]")
ip+=2
elif opcode[ip] == 0x9 :
print("XOR [", opcode[ip+1], "], [", opcode[ip+2], "]")
ip+=2
elif opcode[ip] == 0xA :
print("OR [", opcode[ip+1], "], [", opcode[ip+2], "]")
ip+=2
elif opcode[ip] == 0x8 :
print("LOD [", opcode[ip+1], "], Stack:[", opcode[ip+2], "]")
ip+=2
else :
print("FAULT")
ip += 1
翻译结果如下:
XOR [ 4 ], [ 4 ]
XOR [ 0 ], [ 0 ]
LOD [ 1 ], Stack:[ 0 ]
LOD [ 2 ], Stack:[ 1 ]
LOD [ 3 ], Stack:[ 2 ]
SHR(U) [ 1 ], 4
MUL [ 1 ], 21
MOV [ 0 ], [ 1 ]
SUB [ 0 ], [ 3 ]
PUSH ( 29 << 24 )+( 126 << 16 )+( 204 << 8 )+107
LOD [ 1 ], Stack:[ 3 ]
SUB [ 0 ], [ 1 ]
POP
OR [ 4 ], [ 0 ]
XOR [ 0 ], [ 0 ]
LOD [ 1 ], Stack:[ 0 ]
LOD [ 2 ], Stack:[ 1 ]
LOD [ 3 ], Stack:[ 2 ]
SHR(U) [ 3 ], 8
MUL [ 3 ], 3
MOV [ 0 ], [ 3 ]
ADD [ 0 ], [ 2 ]
PUSH ( 96 << 24 )+( 121 << 16 )+( 121 << 8 )+124
LOD [ 1 ], Stack:[ 3 ]
SUB [ 0 ], [ 1 ]
POP
OR [ 4 ], [ 0 ]
XOR [ 0 ], [ 0 ]
LOD [ 1 ], Stack:[ 0 ]
LOD [ 2 ], Stack:[ 1 ]
LOD [ 3 ], Stack:[ 2 ]
SHR(U) [ 1 ], 8
MOV [ 0 ], [ 1 ]
ADD [ 0 ], [ 2 ]
PUSH ( 95 << 24 )+( 188 << 16 )+( 189 << 8 )+189
LOD [ 1 ], Stack:[ 3 ]
SUB [ 0 ], [ 1 ]
POP
OR [ 4 ], [ 0 ]
HALT
mem[ 4 ] ^= mem[ 4 ];
mem[ 0 ] ^= mem[ 0 ];
mem[ 1 ]=stk[ 0 ];
mem[ 2 ]=stk[ 1 ];
mem[ 3 ]=stk[ 2 ];
mem[ 1 ] >>= 4 ;
mem[ 1 ] *= 21 ;
mem[ 0 ] = mem[ 1 ];
mem[ 0 ] -= mem[ 3 ];
stk[++top] = ( 29 << 24 )+( 126 << 16 )+( 204 << 8 )+107;
mem[ 1 ]=stk[ 3 ];
mem[ 0 ] -= mem[ 1 ];
top --;
mem[ 4 ] |= mem[ 0 ];
mem[ 0 ] ^= mem[ 0 ];
mem[ 1 ]=stk[ 0 ];
mem[ 2 ]=stk[ 1 ];
mem[ 3 ]=stk[ 2 ];
mem[ 3 ] >>= 8 ;
mem[ 3 ] *= 3 ;
mem[ 0 ] = mem[ 3 ];
mem[ 0 ] += mem[ 2 ];
stk[++top] = ( 96 << 24 )+( 121 << 16 )+( 121 << 8 )+124;
mem[ 1 ]=stk[ 3 ];
mem[ 0 ] -= mem[ 1 ];
top --;
mem[ 4 ] |= mem[ 0 ];
mem[ 0 ] ^= mem[ 0 ];
mem[ 1 ]=stk[ 0 ];
mem[ 2 ]=stk[ 1 ];
mem[ 3 ]=stk[ 2 ];
mem[ 1 ] >>= 8 ;
mem[ 0 ] = mem[ 1 ];
mem[ 0 ] += mem[ 2 ];
stk[++top] = ( 95 << 24 )+( 188 << 16 )+( 189 << 8 )+189;
mem[ 1 ]=stk[ 3 ];
mem[ 0 ] -= mem[ 1 ];
top --;
mem[ 4 ] |= mem[ 0 ];
得出以下约束条件:
x / 16 * 21 - z == ( 29 << 24 )+( 126 << 16 )+( 204 << 8 )+107
z / 256 * 3 + y == ( 96 << 24 )+( 121 << 16 )+( 121 << 8 )+124
x / 256 + y == ( 95 << 24 )+( 188 << 16 )+( 189 << 8 )+189
x & 0xFF == 0x5E
y & 0xFF000000 == 0x5E0000
z & 0xFF == 0x5E
嗯这些东西丢Z3一解即可
[CTF/Reverse] [GUET2018] encrypt
老样子, IDA
三步处理,第一步输入无关,可以动调获得栈里的数据
第二步是把预处理的数组和输入做第一次变换,然后把输入做第二次变换赋给Dst
Dst最后和目标内存比对.
先动调拿到预处理数组(后附)
额有好多0x0, 有一点点诡异但是暂时先不管它.
回头看第二次变换, 三位变四位, 不足补 =, 好明显的魔改Base64
Dst[0] = Input[0][High 6] + 0x3D
Dst[1] = Input[1][High 4] | (Input[0][Low 2] << 4) + 0x3D
Dst[2] = Input[2][High 2] | (Input[1][High 4] << 2) + 0x3D
Dst[3] = Input[2][Low 6] + 0x3D
我们回头去看第一个变换, 第一个变换是一个固定的异或,可知异或的值与输入无关,我们可以写个脚本找出来每次异或的值, 如果你熟悉常见算法, 应该很容易能看得出来这是一个RC4的打乱Sbox和加密的过程.
#include <cstdio>
unsigned char Table[] =
{
0xB0, 0x31, 0x75,
0x70, 0xF8, 0xDF, 0x07, 0x3C,
0x78, 0x71, 0x50, 0x29, 0x2C,
0x16, 0x69, 0x12, 0xC8, 0x2B,
0x3B, 0x7F, 0xB2, 0xE7, 0x4B,
0x68, 0x8C, 0xC5, 0xA6, 0x15,
0x03, 0x58, 0x47, 0x04, 0x13,
0x8D, 0x87, 0x26, 0x09, 0xED,
0x17, 0x8A, 0xC2, 0xF2, 0x43,
0xC0, 0xAC, 0x59, 0x97, 0xF5,
0x3F, 0x67, 0x5E, 0x39, 0x86,
0xD5, 0x72, 0x61, 0xDA, 0xF7,
0x01, 0x05, 0x8B, 0xC3, 0xB1,
0x77, 0xAF, 0x1D, 0x30, 0xC6,
0x45, 0x0E, 0x5F, 0xEE, 0xAE,
0xF0, 0x28, 0xCE, 0xCD, 0xA7,
0x9B, 0x2A, 0x19, 0x48, 0x08,
0x44, 0x20, 0xFE, 0x6D, 0xB5,
0x2E, 0x6A, 0xF1, 0x34, 0xBC,
0x1E, 0x3E, 0xCC, 0x41, 0x92,
0xD8, 0xBD, 0xA5, 0xE8, 0x4D,
0x0A, 0x49, 0x0D, 0xA2, 0xFA,
0x62, 0x74, 0xD4, 0x83, 0x96,
0x94, 0x3D, 0xCB, 0x18, 0x63,
0x99, 0x46, 0xCA, 0xB7, 0x8E,
0xCF, 0xFB, 0xA3, 0x6C, 0x7E,
0x51, 0x27, 0x60, 0x9A, 0x11,
0xF3, 0x5C, 0x6E, 0xBA, 0x42,
0x76, 0x2F, 0xEF, 0xBF, 0x21,
0xAA, 0xE4, 0xD6, 0x1B, 0x55,
0x7D, 0xBE, 0xEA, 0xD3, 0x10,
0xF4, 0xC7, 0x4A, 0x23, 0x79,
0x84, 0xA4, 0x1C, 0xAB, 0x14,
0xDB, 0x4C, 0x3A, 0xB8, 0x52,
0xEC, 0x37, 0x38, 0xB6, 0xD2,
0xA0, 0x5A, 0x5B, 0x98, 0x66,
0x54, 0x9E, 0x4E, 0x4F, 0xB4,
0xC4, 0xC9, 0xD0, 0x25, 0x9C,
0x80, 0xDE, 0x2D, 0x06, 0x22,
0x0B, 0x91, 0x6B, 0x9F, 0xF6,
0xE6, 0xE2, 0xC1, 0x0F, 0x93,
0x90, 0x7B, 0x9D, 0x8F, 0xDD,
0xE5, 0x65, 0x35, 0xAD, 0xA9,
0xDC, 0x82, 0xBB, 0x00, 0x53,
0xD1, 0xA8, 0x33, 0xE9, 0x40,
0x1A, 0xFF, 0xA1, 0x95, 0x36,
0xD9, 0xEB, 0x89, 0xE3, 0x7C,
0x73, 0x85, 0x88, 0x7A, 0xE0,
0xFD, 0x64, 0x0C, 0x57, 0x32,
0xB3, 0xB9, 0x1F, 0xD7, 0xFC,
0x81, 0xE1, 0x02, 0xF9, 0x5D,
0x56, 0x6F, 0x24
};
unsigned char Target[] =
{
0x5A, 0x60, 0x54, 0x7A, 0x7A, 0x54, 0x72, 0x44, 0x7C, 0x66,
0x51, 0x50, 0x5B, 0x5F, 0x56, 0x56, 0x4C, 0x7C, 0x79, 0x6E,
0x65, 0x55, 0x52, 0x79, 0x55, 0x6D, 0x46, 0x6B, 0x6C, 0x56,
0x4A, 0x67, 0x4C, 0x61, 0x73, 0x4A, 0x72, 0x6F, 0x5A, 0x70,
0x48, 0x52, 0x78, 0x49, 0x55, 0x6C, 0x48, 0x5C, 0x76, 0x5A,
0x45, 0x3D
};
int main() {
int i1 = 0, i2 = 0;
for(int i = 0; i < 52/4*3; i ++ ) {
i1 ++;
int t1 = Table[i1];
i2 = (i2+t1)&0xff;
int t2 = Table[i2];
Table[i1] = t2;
Table[i2] = t1;
printf("input[%d] ^= %d AND i1 = %d, i2 = %d\n", i, Table[(t1+t2)&0xff], i1, i2);
}
}
最后Exp
Xor_array = [
0x10, 0x59, 0x9c, 0x92, 0x06,
0x22, 0xcf, 0xa5, 0x72, 0x1e,
0x45, 0x6a, 0x06, 0xcb, 0x08,
0xc3, 0xe4, 0x49, 0x5a, 0x63,
0x0c, 0xdf, 0xf6, 0x5f, 0x08,
0x28, 0xbd, 0xe2, 0x10, 0x15,
0x1f, 0x6e, 0xaa, 0x5a, 0xca,
0xec, 0x80, 0xaf, 0x9b, 0x16,
0xbb, 0x3d, 0x13, 0x2f, 0x6a,
0xa4, 0xc7, 0x2e, 0xbc, 0x4b,
0x60, 0x9a
]
Dst = [
0x5A, 0x60, 0x54, 0x7A, 0x7A, 0x54, 0x72, 0x44, 0x7C, 0x66,
0x51, 0x50, 0x5B, 0x5F, 0x56, 0x56, 0x4C, 0x7C, 0x79, 0x6E,
0x65, 0x55, 0x52, 0x79, 0x55, 0x6D, 0x46, 0x6B, 0x6C, 0x56,
0x4A, 0x67, 0x4C, 0x61, 0x73, 0x4A, 0x72, 0x6F, 0x5A, 0x70,
0x48, 0x52, 0x78, 0x49, 0x55, 0x6C, 0x48, 0x5C, 0x76, 0x5A,
0x45, 0x3D
]
Flag, cur = [0] * 100, 0
flag = ""
for i in range(0, len(Dst), 4) :
Flag[cur] = ((((Dst[i] - 0x3D) & 0x3F) << 2) | (((Dst[i+1] - 0x3D) & 0x30) >> 4)) ^ Xor_array[cur]
Flag[cur+1] = ((((Dst[i+1] - 0x3D) & 0xF) << 4) | (((Dst[i+2] - 0x3D) & 0x3C) >> 2)) ^ Xor_array[cur+1]
Flag[cur+2] = ((((Dst[i+2] - 0x3D) & 0x3) << 6) | (((Dst[i+3] - 0x3D) & 0x3F) )) ^ Xor_array[cur+2]
cur += 3
for i in Flag :
print(chr(i), end="")
[CTF/Reverse] [QCTF2018] BabyRe
可恶! 事 Rust 逆向
这个 subA110 事真正的主函数, 不能反编译
主函数逻辑好像还可以, 我们主要关注cmp
loc_A1B5 有一个 failed read的报错, 猜测这里是读入结束, 15CD0 可能是读入函数, 进而猜测sp+1E8+var_158应该是输入的地址
A259 有一个 RAX和32的cmp, 失败跳到最后, 猜测应该是长度, 那么关键函数就是右侧这一个分支了, 这个分支十节竟然调用了十个函数
看了一会没什么头绪,没能分析出来这些函数传参到底有什么规律, 输入一些东西动调一下
在A273打一个断点, 我晕, 这输入怎么不是在这儿?
回头在15CD0打个断点, 没错, 它就是输入, 可是我输入之后, 我的输入呢???? 我翻了一年,死活没在栈里和内存里找到输入, 搜索也没有什么用, 然后发现是搜索方式的问题, 应该向上搜索,在6010处找到输入...(实际可能因为基址不同地址不同)
可是... 找倒是找到了, 它一点也没动... 但是结合地址可以推测出来栈里存放的也是地址, 这应该是个双间址, 我们可以通过这个来找到变换之后的地址, ESP+1E8+var158 确实是输入的地址. 我们跟两个函数看看它到底对输入干了什么
终于, 我们在 A110 + 232 这个地址的函数参数里发现了 rsp+1E8+varB8 中存放了 7FA3608B0A0, 找到了这个变换到底特么的在哪
这时我多少有点破防,不过第一个变换只是四位一组做了一个轮换(1 2 4 3), 还好, 后面还有三个代码块, 猜测应该还有几次变换
下一个代码块后, 变成了
0x4A, 0x53, 0x9C, 0xC3,
0x4E, 0x57, 0xA0, 0xC7,
0x52, 0x5B, 0xA4, 0xCB,
0x56, 0x5F, 0xA8, 0xCF,
0x5A, 0x63, 0xAC, 0xD3,
0x5E, 0x67, 0xB0, 0xD7,
0x68, 0x6B, 0xBA, 0xDB,
0x6C, 0x75, 0xBE, 0xE5
很显然,竖着两个差了4, 和最开始的是一样的, 这时候就该猜了,众所周知,逆向是6分肝4分猜
四位应该是分别加 0x09, 0x12, 0x58, 0x81
然后变成了
0x52, 0xD4, 0x39, 0x3C,
0x72, 0xD5, 0x41, 0x7C,
0x92, 0xD6, 0x49, 0xBC,
0xB2, 0xD7, 0x51, 0xFC,
0xD2, 0xD8, 0x59, 0x3D,
0xF2, 0xD9, 0x61, 0x7D,
0x43, 0xDA, 0x75, 0xBD,
0x63, 0x5D, 0x7D, 0x5E
超, 这怎么猜,不过有一个很明显的是第四位,把两个16进制位换过来了, 猜测还是4个为一组,然后每个都有不同的变换方式,其中第四个位就是 i = (i >> 4) | (i <<4) & 0xFF
有了这个导向其实就能猜了,我们把16进制都变成bin看, 看看一组到底按位进行了怎么样的变换
先看4A变52和4E变72
0100 1010 -> 0101 0010
0100 1110 -> 0111 0010
看上去像是把整个数字倒过来但是到第三个就不对了,
0x52 -> 0x92 :
0101 0010 -> 1001 0010
看上去像是4567 8321 这样, 再看一个
0x6C -> 0x63 :
0110 1100 -> 0110 0011
这样看上去像是 4567 8123, 循环左移三位(右移五位),这看起来更靠谱一点
再看第二位 0x53 0x57 变 0xD4, 0xD5
0101 0011 -> 1101 0100
0101 0111 -> 1101 0101
循环右移两位(左移六位)
同理第三位9c->39 : 1001 1100 -> 0011 1001 循环左移一位
第四位就是循环左移四位了
在函数9F50处出现了wrong, 猜测这个函数就是判定函数了, 我们重来一次然后步进, 看看是怎么对比的
terminate之后先直接去看了看9F50, 这..这有点太明显了吧, 就是明文比对就好了
mov byte ptr [rax], 0DAh
mov byte ptr [rax+1], 0D8h
mov byte ptr [rax+2], 3Dh ; '='
mov byte ptr [rax+3], 4Ch ; 'L'
mov byte ptr [rax+4], 0E3h
mov byte ptr [rax+5], 63h ; 'c'
mov byte ptr [rax+6], 97h
mov byte ptr [rax+7], 3Dh ; '='
mov byte ptr [rax+8], 0C1h
mov byte ptr [rax+9], 91h
mov byte ptr [rax+0Ah], 97h
mov byte ptr [rax+0Bh], 0Eh
mov byte ptr [rax+0Ch], 0E3h
mov byte ptr [rax+0Dh], 5Ch ; '\'
mov byte ptr [rax+0Eh], 8Dh
mov byte ptr [rax+0Fh], 7Eh ; '~'
mov byte ptr [rax+10h], 5Bh ; '['
mov byte ptr [rax+11h], 91h
mov byte ptr [rax+12h], 6Fh ; 'o'
mov byte ptr [rax+13h], 0FEh
mov byte ptr [rax+14h], 0DBh
mov byte ptr [rax+15h], 0D0h
mov byte ptr [rax+16h], 17h
mov byte ptr [rax+17h], 0FEh
mov byte ptr [rax+18h], 0D3h
mov byte ptr [rax+19h], 21h ; '!'
mov byte ptr [rax+1Ah], 99h
mov byte ptr [rax+1Bh], 4Bh ; 'K'
mov byte ptr [rax+1Ch], 73h ; 's'
mov byte ptr [rax+1Dh], 0D0h
mov byte ptr [rax+1Eh], 0ABh
mov byte ptr [rax+1Fh], 0FEh
直接写脚本即可
target = [
0x0DA, 0x0D8, 0x3D, 0x4C, 0x0E3, 0x63, 0x97, 0x3D, 0x0C1, 0x91, 0x97, 0x0E, 0x0E3, 0x5C, 0x8D, 0x7E, 0x5B, 0x91, 0x6F, 0x0FE, 0x0DB, 0x0D0, 0x17, 0x0FE, 0x0D3, 0x21, 0x99, 0x4B, 0x73, 0x0D0, 0x0AB, 0x0FE
]
def ROL(x, n) :
return ((x << n) | (x >> (8-n))) & 0xFF
for i in range(0, len(target), 4) :
target[i] = ROL(target[i], 5) - 0x09
target[i+1] = ROL(target[i+1], 2) - 0x12
target[i+2] = ROL(target[i+2], 7) - 0x58
target[i+3] = ROL(target[i+3], 4) - 0x81
target[i], target[i+1], target[i+2], target[i+3] = \
target[i+1], target[i+3], target[i], target[i+2]
for i in target :
print(chr(i&0xFF), end="")
# QCRF{Rss4_/s_fsn4nb_1nr3r3qt1ne}
多少有点像了,但是肯定不对, 感觉是每组第三位错了, 这个R应该是T, 哦, 犯蠢了, 0xA-0x3 = 7 不等于 9
Flag :QCTF{Rus4_1s_fun4nd_1nt3r3st1ng}