占个坑

比赛的时候硬刚的catchme,逆完SO找JNI_LOADER之后除了一堆sub函数和一个b64表啥也看不出来,然后直接润了。听大爹说直接findcrypt就能找到AES加密算法,打算做完Ffuction回来再做这个。

Ffunction

解压后发现是一个exe文件和几个dll文件,由导入表知识可知,dll文件存的是一些API,在可执行文件运行到某处的时候会发生一次jmp跳转进而运行导入表中的内容,所以我们对exe文件启用动调,在Output输出框中尝试找到关键的dll文件是哪一个。

可执行文件可以直接试出flag长度为30位,我们按死F8,等程序跑飞时,输入30位测试输入,输出框跳出了load my_plugin.dll,说明关键代码在my_plugin.dll里面。

1658220900498

我们用打开my_plugin.dll,看到有一个叫f的函数,考虑到这个题目名字就是Ffunction,应该是关键函数,点进去看,大概分为两部分,先看第二部分

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
bool __fastcall f(_BYTE *a1, const void *a2)
{
第一部分
___________________________
v15 = 2 * (int)v3 / 8;
if ( v15 > 0 )
{
for ( i = 0i64; i < v15; v12[2 * i++ + 1] = v19 )
{
v17 = v12[2 * i];
v18 = 0;
v19 = v12[2 * i + 1];
v20 = 32i64;
do
{
v18 += 2042207799;
v17 -= (v18 + v19) ^ (dword_180003000 + (v19 >> 5)) ^ (dword_180003004 + 16 * v19);
v19 -= (v18 + v17) ^ (dword_180003008 + (v17 >> 5)) ^ (dword_18000300C + 16 * v17);
--v20;
}
while ( v20 );
v12[2 * i] = v17;
}
}
v21 = memcmp(v12, a2, 2 * (int)v3) == 0; // memcmp不多说
free(v12);
return v21;
}

一眼TEA加密算法,稍微改了改,key可以直接扒出来,密文就是v21 = memcmp(v12, a2, 2 * (int)v3) == 0;这句中的a2

动态调试断点下到函数开始处,可以dump出a2密文,根据后面的tea加密算法可知,密文的格式为unsigned int_32型(8位16进制),所以按D键改成dd格式,dump出前20个数据。

1658221947152

动态调试dll文件的方法就是把调试器改为调用它的可执行exe文件(即改变下面这个图中的Application路径)

1658221769695

然后再看第一部分

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
bool __fastcall f(_BYTE *a1, const void *a2)
{
v2 = a1;
v3 = -1i64;
do
++v3;
while ( a1[v3] );
if ( (int)v3 % 8 )
return 0;
v5 = (int)v3 / 2;
v6 = (int)v3;
if ( (int)v3 / 2 > 0 )
{
v7 = a1;
v8 = &a1[(int)v3 - 1];
do
{
v9 = *v8--;
v10 = *v7;
*v7++ = v9;
v8[1] = v10;
--v5;
}
while ( v5 );
} // 第一个while是把前一半和后一半对称调换
v11 = malloc(2i64 * (int)v3);
v12 = v11;
if ( (int)v3 > 0 )
{
v13 = (char *)v11;
do
{
v13 += 2;
v14 = *v2++ & 0xF0;
*(v13 - 2) = v14;
*(v13 - 1) = *(v2 - 1) & 0xF;
--v6;
}
while ( v6 );
} // 第二个while把原来长度为v3的字符串扩展成 2 * v3,第k位分别 & 0xF0(11110000)填入第 2 * k 位,再 & 0xF(00001111)填入 2 * k + 1 位

这样我们先把第 k 和第 k + 1位用或运算合并,再对称调换就可以了。

放上exp

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define uint32 unsigned int
using namespace std;
uint32 delta = 2042207799;
uint32 enc[22] = {
0x5C15754C, 0x0D1D781E7, 0x501BF173, 0x0CB4DB222,
0x215D61F5, 0x3FCA9EE7, 0x7C76B5C7, 0x0C7DD8CB9,
0x990D23FA, 0x0BAB1AD3, 0x8E12C932, 0x0D307BAF2,
0x0E52DD123, 0x0FBB68F2C, 0x0BDD853E3, 0x892E1E4E,
0x39DD66FA, 0x87FEEC65, 0x307C5E60, 0x340C6C00
};

char flag[100];
uint32 key[4] = {0x0DEADBEEF, 0x0BABEC0FE, 0x0DEADC0DE, 0x0FACEB00C};

void tea_decrypt(int x)
{
uint32 sum = delta * 32;
uint32 v0 = enc[x], v1 = enc[x + 1];
for(int i = 0; i < 32; i++)
{
v1 += (sum + v0) ^ (key[2] + (v0 >> 5)) ^ (key[3] + 16 * v0);
v0 += (sum + v1) ^ (key[0] + (v1 >> 5)) ^ (key[1] + 16 * v1);
sum -= delta;
}
enc[x] = v0;
enc[x + 1] = v1;
}

int main()
{
for(int i = 0; i < 20; i += 2) tea_decrypt(i);
int len = sizeof(enc);
char *buffer = (char *)enc;
for (int i = 0; i < len; i += 2) flag[i >> 1] = buffer[i] | buffer[i + 1];
len >>= 1;
for (int i = 0; i < (len >> 1); ++i) swap(flag[i], flag[len - i - 1]);
for(int i = 0;i < len; i++)printf("%c", flag[i]);
}

动调显示字符长度应该是80,但是这里的sizeof(len) = 88,所以多输出了俩空格(我不理解,但是确实能做。。。)

1658222725282

感觉是个base64解码,解码出来结果是

f}l!a!gC{_Ehmtp10ww_erre_tFt1u

一眼栅栏密码分栏为2:flag{Emp0wer_F1}!!C_ht1w_rettu

还是不对,考虑把后一半flag倒过来,得出最终flag{Emp0wer_F1utter_w1th_C!!}