今天被大佬学弟带着做了一道国外的ctf,质量果然和国内的套娃题不一样,挺有意思的,写个wp纪念一下。
64位elf文件,无壳,用ida64打开,主要算法代码如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
char v4; // al
char v5; // dl
unsigned int v6; // eax
int i; // [rsp+18h] [rbp-E8h]
int j; // [rsp+1Ch] [rbp-E4h]
char dest[100]; // [rsp+20h] [rbp-E0h]
char v10; // [rsp+84h] [rbp-7Ch]
char s1[104]; // [rsp+90h] [rbp-70h]
unsigned __int64 v12; // [rsp+F8h] [rbp-8h]
v12 = __readfsqword(0x28u);
if ( argc > 1 )
{
strncpy(dest, argv[1], 0x64uLL);
v10 = 0;
for ( i = 0; (unsigned int)valid_char((unsigned int)dest[i]); ++i )
{
if ( i )
{
v4 = jumble((unsigned int)dest[i]);
v5 = s1[i - 1] + v4;
v6 = (unsigned int)((s1[i - 1] + v4) >> 31) >> 28;
s1[i] = ((v6 + v5) & 0xF) - v6;
}
else
{
s1[0] = (char)jumble((unsigned int)dest[0]) % 16;
}
}
for ( j = 0; j < i; ++j )
s1[j] += 97;
if ( i == 100
&& !strncmp(
s1,
"adpjkoadapekldmpbjhjhbaghlfldbhjdalgnbeedheenfoeddabpmdnliokcahomdphbcleipfgibjdcgmjcmadaomiakpdjcni",
0x64uLL) )
{
puts("You got the key, congrats! Now xor it with the flag!");
result = 0;
}
else
{
puts("Invalid key!");
result = 1;
}
}
else
{
printf("USAGE: %s [KEY]\n", *argv, envp, argv);
result = 1;
}
return result;
}
大体看一下整体的正向思路是先检查输入的合法化,然后进行第一层加密,最后+97与一串字符串比较,然后根据给的提示把输入与给的字符串进行异或得到flag。接下来一层一层分析加密。
先看vaild_char函数:
signed __int64 __fastcall valid_char(char a1)
{
if ( a1 > 47 && a1 <= 57 )
return 1LL;
if ( a1 <= 96 || a1 > 102 )
return 0LL;
return 1LL;
}
这里是限制输入为十六进制0~f
然后看一下jumble函数:
__int64 __fastcall jumble(char a1)
{
char v2; // [rsp+0h] [rbp-4h]
unsigned __int8 v3; // [rsp+0h] [rbp-4h]
v2 = a1;
if ( a1 > 96 )
v2 = a1 + 9;
v3 = 2 * (v2 % 16);
if ( (char)v3 > 15 )
++v3;
return v3;
}
传入的参数为我们的输入,这里做的变化经过分析发现是在干以下事情:
如果a1<=7,返回2a1,如果7<a1<=0x0Fh,返回2a1+1。
接下来分析第一层加密函数:
for ( i = 0; (unsigned int)valid_char(dest[i]); ++i )
{
if ( i )
{
v4 = jumble(dest[i]);
v5 = s1[i - 1] + v4;
v6 = (unsigned int)((s1[i - 1] + v4) >> 31) >> 28;
s1[i] = ((v6 + v5) & 0xF) - v6;
}
else
{
s1[0] = (char)jumble(dest[0]) % 16;
}
}
v4取出jumble返回的值,v5将输入的s[i-1]与s[i]相加,v6看似很迷茫,但经过动态调试发现v6一直为0,这里我就不管他了。有了v6看作0,s1[i]=v5&0xF,其实这个就等价于s1[i]=v5%16,这样这个加密就很清晰了。
这里还要注意s1[0] = (char)jumble(dest[0]) % 16这个,分析出s1[0]的值是很关键的,下面字符串第一个为a,‘a’-97=0,所以jumble(dest[0]) % 16应该是0,也就是我们第一个输入为0。
第二层加密是一个全体+97,很简单不多赘述。
接下来我写下我的解密脚本:
#include <iostream>
using namespace std;
int rejumble(int a)
{
int v2 = a;
unsigned __int8 v3;
if (v2 < 15) {
v3 = v2 / 2;
}
else {
v3 = (v2 - 1) / 2;
}
return v3;
}
int main()
{
char a[] = { "adpjkoadapekldmpbjhjhbaghlfldbhjdalgnbeedheenfoeddabpmdnliokcahomdphbcleipfgibjdcgmjcmadaomiakpdjcni" };
int b[100] = { 0 };//-97后 0 3 15 9 10 14 0 3 0 15 4 10 11 3 12 15 1 9 7 9 7 1 0 6 7 11 5 11 3 1 7 9 3 0 11 6 13 1 4 4 3 7 4 4 13 5 14 4 3 3 0 1 15 12 3 13 11 8 14 10 2 0 7 14 12 3 15 7 1 2 11 4 8 15 5 6 8 1 9 3 2 6 12 9 2 12 0 3 0 14 12 8 0 10 15 3 9 2 13 8
int c[100] = { 0 };
for (int i = 0; i < 100; i++) {
b[i] = (int)(a[i] - 97);
}
b[0] = 0;
for (int i = 1; i < 100; i++) {
if (b[i] - b[i - 1] < 0 || (b[i] - b[i - 1]) % 2 != 0) {
if (b[i] - b[i - 1] < 0 && (b[i] - b[i - 1]) % 2 != 0) c[i] = rejumble(b[i] + 32 - b[i - 1]);
else c[i] = rejumble(b[i] + 16 - b[i - 1]);
}
else {
c[i] = rejumble(b[i] - b[i - 1]);
}
}
for (int i = 0; i < 100; i++) {
cout << hex << c[i];
}
}
得到
09658219efa384c9147175f3825347315eddb290f2e0c4c3f0e87eb57e3647bb7b6458cc2b381c45f23ec529e77645a23cdd,与flag异或就是flag。
import binascii
Ans1 = "790ce176acf7c2b277040687b23e185b2bb0d0fcc1939bf782db10c1210218dc4b2b3c931a5c2f04ad5aa711d04175920aa0"
Ans2 = "09658219efa384c9147175f3825347315eddb290f2e0c4c3f0e87eb57e3647bb7b6458cc2b381c45f23ec529e77645a23cdd"
ans1 = binascii.unhexlify(Ans1)
ans2 = binascii.unhexlify(Ans2)
key = ""
for i in range(len(ans1)):
key += chr(ans1[i] ^ ans2[i])
print(key)
flag:picoCTF{cust0m_jumbl3s_4r3nt_4_g0Od_1d3A_db877006}