这是一个 64 位 ELF 文件,在 Linux 中打开了一下没什么提示,随便输入几个字符提示 wrong,还是用 ida 打开它吧,打开之后直接看到主函数

其中有一个 for 循环进行了很多轮异或运算,而这个 sub_402219 函数正是参与运算的值,而且在后面它又作为函数单独出现,但是它又打不开,在汇编代码中可以找到这个函数的地址,发现这里有一大堆数据,所以我们需要把这一段数据给还原

我们选中 402219 的地址段,按 D 将其转化为数据,然后再通过 idc 脚本将其还原,脚本如下

然后选择数据之后按 C 分析数据,点击 force 强制执行,再将其转化为函数就可以了,这样之后我们的 sub_402219 函数就正常了,就可以正常打开了

在打开之前我们先用 findcrypt 看一下有哪些加密

有 md5 和 AES 两种加密,我们再看看 sub_402219 函数

# sub_402219 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall sub_402219(__int64 a1)
{
unsigned int v2; // [rsp+18h] [rbp-D8h]
int i; // [rsp+1Ch] [rbp-D4h]
char v4[200]; // [rsp+20h] [rbp-D0h] BYREF
unsigned __int64 v5; // [rsp+E8h] [rbp-8h]

v5 = __readfsqword(0x28u);
sub_400A71(v4, &unk_603170);
sub_40196E(v4, a1);
sub_40196E(v4, a1 + 16);
v2 = 1;
for ( i = 0; i <= 31; ++i )
{
if ( *(i + a1) != byte_6030A0[i] )
v2 = 0;
}
return v2;
}

可以看到三个关键函数,第一个函数应用了刚刚在主函数中可以看到的 & unk_603170 这一组数据,但我们不知道,可以在后面的调试中获取

在进行三轮加密之后,与 byte_6030A0 数组进行对比,byte_6030A0 数组的数据如下

1
2
3
4
0xBC, 0x0A, 0xAD, 0xC0, 0x14, 0x7C, 0x5E, 0xCC, 0xE0, 0xB1, 
0x40, 0xBC, 0x9C, 0x51, 0xD5, 0x2B, 0x46, 0xB2, 0xB9, 0x43,
0x4D, 0xE5, 0x32, 0x4B, 0xAD, 0x7F, 0xB4, 0xB3, 0x9C, 0xDB,
0x4B, 0x5B

而这段加密是 AES 加密(小声 bb:我还没有学),不过通过刚才的 findcrypt 可以分析出来这里是 AES 加密,第一个函数就是生成轮密钥,而且那个未知数组是初始密钥,第二个和第三个函数分别是对输入字符串的前后 16 位进行 AES 加密

# sub_40207B 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 __fastcall sub_40207B(__int64 a1)
{
char v2[16]; // [rsp+10h] [rbp-50h] BYREF
__int64 v3; // [rsp+20h] [rbp-40h] BYREF
__int64 v4; // [rsp+30h] [rbp-30h] BYREF
__int64 v5; // [rsp+40h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+58h] [rbp-8h]

v6 = __readfsqword(0x28u);
sub_401CF9(&BASE64_table_603120, 0x40uLL, v2);
sub_401CF9(&unk_603100, 0x14uLL, &v3);
sub_401CF9(&Prime_Constants_char_6030C0, 0x35uLL, &v4);
sub_401CF9(MD5_Constants_4025C0, 0x100uLL, &v5);
sub_401CF9(v2, 0x40uLL, a1);
return __readfsqword(0x28u) ^ v6;
}

在这个函数中,sub_401CF9 函数将 base64 表传入,并进行了加密

可以识别出这是 MD5 加密,在加密之后,被储存在 v2 中,之后又在 14 行进行了一次 MD5 加密后储存在 a1,也就是我们要找到的 & unk_603170 数组中

# 总体思路

在第一个函数中将 base64 表进行了两次 MD5 加密,然后以其作为第二个函数中的 AES 加密的初始密钥,在进行 AES 加密,加密后的结果已经储存在 byte_6030A0 数组中且是可见的,而密钥是可以通过调试获取的,但因为这道题它是一个 ELF 文件,在 windows 是无法调试的,所以只能通过 Linux 进行远程调试,获取数据之后,用脚本进行 AES 解密就可以得到 flag

# 远程调试

我用的 Linux 系统是 kali,第一次调试,先要把 linux_server 文件放到 kali 里面,然后再直接把文件放到 kali 的桌面上,然后复制一下它的文件位置,然后我们在桌面打开 kali 的终端,并且打开 Linux_server64 文件,之后回到 windows 的 ida 中,选择 debugger 为 remote Linux debugger,之后输入文件在 kali 中位置和虚拟机的 ip 即可开始调试了

这里有一个点要注意,就是我们如果在函数的开始部分就下断点,然后通过输入一个 32 长度的字符串的话,是无法绕过 if 的判断的,还是会退出程序,所以我们就在又在 if 判断的前后分别下一个断点,在运行到 if 之前的断点的时候再 ctrl+F7 跳过到下一个断点,通过这个方式来绕过,绕过之后再单步调试到 & unk_603170 所在函数,然后再进入汇编代码段获取数据就可以获取到它的数据了

# 脚本

在知道了密钥之后和最终密文之后,就是通过脚本进行 AES 解密获取明文 flag 了,我还不会 AES 所以就参照了大佬的脚本,不过不知道为什么我运行不了这个脚本,不过有的别的脚本可以运行,这里我就把两个脚本都放在这吧

1
2
3
4
5
6
from Crypto.Cipher import AES
import codecs

aes = AES.new(decode_hex('CB8D493521B47A4CC1AE7E62229266CE')[0], AES.MODE_ECB)
print(aes.decrypt(decode_hex('BC0AADC0147C5ECCE0B140BC9C51D52B46B2B9434DE5324BAD7FB4B39CDB4B5B')[0]))

1
2
3
4
5
6
7
8
9
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
mode = AES.MODE_ECB
key = b'\xcb\x8d\x49\x35\x21\xb4\x7a\x4c\xc1\xae\x7e\x62\x22\x92\x66\xce'
text = b'\xBC\x0A\xAD\xC0\x14\x7C\x5E\xCC\xE0\xB1\x40\xBC\x9C\x51\xD5\x2B\x46\xB2\xB9\x43\x4D\xE5\x32\x4B\xAD\x7F\xB4\xB3\x9C\xDB\x4B\x5B'
cryptos = AES.new(key, mode)
cipher_text = cryptos.decrypt(text)
print(b2a_hex(cipher_text))

注意一下第二个脚本得到的数据还要十六进制转 ASCII 一下

最后得到 flag {924a9ab2163d390410d0a1f670}

题目来源:BUUCTF 在线评测 (buuoj.cn)——re3