记录了我目前做过的逆向题目和题解,以做题平台和比赛为分类依据
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; char v4;
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(); } 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; int v2; int v3; int i; int v5; char src[8]; __int64 v7; int v8; __int64 v9[2]; int v10; unsigned __int64 v11;
v11 = __readfsqword(0x28u); *(_QWORD *)src = 0x534C43444ELL; 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:
|
DUCTF
也是第一次打国外的CTF比赛了,感觉自己又进化了(bush)
Rocky
下载好附件包,发现是二进制文件,用IDA打开文件
F5后得到伪代码

题目逻辑
这个题目的逻辑还是很简单的
输入字符串—>进行md5加密—>将加密后的s2与已知数组s进行对比,一样就输出flag
解题思路
s1数组是小端排序,想要获取数据有些麻烦
所以我们用IDA远端动态调试来获取数据

将获得数据进行md5解密后就得到了我们想要输入的数据: emergencycall911
MD5 在線免費解密 MD5、SHA1、MySQL、NTLM、SHA256、SHA512、Wordpress、Bcrypt 的雜湊
(感谢冬瓜大佬提供)
重启调试,我们就得到了flag

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]; char n64_1; _QWORD v6[2]; char n64;
_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; Stream *Stream_1; int result; unsigned __int64 i;
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; _BYTE Buffer_1[200]; char *Buffer; __int64 n96; size_t Size;
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): """
""" raw_data = encrypted_data iv = raw_data[:AES.block_size] ciphertext = raw_data[AES.block_size:] cipher = AES.new(key, AES.MODE_CBC, iv) decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size) return decrypted_data.decode('utf-8')
iv = b'kangaroooooooooo' key = b'skippy_the_bush_'
ciphertext = bytes.fromhex(iv.hex()+"AE27241B7FFD2C8B3265F22AD1B063F0915B6B95DCC0EEC14DE2C563F7715594007D2BC75E5D614E5E51190F4AD1FD21C5C4B1AB89A4A725C5B8ED3CB37630727B2D2AB722DC9333264725C6B5DDB00DD3C3DA6313F1E2F4DF5180D5F3831843") decrypted = decrypt(ciphertext,key) print("解密后的数据:", decrypted) print(len(decrypted))
|
运行一下结果就出来了

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


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


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

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

选择那个Apply patches to input file
然后就可以正常运行了