记录了我目前做过的逆向题目和题解,以做题平台和比赛为分类依据

BUUCTF

第八题 “helloword”

既然是第一道题,那么写一个最简单的应该是很合理的

下载题目文件后,发现是一个.apk文件,把它拖入jadx,找到mainactivity,点开就是明晃晃的flag了

小结:题目确定没有拼错???

第十题 “SimpleRev”

下载文件后,拖入查壳软件中

没有壳,直接一套流程,就找到了伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
tcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char v4; // [rsp+Fh] [rbp-1h]

while ( 1 )
{
while ( 1 )
{
printf("Welcome to CTF game!\nPlease input d/D to start or input q/Q to quit this program: ");
v4 = getchar();
if ( v4 != 100 && v4 != 68 )
break;
Decry();//关键函数,生成flag算法和比较算法
}
if ( v4 == 113 || v4 == 81 )
Exit("Welcome to CTF game!\nPlease input d/D to start or input q/Q to quit this program: ", argv);
puts("Input fault format!");
v3 = getchar();
putchar(v3);
}
}

打开Decry()函数

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
unsigned __int64 Decry()
{
char v1; // [rsp+Fh] [rbp-51h]
int v2; // [rsp+10h] [rbp-50h]
int v3; // [rsp+14h] [rbp-4Ch]
int i; // [rsp+18h] [rbp-48h]
int v5; // [rsp+1Ch] [rbp-44h]
char src[8]; // [rsp+20h] [rbp-40h] BYREF
__int64 v7; // [rsp+28h] [rbp-38h]
int v8; // [rsp+30h] [rbp-30h]
__int64 v9[2]; // [rsp+40h] [rbp-20h] BYREF
int v10; // [rsp+50h] [rbp-10h]
unsigned __int64 v11; // [rsp+58h] [rbp-8h]

v11 = __readfsqword(0x28u);
*(_QWORD *)src = 0x534C43444ELL;//LL 是编程语言(如 C、C++ 等)中用于表示该数字为 long long 类型的后缀,并非数字本身的一部分,在转换成字符串时可以忽略,注意:数据在内存中是小端顺序,高位在高地址处,低位在低地址处,故实际的字符顺序应为'0x4e44434c53'经过16字符转换为ASCII码转换后字符为'NDCLS'
v7 = 0LL;
v8 = 0;
v9[0] = 0x776F646168LL;//同理转换
v9[1] = 0LL;
v10 = 0;
text = join(key3, (const char *)v9);
strcpy(key, key1);
strcat(key, src);
v2 = 0;
v3 = 0;
getchar();
v5 = strlen(key);
for ( i = 0; i < v5; ++i )
{
if ( key[v3 % v5] > 64 && key[v3 % v5] <= 90 )
key[i] = key[v3 % v5] + 32;
++v3;
}
printf("Please input your flag:");
while ( 1 )
{
v1 = getchar();
if ( v1 == 10 )
break;
if ( v1 == 32 )
{
++v2;
}
else
{
if ( v1 <= 96 || v1 > 122 )
{
if ( v1 > 64 && v1 <= 90 )
{
str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;
++v3;
}
}
else
{
str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;
++v3;
}
if ( !(v3 % v5) )
putchar(32);
++v2;
}
}
if ( !strcmp(text, str2) )
puts("Congratulation!\n");
else
puts("Try again!\n");
return __readfsqword(0x28u) ^ v11;
}

通过观察和拼接可以得到key = adsfkndcls text = killshadow

下面是大佬的解密程序

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
#include<stdio.h> 
int main()
{
char key[] = "adsfkndcls";
char text[] = "killshadow";
int i;
int v3=10;//长度
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 128; j++)
{
if (j < 'A' || j > 'z' || j > 'Z' && j < 'a')
{
continue;
}
if ((j - 39 - key[v3 % 10] + 97) % 26 + 97 == text[i])
{
printf("%c",j);
v3++;
break;
}
}
}
}
————————————————
版权声明:本文为CSDN博主「Wαff1ε」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Waffle666/article/details/110250158

DUCTF

也是第一次打国外的CTF比赛了,感觉自己又进化了(bush)

Rocky

下载好附件包,发现是二进制文件,用IDA打开文件

F5后得到伪代码

image-20250723115303882

题目逻辑

这个题目的逻辑还是很简单的

输入字符串—>进行md5加密—>将加密后的s2与已知数组s进行对比,一样就输出flag

解题思路

s1数组是小端排序,想要获取数据有些麻烦

所以我们用IDA远端动态调试来获取数据

image-20250723120041654

将获得数据进行md5解密后就得到了我们想要输入的数据: emergencycall911

MD5 在線免費解密 MD5、SHA1、MySQL、NTLM、SHA256、SHA512、Wordpress、Bcrypt 的雜湊

(感谢冬瓜大佬提供)

重启调试,我们就得到了flag

image-20250723120506433

Skippy

这个题目writeup和我的解法不一样,我的解法是纯静态分析

个人解法

在下载文件并用IDA打开后,用F5查看伪代码

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall main(int argc, const char **argv, const char **envp)
{
_QWORD v4[2]; // [rsp+20h] [rbp-40h] BYREF
char n64_1; // [rsp+30h] [rbp-30h]
_QWORD v6[2]; // [rsp+40h] [rbp-20h] BYREF
char n64; // [rsp+50h] [rbp-10h]

_main();
v6[0] = 0xE8BEF2E0E0D2D6E6uLL;
v6[1] = 0xBED0E6EAC4BECAD0uLL;
n64 = 64;
sandwich(v6);
v4[0] = 0xDEDEE4C2CEDCC2D6uLL;
v4[1] = 0xDEDEDEDEDEDEDEDEuLL;
n64_1 = 64;
sandwich(v4);
decrypt_bytestring(v6, v4);
return 0;
}

从主函数我们可以看出来,现在有两个参数数组v6和v4在经过sandwich函数的改变后,传入decrypt_bytestring函数做参数

sandwich函数

1
2
3
4
5
6
__int64 __fastcall sandwich(__int64 a1)
{
stone(a1);
decryptor(a1);
return stone(a1);
}

经过检查后发现sandwich函数中stone函数没有对传入a1进行改变,只有decryptor函数改变

decryptor函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall decryptor(__int64 a1)
{
Stream *Stream; // rax
Stream *Stream_1; // rax
int result; // eax
unsigned __int64 i; // [rsp+28h] [rbp-8h]

Stream = __acrt_iob_func(2u);
fwrite("Uh oh... Skippy sees a null zone in the way...\n", 1u, 0x2Fu, Stream);
Stream_1 = __acrt_iob_func(2u);
fflush(Stream_1);
result = _mingw_printf("%d\n", MEMORY[0]);//不必理会
//真正改变传入的参数数组的算法
for ( i = 0; i <= 0xF; ++i )
{
result = a1 + i;
*(_BYTE *)(a1 + i) >>= 1;
}
return result;
}

我们发现其实就是将每个字节右移一位

将数组 v6和v4带入,可以得到处理过后的数组

1
2
3
4
v6[0] = 0x745F797070696B73  // 对应字符串 "skippy_t"
v6[1] = 0x5F687375625F6568 // 对应字符串 "he_bush_"
v4[0] = 0x6F6F7261676E616B // 对应ASCII字符串 "kangaroo"(小端:6B=k,61=a,6E=n,67=g,61=a,72=r,6F=o,6F=o)
v4[1] = 0x6F6F6F6F6F6F6F6F // 对应ASCII字符串 "oooooooo"

decrypt_bytestring函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall decrypt_bytestring(__int64 a1, __int64 a2)
{
void *v2; // rsp
_BYTE Buffer_1[200]; // [rsp+20h] [rbp-60h] BYREF
char *Buffer; // [rsp+E8h] [rbp+68h]
__int64 n96; // [rsp+F0h] [rbp+70h]
size_t Size; // [rsp+F8h] [rbp+78h]

Size = 96;
n96 = 96;
v2 = alloca(112);
Buffer = Buffer_1;
memcpy(Buffer_1, &precomputed, 0x60u);//获得加密密文
AES_init_ctx_iv(Buffer_1, a1, a2);//密钥拓展
AES_CBC_decrypt_buffer(Buffer_1, Buffer, Size);
Buffer[Size] = 0;
stone(Buffer);
return puts(Buffer);
}

然后就是一个有密钥拓展的AES-CBC算法

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
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes

def decrypt(encrypted_data,key):
"""

"""
# Base64解码
raw_data = encrypted_data

# 提取IV(前16字节)和密文
iv = raw_data[:AES.block_size]
# ciphertext = raw_data[AES.block_size:]
ciphertext = raw_data[AES.block_size:]
# 创建AES-CBC解密器
cipher = AES.new(key, AES.MODE_CBC, iv)

# 解密并去除填充
decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size)

return decrypted_data.decode('utf-8')

# 或者使用固定密钥(16字节)
iv = b'kangaroooooooooo'
key = b'skippy_the_bush_'

ciphertext = bytes.fromhex(iv.hex()+"AE27241B7FFD2C8B3265F22AD1B063F0915B6B95DCC0EEC14DE2C563F7715594007D2BC75E5D614E5E51190F4AD1FD21C5C4B1AB89A4A725C5B8ED3CB37630727B2D2AB722DC9333264725C6B5DDB00DD3C3DA6313F1E2F4DF5180D5F3831843")
decrypted = decrypt(ciphertext,key)
print("解密后的数据:", decrypted)
print(len(decrypted))

运行一下结果就出来了

image-20250723153002148

官方解法

查看伪代码,发现stone函数和decryptor函数存在无效指针读取操作

image-20250725101952027

image-20250725102014987

找到对应的汇编,把对应结果给nop掉

image-20250725102034257

image-20250725102057820

然后让程序运行一下就得到了flag

image-20250725100757825

第一次修改汇编,发现程序并没有按照自己的修改跑起来,原本以为要用动态调试,结果报了一堆奇奇怪怪的错误,查资料后了解到是要将修改后的数据保存在输入的文件中

image-20250725101625273

选择那个Apply patches to input file

然后就可以正常运行了


© 2025 luminarydawn 使用 Stellar 创建
总访问 113701 次 | 本页访问 326