逆向签到题 一道简单的逆向题目,直接将所给的elf文件放进ida中即可获得flag
int __fastcall main(int argc, const char **argv, const char **envp) { char s2[104]; // [rsp+10h] [rbp-70h] BYREF unsigned __int64 v5; // [rsp+78h] [rbp-8h] v5 = __readfsqword(0x28u); puts("plz input the key:"); __isoc99_scanf("%s", s2); if ( !strcmp("flag{7ujm8ikhy6}", s2) ) puts("flag{7ujm8ikhy6}"); else puts("key error"); return 0; }
flag为flag{7ujm8ikhy6}
re2 这道题目给了一个enflag.txt文件与勒索病毒.exe文件,查看enflag.txt文件
haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re/re2$ cat enflag.txt �%�L6;Y����2���5
应该是一个加密文件,不知道是什么加密,运行一下.exe文件
haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re/re2$ ./勒索病毒.exe **************************我的Flag出了什么问题??************************** 您的一些重要数据被我们加密了,就算您叫破喉咙来也没有办法恢复。 **************************那有没有恢复的方法呢??************************** 有的。只能通过我们的财付通,支付宝服务才能恢复,我以人格担保,只要充钱,就能解密 做出你的选择: 1.充钱 2.退出 1 打开源文件失败!
可以看出有两个选项,一个是充钱,一个是退出,但是你随便点击任何一个选项都会退出程序,因此放进ida中进行分析
if ( *(_DWORD *)p_Str == 1 ) { v13 = fopen("flag.txt", "r"); if ( !v13 ) { sub_401037((char *)&Format__2, v4); getchar(); exit(0); } v12 = fopen("enflag.txt", "w"); if ( !v12 ) { sub_401037((char *)&Format__3, v4); getchar(); exit(0); } sub_401037(asc_406D84, v4); sub_401073("%s", (char)Str); sub_401069(Str, Str1); sub_401028(Str, v15, v14, v13, v12); }
通过这一段代码就可以知道,得先在本地新创建一个flag.txt文件,才能继续运行下面的代码,所以先创建,然后再运行一次程序
haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re/re2$ ./Ransomware.exe **************************我的Flag出了什么问题??************************** 您的一些重要数据被我们加密了,就算您叫破喉咙来也没有办法恢复。 **************************那有没有恢复的方法呢??************************** 有的。只能通过我们的财付通,支付宝服务才能恢复,我以人格担保,只要充钱,就能解密 做出你的选择: 1.充钱 2.退出 1 请输入您的密钥:
首先会让我们输入密钥,输入的字符串会作为函数sub_401069与函数sub_401028的参数运行,首先看一下sub_401069(Str, Str1);
char __cdecl sub_401A70(char *Str, char *Str1) { char v3; // [esp+0h] [ebp-E4h] signed int i; // [esp+D0h] [ebp-14h] signed int v5; // [esp+DCh] [ebp-8h] __CheckForDebuggerJustMyCode(&unk_40B027); v5 = strlen(Str); for ( i = 0; i < v5; ++i ) Str1[i] += Str[i] ^ 0x1F; if ( !strcmp(Str1, "DH~mqqvqxB^||zll@Jq~jkwpmvez{") ) sub_401037((char *)&Format__4, v3); else sub_401037("Error!\n", v3); return *Str1; }
这段代码首先计算了我们输入密钥的长度,接着每个字符与0x1F进行异或,如果异或后得到的结果与字符串DHmqqvqxB^||zll@Jqjkwpmvez{一样,则输出变量Format__4对应的中文字符。因此我们可以通过编写脚本得到我们需要输入的字符串密钥是什么,脚本如下:
str_1 = "DH~mqqvqxB^||zll@Jq~jkwpmvez{" str_2 = "" for i in str_1: str_2 += chr(ord(i) ^ 0x1F) print(str_2) haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re/re2$ python3 xor_re.py [Warnning]Access_Unauthorized
得到需要输入的密钥后再次运行一遍程序
haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re/re2$ ./Ransomware.exe **************************我的Flag出了什么问题??************************** 您的一些重要数据被我们加密了,就算您叫破喉咙来也没有办法恢复。 **************************那有没有恢复的方法呢??************************** 有的。只能通过我们的财付通,支付宝服务才能恢复,我以人格担保,只要充钱,就能解密 做出你的选择: 1.充钱 2.退出 1 请输入您的密钥:[Warnning]Access_Unauthorized 充值成功. 加密成功! 您的充值金额不足。 **************************我的Flag出了什么问题??************************** 您的一些重要数据被我们加密了,就算您叫破喉咙来也没有办法恢复。 **************************那有没有恢复的方法呢??************************** 有的。只能通过我们的财付通,支付宝服务才能恢复,我以人格担保,只要充钱,就能解密 做出你的选择: 1.充钱 2.退出
行,还是没有flag,那么就跟函数sub_401028(Str, v15, v14, v13, v12);有关了,那么就进一步分析该函数
int __cdecl sub_4014E0(char *Str, int a2, int a3, FILE *Stream, Stream *Stream_1) { char v6; // [esp+0h] [ebp-D8h] size_t v7; // [esp+D0h] [ebp-8h] __CheckForDebuggerJustMyCode(&unk_40B027); v7 = strlen(Str); sub_4010F0(a2, Str, v7); sub_4010C8(a3); sub_40116D(a3, a2); sub_4010EB(a3, Stream, Stream_1); fclose(Stream); fclose(Stream_1); return sub_401037(asc_406B34, v6); } 其中char *Str这一个参数是我们输入的变量
发现有4个函数操作,看起来有点复杂,一个一个来看吧。
第一个函数
int __cdecl sub_401800(int a1, char *Str, signed int n256_1) { int result; // eax int n256_2; // [esp+D0h] [ebp-14h] int n256; // [esp+DCh] [ebp-8h] result = __CheckForDebuggerJustMyCode(&unk_40B027); if ( n256_1 <= 256 ) { for ( n256 = 0; n256 < 256; ++n256 ) { *(_BYTE *)(n256 + a1) = Str[n256 % n256_1]; result = n256 + 1; } } if ( n256_1 > 256 ) { for ( n256_2 = 0; n256_2 < 256; ++n256_2 ) { *(_BYTE *)(n256_2 + a1) = Str[n256_2]; result = n256_2 + 1; } } return result; } 其中n256_1就是我们输入的字符串的长度
这一段代码主要实现的是将输入的密钥填充进一个大小为256个字节的临时数组中,若密钥长度不足256个字节,则反复存入,若超过,则只取前256个字节。
第二个函数
int __cdecl sub_401780(int a1) { int result; // eax int n256; // [esp+D0h] [ebp-8h] result = __CheckForDebuggerJustMyCode(&unk_40B027); for ( n256 = 0; n256 < 256; ++n256 ) { *(_BYTE *)(n256 + a1) = n256; result = n256 + 1; } return result; }
这一段代码是初始化一个数组(S盒),将传入的一个256个字节初始化为0,1,2……255。
第三个函数
int __cdecl sub_4018E0(int a1, int a2) { int result; // eax int n256; // [esp+D0h] [ebp-2Ch] char v4; // [esp+EBh] [ebp-11h] int v5; // [esp+F4h] [ebp-8h] result = __CheckForDebuggerJustMyCode(&unk_40B027); v5 = 0; for ( n256 = 0; n256 < 256; ++n256 ) { v5 = (*(unsigned __int8 *)(n256 + a2) + v5 + *(unsigned __int8 *)(n256 + a1)) % 256; v4 = *(_BYTE *)(n256 + a1); *(_BYTE *)(n256 + a1) = *(_BYTE *)(v5 + a1); *(_BYTE *)(v5 + a1) = v4; result = n256 + 1; } return result; }
这一段代码是一个密钥调度算法,是将我们得到的输入密钥的临时数组去打乱初始化的S盒,完成RC4的初始化置换
第四个函数
int __cdecl sub_4015E0(int a1, FILE *Stream, FILE *Stream_1) { int result; // eax char v4; // [esp+103h] [ebp-35h] char i; // [esp+11Bh] [ebp-1Dh] int v6; // [esp+124h] [ebp-14h] int v7; // [esp+130h] [ebp-8h] __CheckForDebuggerJustMyCode(&unk_40B027); v7 = 0; v6 = 0; for ( i = fgetc(Stream); ; i = fgetc(Stream) ) { result = i; if ( i == -1 ) break; v7 = (v7 + 1) % 256; v6 = (v6 + *(unsigned __int8 *)(v7 + a1)) % 256; v4 = *(_BYTE *)(v7 + a1); *(_BYTE *)(v7 + a1) = *(_BYTE *)(v6 + a1); *(_BYTE *)(v6 + a1) = v4; fputc(*(_BYTE *)((*(unsigned __int8 *)(v6 + a1) + *(unsigned __int8 *)(v7 + a1)) % 256 + a1) ^ i, Stream_1); } return result; }
这一段代码的作用就是逐字节读取输入流,用打乱后的 S 盒生成伪随机密钥字节,并与明/密文异或,写入输出流。
总的来说,这几个函数的作用就是实现RC4流密码算法,包括密钥调度和加解密过程。那么什么是RC4流密码算法 ?RC4是一种由Ron Rivest于1987年设计的经典对称流密码算法,密钥长度可变(1~256字节),通过密钥调度算法(KSA)将初始有序的S盒打乱为密钥依赖的伪随机排列,再经伪随机生成算法(PRGA)持续输出密钥流,与明文逐字节异或实现加解密;其代码极其简洁高效,曾广泛用于WEP、SSL/TLS等协议,但因密钥流存在统计偏差和易受FMS等攻击,现已被视为不安全并逐渐被AES-GCM等现代算法取代。
OK,大致知道了题目所给的enflag.txt文件是经过了RC4加密得到的,我们又知道了密钥,那么就可以编写解密脚本从而得到flag了。解密脚本如下:
def rc4_crypt(data: bytes, key: bytes) -> bytes: """RC4 加解密(加密与解密操作完全相同)""" # 1. 初始化 S 盒 0~255 S = list(range(256)) # 2. 密钥调度算法 (KSA) j = 0 key_len = len(key) for i in range(256): j = (j + S[i] + key[i % key_len]) & 0xFF S[i], S[j] = S[j], S[i] # 3. 伪随机生成与异或 (PRGA) i = j = 0 result = bytearray() for byte in data: i = (i + 1) & 0xFF j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] t = (S[i] + S[j]) & 0xFF keystream_byte = S[t] result.append(byte ^ keystream_byte) return bytes(result) # 读取密文文件 with open('enflag.txt', 'rb') as f: ciphertext = f.read() # 密钥(注意直接使用字符串,需编码为字节) key = b"[Warnning]Access_Unauthorized" # 解密(RC4 的加密与解密相同) plaintext = rc4_crypt(ciphertext, key) # 输出结果(通常 flag 是可见字符串) print(plaintext.decode('utf-8', errors='replace'))
re3 放入ida中进行分析
v21 = __readfsqword(0x28u); n80 = 80; n64227 = 0xFAE3; n226312059 = 0xD7D3F7B; v10 = 0xA43499F6; n5 = 5; n16 = 0x10; n3833 = 0xEF9; v5 = 0; puts("plz input the key:"); __isoc99_scanf("%s", s); v3 = strlen(s); strncpy(dest, src_, v3 - 6); dest[strlen(s) - 6] = 0; __isoc99_sscanf(dest, "%x", &v5); v17[0] = n80; v17[1] = n64227; v17[2] = n226312059; v17[3] = v10; v17[4] = (n5 << 12) + n16; v17[5] = n3833; v17[6] = v5; n0xFFFF = 0; for ( n6 = 0; n6 <= 6; ++n6 ) { for ( n0xFFFF += (unsigned int)v17[n6]; n0xFFFF > 0xFFFF; n0xFFFF = v15 + (unsigned int)(unsigned __int16)n0xFFFF ) { n0xFFFF_1 = (unsigned __int16)n0xFFFF; v15 = n0xFFFF >> 16; } } if ( n0xFFFF == 0xFFFF ) puts("OK"); else puts("Error");
由于没有长度限制,我们输入的字符串如果超过了5字节会覆盖到src_中,发现这道题就是一个累加的过程,前6个数是固定的,因此加起来是一个定值,通过动调看一下
0x5555554009bb <main+449> cmp qword ptr [rbp - 0x108], 0xffff 0xe560 - 0xffff EFLAGS => 0x293 [ CF pf AF zf SF IF df of ac ]
可以知道是0xe560,那么就可以计算出我们需要输入0x1A9F,因为这道题的flag是4位值,因此直接1A9F就行了,想要程序输出OK,就必须得在1A9F前面再输入其他5个字符,这样1A9F才会被存入到V5中。
haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re$ ./re3 plz input the key: aaaaa1a9fa OK
逆向4 首先运行一下程序
haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re$ ./mingyue.exe 请输入正确的数字: aa
是一个猜数字的程序,再将其放入到ida中进行逆向分析
void __fastcall __noreturn encode(char *a1, __int64 a2) { int v2; // r9d __int64 v3; // r8 char *v4; // r10 char v5; // al __int64 v6; // rbx unsigned __int8 v7; // cl char v8; // [rsp+1Fh] [rbp-3F9h] char v9; // [rsp+20h] [rbp-3F8h] BYREF v2 = 0; v3 = (__int64)a1; if ( a1 ) { v4 = &v9; do { ++v4; ++v2; a1 = &a4890572163qwe[-26 * (v3 / 26)]; //)(*&^%489$!057@#><:2163qwe v5 = a1[v3]; v3 /= 26; a2 = v3; *(v4 - 1) = v5; } while ( v3 ); } v6 = v2; while ( v6 ) { v7 = *(&v8 + v6--); sub_1400011E0(v7 ^ 7u, a2, v3); } sub_140001220(a1, a2, v3); }
这里是将v3当作26进制数进行处理的。
这部分最难以理解的部分就是
a1 = &a4890572163qwe[-26 * (v3 / 26)]; // )(*&^%489$!057@#><:2163qwe v5 = a1[v3]; v3 /= 26; a2 = v3;
首先-26 * (v3 / 26)相当于v3 % 26 - v3,因为在这里v3=26*(v3/26)+v3%26。因此a1 = &a4890572163qwe[v3 % 26 - v3];而v5=a1[v3]就相当于v5=a1[v3%26],然后v3再整除26并保存到a2中。最后保存修改后的字符串。
接着进入函数sub_1400011E0(v7 ^ 7u, a2, v3);
_QWORD *__fastcall sub_1400011E0(char a1) { _QWORD *result; // rax __int64 v3; // rdx result = malloc(0x10u); v3 = qword_140004618; qword_140004618 = (__int64)result; *(_QWORD *)(v3 + 8) = result; *(_BYTE *)v3 = a1; result[1] = 0; return result; }
这段代码就是将异或得到的东西放入到一段空间里面。
再来看sub_140001220()
void __noreturn sub_140001220() { __int64 v0; // r9 int n14_3; // ecx __int64 n14; // rdx char v3; // al int n14_2; // r8d __int64 v5; // r9 char v6; // cl int n14_1; // eax v0 = qword_140004620; n14_3 = 0; n14 = 0; while ( 1 ) { v3 = *(_BYTE *)v0; n14_2 = n14_3 + 1; v5 = *(_QWORD *)(v0 + 8); if ( v3 != aV4pY59[n14] ) // /..v4p$$!>Y59- n14_2 = n14_3; qword_140004620 = v5; if ( !v5 ) break; v6 = *(_BYTE *)v5; n14_1 = n14_2 + 1; v0 = *(_QWORD *)(v5 + 8); if ( v6 != aV4pY59[n14 + 1] ) n14_1 = n14_2; qword_140004620 = v0; if ( v0 ) { n14 += 2; n14_3 = n14_1; if ( n14 < 14 ) continue; } goto LABEL_11; } n14_1 = n14_2; LABEL_11: if ( n14_1 == 14 ) sub_1400012E0(); sub_1400012B0(); }
此时我们知道了,关键在于n14_1是否等于14,若等于就可以获得flag了。简单来说,这段代码就是一个校验过程,判断异或结果的字符串是否与字符串/..v4p$$!>Y59-相同。
大致了解了这个程序的功能是什么了,就是让我们输入一个数字,接着将这个数字进行处理后按索引在字符串)(*&^%489$!057@#><:2163qwe里面找到相应字符并保存后得到新的字符串,再将这个字符串与7异或后与字符串/..v4p$$!>Y59-比较是否一致,一致则那串数字就是flag。接下来就是编写脚本了
str1 = ')(*&^%489$!057@#><:2163qwe' str2 = '/..v4p$$!>Y59-' v7 = '' a = [] for i in range(len(str2)): v7 += chr(ord(str2[i]) ^ 7) for j in v7: a.append(str1.index(j)) flag = 0 for z in range(len(a)): flag *= 26 flag += a[z] print(flag)
逆向5 这道题的关键点在于函数sub_4015BD(1);
int (*__stdcall sub_4015BD(int a1))() { int (*_dba54edb0?d6_7??3ef0f1caf2ad3102_)(); // eax int (*p_sub_401520)(); // [esp+24h] [ebp+4h] _dba54edb0?d6_7??3ef0f1caf2ad3102_ = (int (*)())(unsigned __int8)Str[1];// "dba54edb0?d6>7??3ef0f1caf2ad3102" if ( Str[1] == 1 ) // "dba54edb0?d6>7??3ef0f1caf2ad3102" { _dba54edb0?d6_7??3ef0f1caf2ad3102_ = sub_401520; p_sub_401520 = sub_401520; } return _dba54edb0?d6_7??3ef0f1caf2ad3102_; }
给了我们一个字符串dba54edb0?d6>7??3ef0f1caf2ad3102,接着发现(*p_sub_401520)()这个是返回地址,也就是说执行完这个函数会执行sub_401520这个函数,再来看看sub_401520这个函数以及这个的返回函数sub_40163E(i)
int sub_401520() { FARPROC H; // [esp+14h] [ebp-14h] HMODULE hModule; // [esp+18h] [ebp-10h] size_t i; // [esp+1Ch] [ebp-Ch] hModule = LoadLibraryA("1.dll"); H = GetProcAddress(hModule, "H"); // "dba54edb0?d6>7??3ef0f1caf2ad3102" for ( i = 0; i < strlen(Str); ++i ) { a12345678901111[i] = Str[i]; // "dba54edb0?d6>7??3ef0f1caf2ad3102" a12345678901111[i] = ((int (__cdecl *)(_DWORD))H)(a12345678901111[i]);// "12345678901111111222222222222222" } return sub_40163E(i); }
size_t sub_40163E() { size_t _dba54edb0?d6_7??3ef0f1caf2ad3102_; // eax size_t _dba54edb0?d6_7??3ef0f1caf2ad3102__1; // [esp+1Ch] [ebp-Ch] for ( _dba54edb0?d6_7??3ef0f1caf2ad3102__1 = 0; ; ++_dba54edb0?d6_7??3ef0f1caf2ad3102__1 ) { _dba54edb0?d6_7??3ef0f1caf2ad3102_ = strlen(Str);// "dba54edb0?d6>7??3ef0f1caf2ad3102" if ( _dba54edb0?d6_7??3ef0f1caf2ad3102__1 >= _dba54edb0?d6_7??3ef0f1caf2ad3102_ ) break; putchar(a12345678901111[_dba54edb0?d6_7??3ef0f1caf2ad3102__1]);// "12345678901111111222222222222222" } dword_405020 = 1; return _dba54edb0?d6_7??3ef0f1caf2ad3102_; }
我大致猜测这个就是输出flag的函数。那么这道题关键就在于这个1.dll文件了。这个文件加了壳,使用工具将其脱壳后,查看函数H干了什么
int __cdecl H(unsigned __int8 a1) { return a1 ^ 7; }
发现就是简单的与7进行异或,那么就简单了,直接写脚本就行了
str_1 = "dba54edb0?d6>7??3ef0f1caf2ad3102" flag = "" for i in str_1: flag += chr(ord(i)^7) print(flag)
红包题 武穆遗书 这道题加了壳,使用upx脱壳工具进行脱壳,工具下载与使用命令如下
sudo apt update && sudo apt install upx upx -d re6.exe
接着,放入ida中进行分析
int __cdecl main(int argc, const char **argv, const char **envp) { const char *v4; // [esp+0h] [ebp-3Ch] _BYTE v5[4]; // [esp+4h] [ebp-38h] BYREF char v6[52]; // [esp+8h] [ebp-34h] BYREF sub_4011D0(); sub_401200(); sub_401280(); if ( sub_401040() ) exit(0); v4 = (const char *)operator new(0x1Cu); sub_401390(v4, v5, &unk_4070F4, 28); if ( sub_4010E0() ) exit(0); while ( 1 ) { printf("input the password less than 50 char:\n"); gets(v6); fflush((FILE *)iob[0]._ptr); if ( !strcmp(v4, v6) ) break; printf("password error!!! please try again!\nyour input is %s \n", v6); } printf("win!!!the password and your input are all %s\n", v6); system((const char *)Command); // "pause" return 0; }
这道题看起来是有点复杂了,于是采用动调的方式进行分析,因为这道题在前面几个函数里面有判断然后退出程序,因此就需要在前面下断点,该nop的nop,该改值的改值。最后就能得到flag了。这道题主要考察的就是是否会使用ida进行动态调试。
红包六 给了一个jar包文件,但一般来说,可以把这个就看作是一个压缩包,解压后得到Java代码
import java.util.Base64; import java.util.Scanner; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.swing.JOptionPane; public class EzJar { //hint: flag not here public static void main(String[] args) throws Exception { JOptionPane.showMessageDialog(null, "Give me your flag:", "alert", JOptionPane.QUESTION_MESSAGE); System.out.print("Give me your flag:"); Scanner sc = new Scanner(System.in); String s = sc.next(); Cipher cipher = Cipher.getInstance("DES"); cipher.init(1, new SecretKeySpec("easy_key".getBytes(), "DES")); String result = new String(Base64.getEncoder().encode(cipher.doFinal(s.getBytes()))); System.out.println(result); if ("UUwnbEk0rzJT9G+ET6MU+Y+6ChoFhCceRnfdDTcuEeJ9+6qwaZFV3w==".equals(result)) { JOptionPane.showMessageDialog(null, "Accept!"); System.out.print("Accept!"); } else { JOptionPane.showMessageDialog(null, "Wrong answer!"); System.out.print("Wrong answer!"); } } }
知道了这是一个DES与base64一起加密的程序,最后进行解密就可以得到flag了是不是?当然不是因为这里有个提示
//hint: flag not here
行,那么就又只能乖乖返回那个jar包进行分析了。将这个放入到010中发现有压缩包,那么使用脚本提取出来
import zlib inflator = zlib.decompressobj(-zlib.MAX_WBITS) f = open("EzJar.jar", "rb") f.seek(659) a = f.read(3248) f.close() x = inflator.decompress(a) f = open("EzJar.class", "wb") f.write(x) f.close()
将得到的文件放入jadx中进行分析
package defpackage; import java.awt.Component; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Arrays; import java.util.Base64; import java.util.Scanner; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.swing.JOptionPane; /* renamed from: EzJar reason: default package */ /* loaded from: EzJar.class */ public class EzJar { private static final /* synthetic */ String[] I = null; private static final /* synthetic */ int[] l = null; private static final /* synthetic */ String banner; private static final /* synthetic */ String flag; private static final /* synthetic */ String WA; private static final /* synthetic */ String AC; private static final /* synthetic */ String enc; private static final /* synthetic */ String key; public static void main(String[] strArr) throws Exception { JOptionPane.showMessageDialog((Component) null, I[l[0]], I[l[1]], l[2]); System.out.print(I[l[3]]); String next = new Scanner(System.in).next(); Cipher cipher = Cipher.getInstance(I[l[2]]); cipher.init(l[1], new SecretKeySpec(I[l[4]].getBytes(), I[l[5]])); if (!lIl(I[l[6]].equals(new String(Base64.getEncoder().encode(cipher.doFinal(next.getBytes())))) ? 1 : 0)) { JOptionPane.showMessageDialog((Component) null, I[l[9]]); System.out.print(I[l[10]]); return; } JOptionPane.showMessageDialog((Component) null, I[l[7]]); System.out.print(I[l[8]]); "".length(); if (0 == 0) { } } private static void lII() { l = new int[18]; l[0] = (156 ^ 136) & ((127 ^ 107) ^ (-1)); l[1] = " ".length(); l[2] = " ".length(); l[3] = " ".length(); l[4] = (185 ^ 197) ^ (32 ^ 88); l[5] = " ".length() ^ (28 ^ 24); l[6] = 16 ^ 22; l[7] = (69 ^ 77) ^ (70 ^ 73); l[8] = (((106 + 107) - 66) + 32) ^ (((33 + 90) - 98) + 162); l[9] = (((154 + 21) - 48) + 38) ^ (((78 + 146) - 163) + 111); l[10] = 29 ^ 23; l[11] = 64 ^ 75; l[12] = 105 ^ 101; l[13] = " ".length() ^ (106 ^ 101); l[14] = 72 ^ 70; l[15] = 144 ^ 159; l[16] = 160 ^ 176; l[17] = 177 ^ 160; } private static String lI(String lllllllllIlIlII, String lllllllllIlIlIl) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(lllllllllIlIlIl.getBytes(StandardCharsets.UTF_8)), "Blowfish"); Cipher lllllllllIllIII = Cipher.getInstance("Blowfish"); lllllllllIllIII.init(l[3], secretKeySpec); return new String(lllllllllIllIII.doFinal(Base64.getDecoder().decode(lllllllllIlIlII.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8); } catch (Exception lllllllllIlIlll) { lllllllllIlIlll.printStackTrace(); return null; } } private static String I(String lllllllllIIlIIl, String lllllllllIIlIII) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("MD5").digest(lllllllllIIlIII.getBytes(StandardCharsets.UTF_8)), l[8]), "DES"); Cipher lllllllllIIlIll = Cipher.getInstance("DES"); lllllllllIIlIll.init(l[3], secretKeySpec); return new String(lllllllllIIlIll.doFinal(Base64.getDecoder().decode(lllllllllIIlIIl.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8); } catch (Exception lllllllllIIlIlI) { lllllllllIIlIlI.printStackTrace(); return null; } } private static void ll() { I = new String[l[17]]; I[l[0]] = I("AQiA0bYffm9HvMlm7RnEMX/tEQAUj4Xb", "FrlAZ"); I[l[1]] = I("hXzZyx8IUHw=", "Esxsh"); I[l[3]] = l("ID8PFlEKM1kKHhIkWRUdBjFD", "gVysq"); I[l[2]] = I("50fO6ARqllg=", "VZbFF"); I[l[4]] = lI("mvXqH+/XIESPZaSG3ZbZlA==", "TuZSw"); I[l[5]] = l("JQ0R", "aHBFu"); I[l[6]] = I("dMKiRQ19iTevvzL7NtVg5+ye5BywL2QaxtVANFLuC5B2/KuC+/5L6BwtCB7zpWK1XBTQr0VWC3Vt/uYEl2xmjskE0dDrCk2C", "dPxYA"); I[l[7]] = lI("B/MVYKSzgq8=", "phiUP"); I[l[8]] = I("ZtBOhuHeK3Y=", "MfnkQ"); I[l[9]] = lI("aPhz+GjGynRlU3Alo00QeQ==", "wWtUj"); I[l[10]] = l("BRQFNiRyBwQrNDcUSw==", "RfjXC"); I[l[11]] = I("pT10j0lChvyrNwYRFdqBzxqFp1ruTgo9", "hGcuT"); I[l[12]] = lI("UhBbCDk5yqaWl1uHJyS/OGmtcfVyvOOsk78/1f0MU8U3UfAf1Xf0FWNbpcKes/0HRz9SU/icRJHswW2xWjHrcFzhpsvwzqUl", "eeMoV"); I[l[13]] = I("s11BihYBzRBpcX9EF43utw==", "RjiXK"); I[l[14]] = I("wk5jH1cyKoA=", "frsxP"); I[l[15]] = I("Nfa0rxB8IRArMq2F4iLlLg==", "ulQWJ"); I[l[16]] = lI("wLcWNd0Xsbw=", "JgPGn"); } static { lII(); ll(); banner = I[l[11]]; flag = I[l[12]]; key = I[l[13]]; enc = I[l[14]]; WA = I[l[15]]; AC = I[l[16]]; } private static boolean llI(int i, int i2) { return i < i2; } private static String l(String llllllllllIlIll, String llllllllllIlIlI) { String str = new String(Base64.getDecoder().decode(llllllllllIlIll.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); StringBuilder llllllllllIlIIl = new StringBuilder(); char[] llllllllllIlIII = llllllllllIlIlI.toCharArray(); int llllllllllIIIlI = l[0]; char[] charArray = str.toCharArray(); int length = charArray.length; int i = l[0]; while (llI(i, length)) { char llllllllllIllII = charArray[i]; llllllllllIlIIl.append((char) (llllllllllIllII ^ llllllllllIlIII[llllllllllIIIlI % llllllllllIlIII.length])); "".length(); llllllllllIIIlI++; i++; "".length(); if ((-" ".length()) > " ".length()) { return null; } } return String.valueOf(llllllllllIlIIl); } private static boolean lIl(int i) { return i != 0; } }
这段代码有点长,但大致就是密钥和密文都被加密了,得先解出密文和密钥,再写脚本得到密码,这个密码就是flag。
因此我们需要分别得出密文(I[6])和密钥(I[4]),脚本如下
# get_expected_cipher.py import base64 import hashlib from Crypto.Cipher import DES # 密文与密钥种子 cipher_b64 = "dMKiRQ19iTevvzL7NtVg5+ye5BywL2QaxtVANFLuC5B2/KuC+/5L6BwtCB7zpWK1XBTQr0VWC3Vt/uYEl2xmjskE0dDrCk2C" key_seed = "dPxYA" # 计算 DES 密钥:MD5("dPxYA") 的前 8 字节 full_md5 = hashlib.md5(key_seed.encode()).digest() des_key = full_md5[:8] # Base64 解码 → DES 解密 ciphertext = base64.b64decode(cipher_b64) cipher = DES.new(des_key, DES.MODE_ECB) plain = cipher.decrypt(ciphertext) # 去除 PKCS5 填充 pad_len = plain[-1] expected_base64 = plain[:-pad_len].decode() print("Expected cipher (Base64):", expected_base64) # 结果为 Dg/TZuRXF4+UwSZ8Dpwgw8+VOoHVl1YlPL1QRVhroCy4ptnKEcdC05iXcpLyDnuR
# get_des_key.py import base64 import hashlib from Crypto.Cipher import Blowfish # 密文与密钥种子 cipher_b64 = "mvXqH+/XIESPZaSG3ZbZlA==" key_seed = "TuZSw" # 计算 Blowfish 密钥:MD5("TuZSw") key = hashlib.md5(key_seed.encode()).digest() # Base64 解码 → Blowfish 解密 ciphertext = base64.b64decode(cipher_b64) cipher = Blowfish.new(key, Blowfish.MODE_ECB) plain = cipher.decrypt(ciphertext) # 去除 PKCS5 填充 pad_len = plain[-1] des_key = plain[:-pad_len].decode() print("DES key:", des_key) # 结果为 Ctf3r_me
得到最后的flag的脚本如下:
import base64 from Crypto.Cipher import DES # 密文 (Base64) cipher_b64 = "Dg/TZuRXF4+UwSZ8Dpwgw8+VOoHVl1YlPL1QRVhroCy4ptnKEcdC05iXcpLyDnuR" ciphertext = base64.b64decode(cipher_b64) # 密钥(8字节,正好满足DES) key = b"Ctf3r_me" # DES 解密,默认 ECB 模式,PKCS5 填充 cipher = DES.new(key, DES.MODE_ECB) plaintext = cipher.decrypt(ciphertext) # 去除填充 (PKCS5) pad_len = plaintext[-1] flag = plaintext[:-pad_len].decode() print(flag)
萌新赛 数学不及格 int __fastcall main(int argc, const char **argv, const char **envp) { unsigned int n200; // [rsp+14h] [rbp-4Ch] char *endptr; // [rsp+18h] [rbp-48h] BYREF char *endptr_; // [rsp+20h] [rbp-40h] BYREF char *endptr__1; // [rsp+28h] [rbp-38h] BYREF char *endptr__2; // [rsp+30h] [rbp-30h] BYREF __int64 v9; // [rsp+38h] [rbp-28h] __int64 v10; // [rsp+40h] [rbp-20h] __int64 v11; // [rsp+48h] [rbp-18h] __int64 v12; // [rsp+50h] [rbp-10h] unsigned __int64 v13; // [rsp+58h] [rbp-8h] v13 = __readfsqword(0x28u); if ( argc != 5 ) { puts("argc nonono"); exit(1); } n200 = strtol(argv[4], &endptr, 16) - 0x6543; v9 = f(n200); v10 = strtol(argv[1], &endptr_, 0x10); v11 = strtol(argv[2], &endptr__1, 0x10); v12 = strtol(argv[3], &endptr__2, 0x10); if ( v9 - v10 != 0x233F0E151CLL ) { puts("argv1 nonono!"); exit(1); } if ( v9 - v11 != 0x1B45F81A32LL ) { puts("argv2 nonono!"); exit(1); } if ( v9 - v12 != 0x244C071725LL ) { puts("argv3 nonono!"); exit(1); } if ( (int)n200 + v12 + v11 + v10 != 0x13A31412F8CLL ) { puts("argv sum nonono!"); exit(1); } puts("well done!decode your argv!"); return 0; }
首先得知道long int strtol(const char *str, char endptr, int base);函数,把参数 str所指向的字符串根据给定的 base 转换为一个长整数(类型为 long int 型),base 必须介于 2 和 36(包含)之间,或者是特殊值 0。接着这个程序就是进行一系列的数学计算问题,如果结果正确,再将这个一大串数字转化为字符串就是flag了。
再来看一下f()函数
__int64 __fastcall f(int n200) { int n2; // [rsp+1Ch] [rbp-14h] __int64 v3; // [rsp+20h] [rbp-10h] _QWORD *ptr; // [rsp+28h] [rbp-8h] if ( n200 <= 1 || n200 > 200 ) return 0; ptr = malloc(8LL * n200); *ptr = 1; ptr[1] = 1; v3 = 0; for ( n2 = 2; n2 < n200; ++n2 ) { ptr[n2] = ptr[n2 - 1] + ptr[n2 - 2]; v3 = ptr[n2]; } free(ptr); return v3; }
这实现的是斐波那契数列。
接下来就是直接上脚本就行了
def fib(n): """返回第 n 个斐波那契数 (fib(1)=1, fib(2)=1)""" if n <= 0: return 0 a, b = 1, 1 for _ in range(3, n + 1): a, b = b, a + b return b K1 = 0x233F0E151C K2 = 0x1B45F81A32 K3 = 0x244C071725 TARGET_SUM = 0x13A31412F8C for n200 in range(2, 201): v9 = fib(n200) v10 = v9 - K1 v11 = v9 - K2 v12 = v9 - K3 if n200 + v10 + v11 + v12 == TARGET_SUM: print(f"[+] Found n200 = {n200}") print(f" v9 = {v9}") print(f" v10 = {v10:#x}") print(f" v11 = {v11:#x}") print(f" v12 = {v12:#x}") argv1 = hex(v10) argv2 = hex(v11) argv3 = hex(v12) argv4 = hex(n200 + 0x6543) print("\n[+] Command line arguments (hex, without 0x if preferred):") print(f" argv[1] = {argv1}") print(f" argv[2] = {argv2}") print(f" argv[3] = {argv3}") print(f" argv[4] = {argv4}") break else: print("[-] No valid n200 found in range [2,200].")
argv[1] = 0x666c61677b argv[2] = 0x6e65776265 argv[3] = 0x655f686572 argv[4] = 0x657d
再将得到的参数通过ASCII码转换为字符串得到最后的flag
argv = [0x666c61677b, 0x6e65776265, 0x655f686572, 0x657d] flag_bytes = b''.join(x.to_bytes((x.bit_length() + 7) // 8, 'big') for x in argv) flag = flag_bytes.decode('ascii') print(flag)
flag白给 这个程序加了壳的,先用upx工具进行脱壳后,再放入ida中进行分析。
关键是下面这段代码
int __fastcall TForm1_Button1Click(int a1) { int System::AnsiString; // edx unsigned int v3[2]; // [esp-10h] [ebp-14h] BYREF int *v4; // [esp-8h] [ebp-Ch] int v5; // [esp+0h] [ebp-4h] BYREF int savedregs; // [esp+4h] [ebp+0h] BYREF v5 = 0; v4 = &savedregs; v3[1] = (unsigned int)&loc_454799; v3[0] = (unsigned int)NtCurrentTeb()->NtTib.ExceptionList; __writefsdword(0, (unsigned int)v3); Controls::TControl::GetText(*(Controls::TControl **)(a1 + 764)); if ( Sysutils::CompareStr(v5, &str_HackAv[1]) ) Dialogs::ShowMessage((Dialogs *)&str_____[1], System::AnsiString); else Dialogs::ShowMessage((Dialogs *)&str______[1], System::AnsiString); __writefsdword(0, v3[0]); v4 = (int *)&loc_4547A0; return System::__linkproc__ LStrClr(&v5); }
主要就是将我们输入的字符串与&str_HackAv[1]进行比较,那么就再来看一下&str_HackAv[1]里面存放了什么内容
CODE:004547A4 _str_HackAv dd 0FFFFFFFFh ; _top CODE:004547A4 ; DATA XREF: _TForm1_Button1Click+27↑o CODE:004547A8 dd 6 ; Len CODE:004547AC db 'HackAv',0 ; Text
输入HackAv试一下,程序输出成功了,那么这就是我们要的flag。
签退 给了一个.pyc文件,这是由python解释器在导入或执行python脚本时生成的编译字节码文件,是一个二进制文件。可以使用uncompyle6将python的字节码文件反编译回可读的python源代码。下载与使用如下:
haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re/萌新赛$ pipx install uncompyle6 installed package uncompyle6 3.9.3, installed using Python 3.12.3 These apps are now globally available - uncompyle6 - uncompyle6-tokenize done! ✨ 🌟 ✨ haoo@LAPTOP-QH6DKIQF:/mnt/d/CTF/CTFSHOW/re/萌新赛$ uncompyle6 re3.pyc > re3.py
打开得到的re3.py文件
# uncompyle6 version 3.9.3 # Python bytecode version base 2.7 (62211) # Decompiled from: Python 3.12.3 (main, Mar 23 2026, 19:04:32) [GCC 13.3.0] # Embedded file name: re3.py # Compiled at: 2020-03-06 17:43:28 import string c_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '()' flag = 'BozjB3vlZ3ThBn9bZ2jhOH93ZaH9' def encode(origin_bytes): c_bytes = [('{:0>8}').format(str(bin(b)).replace('0b', '')) for b in origin_bytes] resp = '' nums = len(c_bytes) // 3 remain = len(c_bytes) % 3 integral_part = c_bytes[0:3 * nums] while integral_part: tmp_unit = ('').join(integral_part[0:3]) tmp_unit = [int(tmp_unit[x:x + 6], 2) for x in [0, 6, 12, 18]] resp += ('').join([c_charset[i] for i in tmp_unit]) integral_part = integral_part[3:] if remain: remain_part = ('').join(c_bytes[3 * nums:]) + (3 - remain) * '0' * 8 tmp_unit = [int(remain_part[x:x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1] resp += ('').join([c_charset[i] for i in tmp_unit]) + (3 - remain) * '.' return rend(resp) def rend(s): def encodeCh(ch): f = lambda x: chr((ord(ch) - x + 2) % 26 + x) if ch.islower(): return f(97) if ch.isupper(): return f(65) return ch return ('').join(encodeCh(c) for c in s) return # okay decompiling re3.pyc
大致就是字符串’BozjB3vlZ3ThBn9bZ2jhOH93ZaH9’是密文,先通过base64编码再通过凯撒密码进行加密得到的,因此我们需要先将其进行凯撒密码进行解密,再进行base64的解码得到最后的flag。脚本如下:
import string # 自定义字符集 c_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '()' # 凯撒逆变换(字母前移2位) def unrend(s): def decode_ch(ch): if ch.islower(): return chr((ord(ch) - 97 - 2) % 26 + 97) elif ch.isupper(): return chr((ord(ch) - 65 - 2) % 26 + 65) else: return ch return ''.join(decode_ch(c) for c in s) # 自定义 Base64 解码 def custom_b64_decode(s): # 移除填充点(如果有) s = s.rstrip('.') binary_str = '' for ch in s: idx = c_charset.index(ch) binary_str += '{:06b}'.format(idx) # 按8位分割还原字节 bytes_list = [] for i in range(0, len(binary_str), 8): byte_chunk = binary_str[i:i+8] if len(byte_chunk) == 8: bytes_list.append(int(byte_chunk, 2)) return bytes(bytes_list) # 目标密文 flag_encoded = 'BozjB3vlZ3ThBn9bZ2jhOH93ZaH9' # 第一步:凯撒逆向 b64_str = unrend(flag_encoded) print(f'凯撒逆向后的 Base64 串:{b64_str}') # 第二步:自定义 Base64 解码 original_bytes = custom_b64_decode(b64_str) flag = original_bytes.decode('ascii') print(f'解码结果:{flag}')