very_easy[该题未做出]

根据矩阵杯2024 Re wp 前两题-CSDN博客这篇博客,遇到类似的题需要下载quickJS后在linux系统上make编译,并以

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
#include "quickjs-libc.h"

const uint32_t qjsc_size = 3881;

const uint8_t qjsc[3884] = {
the_data
};

int main(int argc, char **argv)
{
JSRuntime *rt;
JSContext *ctx;
rt = JS_NewRuntime();
ctx = JS_NewContextRaw(rt);
JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
JS_AddIntrinsicBaseObjects(ctx);
JS_AddIntrinsicDate(ctx);
JS_AddIntrinsicEval(ctx);
JS_AddIntrinsicStringNormalize(ctx);
JS_AddIntrinsicRegExp(ctx);
JS_AddIntrinsicJSON(ctx);
JS_AddIntrinsicProxy(ctx);
JS_AddIntrinsicMapSet(ctx);
JS_AddIntrinsicTypedArrays(ctx);
JS_AddIntrinsicPromise(ctx);
JS_AddIntrinsicBigInt(ctx);
js_std_add_helpers(ctx, argc, argv);
js_std_eval_binary(ctx, qjsc, qjsc_size, 0);
js_std_loop(ctx);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}

但是我编译后报错version 2 expected 67. 然后就放弃了

so_easy

一个so的算法逆向

遇到so可以用多种架构的都看一下,防止某一个反编译出来比较奇怪。

逻辑大致是这样:

image-20240716091801464

这种每个都要循环255次的,爆破怕是不现实,所以一定要找到两种操作对结果的不同影响。

可以发现:

input最高位为0时(input为正数),仅左移一位,最后0位为0
input最高位为1时(input为负数),左移再异或,最后0位为1

根据这一点来判断大小然后写算法逆向

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
#include<iostream>

long long dec(long long input) {
int loop = 0xff;
long long last = input;
long long mask = 1LL << 63;
int check = 0;
do
{

if (last & 1) {
last ^= 0x71234EA7D92996F5;
last = ((unsigned long long)last >> 1) | mask;
check++;

}
else {
last = ((unsigned long long)last >> 1);
}

loop -= 1;
} while (loop);
return last;
}

long long encode(long long input) {
int loop = 0xff;
long long last = input;
int check = 0;
do
{

if (last >= 0)
last <<= 1;
else
last = (last << 1) ^ 0x71234EA7D92996F5LL;
loop -= 1;
} while (loop);
return last;

}

int main(void) {

long long enc[4] = {
0x540A95F0C1BA81AE, 0xF8844E52E24A0314, 0x09FD988F98143EC9, 0x3FC00F01B405AD5E
};
for (int i = 0; i < 4; i++)
{
enc[i] = dec(enc[i]);
std::cout << std::hex << enc[i] << std::endl;
}
std::cout << (char*)enc << std::endl;
}
// WKCTF{2366064af80f669c2cb9519ab}

quite_easy

这个题最后连蒙带猜出来了QAQ

它hook了strcmp函数,在里面做加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for ( j = 0; j < 16; ++j )
{
v4 = *(char *)get_byte(j);
v5 = *(char *)get_byte(j + 32) ^ v4;
v6 = *(_BYTE *)get_byte(j);
v7 = (_BYTE *)get_byte(j + 32);
sub_4C13E3(~(*v7 & v6) & v5);
}
for ( k = 16; k < 32; ++k )
{
v8 = *(char *)get_byte(k);
v9 = *(char *)get_byte(k - 16) ^ v8;
v10 = *(_BYTE *)get_byte(k);
v11 = (_BYTE *)get_byte(k - 16);
sub_4C13E3(~(*v11 & v10) & v9);
}
for ( m = 0; m < 32; ++m )
{
v14 = (_BYTE *)get_byte(m);
*v14 -= *(_BYTE *)(m + a2);
}

加密大概有这三个,sub_4C13E3专门用来放置内容到output

前面几个才是加密,这个加密方式当时是猜的,有点抽象,调不动,复现的时候验证了一下发现确实就是它写的那样加密的

由于调试时可以发现,它把随机生成的16位值拼在flag后面了,所以可以调试得到第一轮的参数,第二轮的参数就是flag的值,然后最后一轮用了传入的第二个字符串,就是那个假flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from prism import *
last = [0x80, 0xD3, 0x6F, 0xFF, 0x15, 0x03, 0x98, 0x8C, 0xB4, 0x5B, 0x96, 0xC0, 0x59, 0xAC, 0x18, 0xDF, 0x2D, 0xCE, 0x3F, 0xFB, 0xC4, 0xED, 0xD8, 0xD2, 0xA8, 0x2D, 0xF8, 0x23, 0x9F, 0x22, 0x25, 0xCE]

mask = [0x66, 0x6C, 0x61, 0x67, 0x7B, 0x65, 0x64, 0x31, 0x64, 0x36, 0x36, 0x35, 0x65, 0x36, 0x35, 0x31, 0x36, 0x61, 0x33, 0x37, 0x61, 0x62, 0x30, 0x39, 0x66, 0x30, 0x62, 0x37, 0x61, 0x34, 0x30, 0x7D]
rand = [0xB1, 0x74, 0x93, 0x32, 0xD6, 0x13, 0xCC, 0x85, 0x20, 0xA8, 0xF4, 0x96, 0x8A, 0xD2, 0x7D, 0x26]
for i in range(32):
last[i]+=mask[i]

for i in range(16):
last[i]^=rand[i]
for i in range(16,32):
last[i]^=last[i-16]
pl(last)
# WKCTF{08898c40064d1fc4836db94fe}

这里应当先执行第一个解密再执行第二个解密,这样第二轮解密才能用当时异或的值