上面一托混淆,左边似乎是三个东西相乘
单独取出最左边一托打印,可以得到大数组
接下来要解密,原代码非常混乱,我们先整理一下,简单去混淆
print
(
all
(
[
[data][a][d] == e
for a, b in enumerate
(
[
[int(a) for a in bin(sum((ord(b) << 6 ^ 4102 - a ^ c for c in b'beginCTF'))).replace('0b', '')]
for a, b in enumerate
(
input('[+]Flag: ')
)
]
)
for d,e in iter #迭代器
(
(enumerate(b))
)
]
)
)
a应该是行数索引,b是输入的字符,经过bin(sum((ord(b) << 6 ^ 4102 - a ^ c for c in b'beginCTF')))
转化成一个二进制数,应该要和大数组里对应行里的一行数字相同
data = [[1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0], [1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0], [1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0], [1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0], [1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0], [1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0], [1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0], [1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0], [1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0], [1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0], [1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], [1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0], [1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0], [1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0], [1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0], [1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0], [1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0], [1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0], [1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0], [1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0], [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0], [1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0], [1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0], [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0], [1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0], [1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0], [1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0], [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0], [1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0], [1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0], [1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0], [1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0], [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0], [1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0], [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0], [1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0]]
# 本文作者:Yuro@Yuro的博客
# 本文链接:https://yurogod.github.io/ctf/events/BeginCTF-2024/
# 版权声明:本站所有文章除特别声明外,均采用 (CC)BY-NC-SA 许可协议。转载请注明出处!
for i, lst in enumerate(data): #i是索引,lst是子序列(每一行)
for j in range(32, 128): #爆破有效字符
n = int("".join([str(x) for x in lst]), 2) #把每一行的数字连起来变成二进制,再转换为十进制
if sum((j << 6 ^ 4102 - i ^ key for key in b'beginCTF')) == n:
print(chr(j), end="")
break
#begin{Y@u_aRe_g00d_aT_play1ng_witH_sNake3}
源码里搜一下score
改一下,没吊用
中间有一坨,后来才知道是ob混淆
Obfuscator.io Deobfuscator
用网站去混淆
救赎之道,就在其中 begin{y0u_re4l1y_g07_1337427_74e96c14c0c79977b52a15ba559bcf30}
(似乎也可以在case6那里改分数)
start函数往下翻,有一坨赋值,把数据按C键转成代码,去掉花(可能是花搞不清楚)——也并不能还原伪代码,不过可以大致看看
while这里进行了异或,可能是加密逻辑
if这里应该是判断比对密文
照理来说应该动调把加密逻辑调出来,但是太麻烦,不如先根据密文猜测
动调取一下密文,寻找memcmp
函数调用的位置,找cmp
指令
67行if下个断点,一路F7
进去,rdx,rcx寄存器里好像有字符串,但是没到cmp,往下走几步
拼接成了字符串
密文和flag头单字节异或一下
结合伪代码中的51
可以猜测每一位异或从51递增的数
#superguesser wp
enc = [0x51, 0x51, 0x52, 0x5F, 0x59, 0x43, 0x5D, 0x5F, 0x59, 0x49,
0x5A, 0x59, 0x56, 0x2E, 0x26, 0x1D, 0x2A, 0x37, 0x1A, 0x27,
0x29, 0x17, 0x28, 0x24, 0x2A, 0x38, 0x25, 0x21, 0x3D, 0x0F,
0x32, 0x3A, 0x3C, 0x3D, 0x36, 0x33, 0x2A]
exm = 'begin{'
flag = ''
#for i in range(6):
# print(enc[i]^ord(exm[i]))
for i in range(len(enc)):
flag += chr(enc[i]^(i+51))
print(flag)
#begin{debugging_is_an_anathor_choice}
具体的原理在官方WP里讲了,看不太懂,就记录一下大致流程吧
主函数看起来是个tea,但是题目都告诉我们not main,说明这个不对
找一找有个Hander函数,一般是异常处理时跳转的函数
有个xxtea
这里应该是真实的加密过程
应该是key
然后就是调密文,动调调不了,有反调试
请教dalao后得知关键代码在__scrt_common_main_seh
的initterm
中
这个函数调用了这两个地址之间的所有函数,参数是两个函数指针
一个个看,点进第三个可以看到两个疑似密文的地址,点进第四个则是触发Handler的部分
第四个sub_51010
实际上就是反调试的位置,也是Handler
的调用位置
可以发现,一旦检测到被调试,Handler就不会触发
第三个sub_51000
则是:
不知道哪一个是密文,X查一下引用
unk_48503C
没有其他引用,unk_485018
引用分别在Handler和main,有可能是main里的假密文
再看一下汇编,这里把offset unk_48503C
和offset unk_485018
的地址进行了异或。
另一处Handler内的unk_485018
引用则再次进行了这个操作,也就是说等于没异或
所以如果真逻辑在Handler内,unk_485018
就是没用的数据
真正的密文就是unk_48503C
由于TEA系加密每次需要的原始数据是2个32位无符号整数,所以取数据时注意一下
也就是8个数,两个一组,加密四轮
密钥是true
抄个XXTEA脚本跑一下——乱码
可能主函数TEA不是假的,又进行了TEA加密
看一下__scrt_common_main_seh
发现调用Handler在main之前,可能XXTEA之后又进行了TEA
查一下明文的引用
可以看到main当中的明文是从Handler中引用的,与之前的猜想呼应
只不过tea加密用的是另一套密钥fake
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2)+(y>>3^z<<4))^((sum^y)+(key[(p&3)^e]^z)))
void btea(uint32_t* v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1)
{
rounds = 6 + 52 / n; //这里可以说是预定义值,n=2是rounds=32
sum = 0;
z = v[n - 1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) //注意这里的p是从0~n-1
{
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX; //这里的MX中传入的p=n-1
} while (--rounds);
}
else if (n < -1)
{
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) //注意这里的p是从n-1~0,和上面是反过来的
{
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX; //这里的MX中传入的 p=0
sum -= DELTA;
} while (--rounds);
}
}
void detea(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0; v[1] = v1;
}
int main()
{
uint32_t v[8] = { 0xCFBE0F1B, 0x05F3083F, 0x4220E43B, 0x3383AFEE, 0xFA3237CE, 0xECADA66E, 0xA8D47CA7, 0xEFC51077 };
uint32_t k[4] = { 116,114,117,101 };//true
uint32_t fk[4] = { 0x66,0x61,0x6B,0x65 };//fake
int i, n = 8;
btea(v, -n, k);//负号是解密
for (i = 0; i < 8; i += 2)
detea(v + i, fk);
printf("%s", v);//begin{not_main_is_matter!}
return 0;
}