Loading... # Reverse(九) ## 数学不及格 https://ctf.show/challenges#%E6%95%B0%E5%AD%A6%E4%B8%8D%E5%8F%8A%E6%A0%BC-121 ida64打开找main ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { int result; // eax@16 __int64 v4; // rsi@16 signed int v5; // [sp+14h] [bp-4Ch]@4 char *endptr; // [sp+18h] [bp-48h]@4 char *v7; // [sp+20h] [bp-40h]@4 char *v8; // [sp+28h] [bp-38h]@4 char *v9; // [sp+30h] [bp-30h]@4 __int64 v10; // [sp+38h] [bp-28h]@4 __int64 v11; // [sp+40h] [bp-20h]@4 __int64 v12; // [sp+48h] [bp-18h]@4 __int64 v13; // [sp+50h] [bp-10h]@4 __int64 v14; // [sp+58h] [bp-8h]@1 v14 = *MK_FP(__FS__, 40LL); if ( argc != 5 ) // 输入参数必须为5个,包含运行文件名,所以关键看剩下四个 { puts("argc nonono"); exit(1); } v5 = (unsigned __int64)strtol(argv[4], &endptr, 16) - 25923; // 这四个都是16进制字符串 v10 = f(v5); v11 = strtol(argv[1], &v7, 16); v12 = strtol(argv[2], &v8, 16); v13 = strtol(argv[3], &v9, 16); if ( v10 - v11 != 151381742876LL ) { puts("argv1 nonono!"); exit(1); } if ( v10 - v12 != 117138004530LL ) { puts("argv2 nonono!"); exit(1); } if ( v10 - v13 != 155894355749LL ) { puts("argv3 nonono!"); exit(1); } if ( v5 + v13 + v12 + v11 != 1349446086540LL ) { puts("argv sum nonono!"); exit(1); } puts("well done!decode your argv!"); result = 0; v4 = *MK_FP(__FS__, 40LL) ^ v14; return result; } __int64 __fastcall f(signed int a1) { __int64 result; // rax@3 signed int i; // [sp+1Ch] [bp-14h]@4 __int64 v3; // [sp+20h] [bp-10h]@4 _QWORD *ptr; // [sp+28h] [bp-8h]@4 if ( a1 > 1 && a1 <= 200 ) // 输入范围必须在2-200 { ptr = malloc(8LL * a1); *ptr = 1LL; ptr[1] = 1LL; v3 = 0LL; for ( i = 2; i < a1; ++i ) { ptr[i] = ptr[i - 1] + ptr[i - 2]; // 斐波那契数列 v3 = ptr[i]; } free(ptr); result = v3; // 取最后一个 } else { result = 0LL; } return result; } ~~~ 已知条件: * f(argv[4] - 25923) - argv[1] = 151381742876 * f(argv[4] - 25923) - argv[2] = 117138004530 * f(argv[4] - 25923) - argv[3] = 155894355749 * argv[4] - 25923 + argv[1] + argv[2] + argv[3] = 1349446086540 python求解并转为字符串 ~~~python f = [1, 1] for i in range(2, 201): f.append(f[i-1] + f[i-2]) print(f) for i in range(2, 201): if f[i] * 3 + i+1 == 151381742876 + 117138004530 + 155894355749 + 1349446086540: # 这里i要注意 print('i is', i) break print('Argv[1]: %x' % (f[i] - 151381742876)) print('Argv[2]: %x' % (f[i] - 117138004530)) print('Argv[3]: %x' % (f[i] - 155894355749)) print('Argv[4]: %x' % (i + 1 + 25923)) flag = '' flag += bytes.fromhex(str(hex(f[i] - 151381742876)[2:])).decode('utf-8') flag += bytes.fromhex(str(hex(f[i] - 117138004530)[2:])).decode('utf-8') flag += bytes.fromhex(str(hex(f[i] - 155894355749)[2:])).decode('utf-8') flag += bytes.fromhex(str(hex(i + 1 + 25923)[2:])).decode('utf-8') print(flag) ~~~ ![image-20231214114755121.png](http://xherlock.top/usr/uploads/2023/12/750123562.png) ## peter的手机 https://ctf.bugku.com/challenges/detail/id/359.html 这题血亏,不仅花金币买了文件,还买了wp,最后才发现ida6.8、ida8打开进不去函数,看不了伪代码,各种问题,但是ida7.0就可以::cry:: 首先解压ipa文件,找到同名64位可执行文件(FirstOS),定位特殊字符串1997019247Acf,查看伪代码 ~~~c void __cdecl -[ViewController btn](ViewController *self, SEL a2) { UITextField *v2; // x0 void *v3; // x0 void *v4; // ST10_8 void *v5; // x0 UILabel *v6; // x0 void *v7; // ST08_8 UILabel *v8; // x0 void *v9; // x0 __int64 v10; // ST00_8 void *v11; // [xsp+18h] [xbp-18h] SEL v12; // [xsp+20h] [xbp-10h] ViewController *v13; // [xsp+28h] [xbp-8h] v13 = self; v12 = a2; NSLog(CFSTR("btn click do")); v2 = -[ViewController edtInput](v13, "edtInput"); v3 = (void *)objc_retainAutoreleasedReturnValue(v2); v4 = v3; v5 = objc_msgSend(v3, "text"); v11 = (void *)objc_retainAutoreleasedReturnValue(v5); objc_release(v4); if ( (unsigned __int64)objc_msgSend(v11, "isEqualToString:", CFSTR("1997019247Acf")) & 1 ) { v6 = -[ViewController EndValue](v13, "EndValue"); v7 = (void *)objc_retainAutoreleasedReturnValue(v6); objc_msgSend(v7, "setText:", CFSTR("right")); objc_release(v7); } else { v8 = -[ViewController EndValue](v13, "EndValue"); v9 = (void *)objc_retainAutoreleasedReturnValue(v8); objc_msgSend(v9, "setText:", CFSTR("error"), v9); objc_release(v10); } objc_storeStrong(&v11, 0LL); } ~~~ 可以看出来大概就是个比较字符串的功能,提交就过了。。。。 这道题是ios文件,简单不涉及算法,直接找字符串,要是难点的就不好做了 ## easyeasy-200 https://ctf.bugku.com/challenges/detail/id/127.html ~~~java public class MainActivity extends AppCompatActivity { String this_is_your_flag; static { System.loadLibrary("easyeasy"); } public MainActivity() { super(); } protected void onCreate(Bundle arg4) { ApplicationInfo v1 = this.getApplicationInfo(); int v2 = v1.flags & 2; v1.flags = v2; if(v2 != 0) { Process.killProcess(Process.myPid()); } super.onCreate(arg4); this.setContentView(2130968602); this.findViewById(2131427413).setOnClickListener(new View$OnClickListener() { public void onClick(View arg8) { MainActivity.this.this_is_your_flag = MainActivity.this.findViewById(2131427414).getText().toString(); // 定位赋值 if(MainActivity.this.this_is_your_flag.length() < 35) { Process.killProcess(Process.myPid()); } else if(MainActivity.this.this_is_your_flag.length() > 39) { Process.killProcess(Process.myPid()); } // flag长度35-39 MainActivity.this.this_is_your_flag = new Format().form(MainActivity.this.this_is_your_flag); // 双击查看发现作用是substring(5,38),说明长度38或39 if(MainActivity.this.this_is_your_flag.length() < 32) { Toast.makeText(MainActivity.this.getApplicationContext(), "No,more.", 1).show(); } else if(new Check().check(MainActivity.this.this_is_your_flag)) { // 核心判断函数 Toast.makeText(MainActivity.this.getApplicationContext(), "Congratulations!You got it.", 1).show(); } else { Toast.makeText(MainActivity.this.getApplicationContext(), "Oh no.Come on!", 1).show(); } } }); } } ~~~ 判断函数,经过分析发现这个代码会检查是否用了模拟器,以及调用lib文件里进行判断 ~~~java public class Check { String emulator; private static String[] known_pipes; static { Check.known_pipes = new String[]{"/dev/socket/qemud", "/dev/qemu_pipe"}; } public Check() { super(); this.emulator = Check.checkPipes(); } boolean check(String arg2) { boolean v0 = this.checkEmulator(this.emulator) ? false : this.checkPasswd(arg2); return v0; } protected native boolean checkEmulator(String arg1) { } private native boolean checkPasswd(String arg1) { } public static String checkPipes() { String v3; int v0 = 0; while(true) { if(v0 >= Check.known_pipes.length) { return "false"; } else if(new File(Check.known_pipes[v0]).exists()) { v3 = "true"; } else { ++v0; continue; } return v3; } return "false"; } } ~~~ ida64打开so文件(看其他wp说得armeabi-v7a下的so,不清楚具体原因,ida对比查看了下感觉应该是这个反编译的结果最简单?),直接搜索check可以找到对应的函数 ~~~c bool __fastcall Java_com_example_ring_wantashell_Check_checkPasswd(int a1, int a2, int a3) { int v5; // r4 const char *v6; // r6 char *v7; // r4 size_t v8; // r0 size_t i; // r1 char v10; // r2 size_t v11; // r0 char *v12; // r4 char *v14; // [sp+0h] [bp-1Ch] BYREF unsigned int v15; // [sp+8h] [bp-14h] BYREF v5 = 0; v6 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); if ( v6 ) { v7 = (char *)operator new[](0x21u); // 开辟v7空间 strcpy(v7, v6); // v6赋给v7,猜测v6位我们输入的passwd sub_8F7C((int)&v15, (char *)&unk_18843); // X查看发现被多次调用,猜测是系统调用函数,和加密无关 v8 = strlen(v7) - 1; if ( v8 ) { for ( i = 0; i < v8; ++i ) // 可以看出来做了个倒置字符串操作 { v10 = v7[i]; v7[i] = v7[v8]; v7[v8--] = v10; } } v11 = strlen(v7); sub_6ED0(&v15, v7, v11); encrypt((const char *)&v14, v15); // 很明显加密函数,猜测上面的函数将v7赋值给了v15,在这里进行加密,加密后的存到v14 v12 = v14; sub_69A4(&v14); sub_69A4(&v15); (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v6); return sub_7834((int)&secret, v12) == 0; } return v5; } ~~~ encrypt加密函数如下 ~~~c // attributes: thunk int __fastcall encrypt(const char *a1, unsigned int a2) { return _Z7encryptPKcj(a1, a2); } int __fastcall encrypt(const char *a1, unsigned int a2, int a3) { int v5; // r10 int v6; // r9 char v7; // t1 int v8; // r5 int v9; // r0 int v10; // r0 int i; // r6 int v12; // r5 char v14; // [sp+1h] [bp-17h] char v15; // [sp+2h] [bp-16h] char v16; // [sp+3h] [bp-15h] char v17; // [sp+4h] [bp-14h] unsigned __int8 v18; // [sp+5h] [bp-13h] BYREF unsigned __int8 v19; // [sp+6h] [bp-12h] unsigned __int8 v20; // [sp+7h] [bp-11h] int v21; // [sp+8h] [bp-10h] v5 = a3; *(_DWORD *)a1 = &unk_1D0E0; if ( a3 ) { v6 = 0; do { v7 = *(_BYTE *)a2++; *(&v18 + v6++) = v7; if ( v6 == 3 ) // 看代码可以感觉出来是base64加密 { v8 = 1; v9 = v18 >> 2; v14 = v18 >> 2; v15 = (16 * v18) & 0x30 | (v19 >> 4); v16 = (v20 >> 6) & 0xC3 | (4 * (v19 & 0xF)); v17 = v20 & 0x3F; while ( 1 ) { sub_6F28(a1, *(unsigned __int8 *)(dword_1D09C + (unsigned __int8)v9)); if ( v8 > 3 ) break; LOBYTE(v9) = *(&v14 + v8++); } v6 = 0; } --v5; } while ( v5 ); if ( v6 ) { if ( v6 <= 2 ) memset(&v18 + v6, 0, 3 - v6); v10 = v18 >> 2; v14 = v18 >> 2; v15 = (16 * v18) & 0x30 | (v19 >> 4); v16 = (v20 >> 6) & 0xC3 | (4 * (v19 & 0xF)); v17 = v20 & 0x3F; if ( v6 >= 0 ) { for ( i = 0; ; ++i ) { sub_6F28(a1, *(unsigned __int8 *)(dword_1D09C + (unsigned __int8)v10)); // 后面可以知道dword_1D09C存储了base64映射表(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/) if ( i >= v6 ) break; LOBYTE(v10) = *(&v15 + i); } } v12 = v6 - 1; while ( ++v12 <= 2 ) sub_6F28(a1, 46); } } return _stack_chk_guard - v21; } ~~~ 最后的secret位于bss段,通常存放全局变量(未初始化和初始化的) ![image-20231216143958364.png](http://xherlock.top/usr/uploads/2023/12/4271277101.png) 可以X查看调用这个secret的地方(从名字看很有可能是最终加密的字符串),发现sub_520c()同样调用了它, ~~~c int sub_520C() { int v1; // [sp+0h] [bp-18h] BYREF char v2[4]; // [sp+4h] [bp-14h] BYREF int v3; // [sp+8h] [bp-10h] sub_8F7C(&secret, "dHR0dGlldmFodG5vZGllc3VhY2VibGxlaHNhdG5hd2k.", (int)&v1); // 基本实锤这是个赋值字符串的函数 _cxa_atexit((void (__fastcall *)(void *))sub_69A4, &secret, &unk_1D000); sub_8F7C(&dword_1D09C, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", (int)v2); // 这里也是赋值了 _cxa_atexit((void (__fastcall *)(void *))sub_69A4, &dword_1D09C, &unk_1D000); return _stack_chk_guard - v3; } ~~~ 因此加密流程是这样的: 1. 输入字符串 2. 先检查环境是否为模拟器,如果是直接返回False,不是调用so文件中的checkPasswd函数检查密码 3. 密码做一个倒置 4. 密码base64加密 5. 和secret("dHR0dGlldmFodG5vZGllc3VhY2VibGxlaHNhdG5hd2k.")cmp对比,记得点号替换为= 解密: ![image-20231216145723269.png](http://xherlock.top/usr/uploads/2023/12/2734842540.png) ## easycrack-100 https://ctf.bugku.com/challenges/detail/id/128.html jeb查看mainactivity ~~~java public class MainActivity extends AppCompatActivity { class CheckText implements TextWatcher { CheckText(MainActivity arg1) { MainActivity.this = arg1; super(); } public void afterTextChanged(Editable arg5) { MainActivity.this.findViewById(2131427416).setText("Status: " + MainActivity.this.parseText(arg5.toString())); } public void beforeTextChanged(CharSequence arg1, int arg2, int arg3, int arg4) { } public void onTextChanged(CharSequence arg1, int arg2, int arg3, int arg4) { } } static { System.loadLibrary("native-lib"); } public MainActivity() { super(); } public String messageMe() { String v3 = ""; int v4 = 51; String[] v1 = this.getApplicationContext().getPackageName().split("\\."); char[] v6 = v1[v1.length - 1].toCharArray(); int v7 = v6.length; int v5; for(v5 = 0; v5 < v7; ++v5) { v4 ^= v6[v5]; v3 = v3 + (((char)v4)); } return v3; } protected void onCreate(Bundle arg4) { super.onCreate(arg4); this.setContentView(2130968603); this.findViewById(2131427416).setText(this.stringFromJNI()); this.findViewById(2131427415).addTextChangedListener(new CheckText(this)); } public native String parseText(String arg1) { } public native String stringFromJNI() { } } ~~~ 去看jni调用的代码,这次看的是armeabi里的so文件 ~~~c int __fastcall Java_com_njctf_mobile_easycrack_MainActivity_parseText(int a1, int a2, int a3) { int v5; // r0 int v6; // r0 int v7; // r0 size_t v8; // r4 size_t v9; // r6 size_t v10; // r2 unsigned int v11; // r3 unsigned int v12; // r6 unsigned int v13; // r0 unsigned __int8 *v14; // r4 const char *v15; // r5 size_t v16; // r2 size_t v18; // r0 const char *v19; // r1 const char *v21; // [sp+Ch] [bp-130h] unsigned __int8 *v23; // [sp+10h] [bp-12Ch] char *v24; // [sp+14h] [bp-128h] size_t v25; // [sp+18h] [bp-124h] const char *v26; // [sp+18h] [bp-124h] _WORD v27[8]; // [sp+1Ch] [bp-120h] BYREF char v28[256]; // [sp+2Ch] [bp-110h] BYREF v5 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 24))(a1, "com/njctf/mobile/easycrack/MainActivity"); v6 = (*(int (__fastcall **)(int, int, const char *, const char *))(*(_DWORD *)a1 + 132))( a1, v5, "messageMe", "()Ljava/lang/String;"); v7 = j__JNIEnv::CallObjectMethod(a1, a2, v6); v25 = 0; v24 = (char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, v7, 0); // 调用了java里的messageMe(一个加密字符串的函数) v21 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); v8 = j_strlen(v21); // 输入字符串的长度 v9 = j_strlen(v24); // messageMe返回字符串长度 v23 = (unsigned __int8 *)j_malloc(v8); // 分配和输入字符串大小相同的空间 if ( v8 ) { do // 循环加密,加密后的字符串存到v23 { if ( v9 ) { v10 = 0; do { v23[v25 + v10] = v24[v10] ^ v21[v25 + v10]; v11 = v25 + v10++ + 1; } while ( v10 < v9 && v11 < v8 ); } v25 += v9; } while ( v25 < v8 ); } memset(v28, 0, sizeof(v28)); strcpy((char *)v27, "I_am_the_key"); HIBYTE(v27[6]) = 0; // 这里比较奇怪,0不是相当于提前终止字符串吗 v27[7] = 0; v12 = v8; // v12存储输入字符串长度 v13 = j_strlen((const char *)v27); // 新的长度为6 j_init((unsigned __int8 *)v28, (unsigned __int8 *)v27, v13); // 做到这里看到v28是256位空的,v27是密钥,可以联想到之前的RC4加密算法 v14 = v23; // v14存储加密后的字符串 j_crypt((unsigned __int8 *)v28, v23, v12); // 传入的分别是复制过的256位,v23前面异或过的字符串,v12是长度 v26 = (const char *)j_malloc((2 * v12) | 1); // v26大小为2*v12+1,由下面可知存的是2位大写十六进制字符串 if ( v12 ) { v15 = v26; do { j_snprintf(v15, 3, "%02X", *v14); v15 += 2; --v12; ++v14; } while ( v12 ); } v16 = j_strlen(v26); // 长度为2*v12 if ( v16 && !j_strncmp(v26, compare, v16) ) // compare='C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4' { j___android_log_print(2, "NJCTF-easycrack", "success: %s", v26); v18 = j_strlen(compare); if ( !j_strncmp(v26, compare, v18) ) v19 = "YOU GOT IT!"; else v19 = "Victory is in sight."; return (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 668))(a1, v19); } else { j___android_log_print(6, "NJCTF-easycrack", "failed : %s", v26); return (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 668))(a1, "Try again."); } } ~~~ 分析得到加密流程如下: 1. 首先得到messageMe返回的字符串==V7D=^,M.E== ![image-20231216170929414.png](http://xherlock.top/usr/uploads/2023/12/911548832.png) 2. 接着输入的字符串循环和上面返回的字符串异或 3. 接着RC4初始化,密钥为"I_am_the_key",这里放张RC4初始化代码和这里j_init的代码对比,可以看到都是两个256循环 ![image-20231216180055814.png](http://xherlock.top/usr/uploads/2023/12/2366816969.png) 4. 接着RC4加密,这里再对比下: ![image-20231216180435502.png](http://xherlock.top/usr/uploads/2023/12/2971639652.png) 5. 得到的结果存到了v23,也就是v14,接着转为2位长度的16进制,并和compare比较 python脚本解密 ~~~python def decrypt(v23, v24): v8 = len(v23) v9 = len(v24) v25 = 0 v21 = [] v11 = 0 if v8: while v25 < v8: if v9: v10 = 0 while v10 < v9 and v11 <= v8: # 要注意原来C语言判断是执行完一个循环才判断,python执行循环前就要判断,条件不同 v21.append(v24[v10] ^ v23[v25 + v10]) v10 += 1 v11 = v25 + v10 + 1 v25 += v9 for i in v21: print(chr(i), end='') print() def get_key(key): key_len = len(key) a1, a2 = [], [] for i in range(256): a1.append(i) a2.append(ord(key[i % key_len])) v5 = 0 for i in range(256): v5 = (v5 + a1[i] + a2[i]) % 256 a1[i], a1[v5] = a1[v5], a1[i] return a1 def rc4_decrypt(key, cipher): v7, v6 = 0, 0 v23 = [] for i in range(len(cipher)): v7 = (v7 + 1) % 256 v6 = (v6 + key[v7]) % 256 key[v7], key[v6] = key[v6], key[v7] v23.append(key[(key[v6] + key[v7]) % 256] ^ cipher[i]) return v23 def messageMe(): message = 'easycrack' new_message = [] v4 = 51 for i in range(len(message)): v4 ^= ord(message[i]) new_message.append(v4) return new_message if __name__ == '__main__': # RC4初始化 key = get_key('I_am_the_key') # 求enflag compare = 'C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4' cipher = [int(compare[i]+compare[i+1], 16) for i in range(0, len(compare), 2)] # RC4解密 v23 = rc4_decrypt(key, cipher) # 求messageMe返回字符串 v24 = messageMe() decrypt(v23, v24) ~~~ 结果:It_s_a_easyCrack_for_beginners,太扎心了,怎么觉得不简单。。。 读反编译的代码还是不太顺利,接着练吧 ## fake-func https://ctf.bugku.com/challenges/detail/id/142.html jeb查看 ~~~java public class MainActivity extends AppCompatActivity { public MainActivity() { super(); } protected void onCreate(Bundle arg2) { super.onCreate(arg2); this.setContentView(2131296284); this.findViewById(2131165218).setOnClickListener(new View$OnClickListener() { public void onClick(View arg3) { if(check.checkflag(MainActivity.this.findViewById(2131165238).getText().toString())) { // 很明显这里做了判断 Toast.makeText(MainActivity.this, "you are right~!", 1).show(); } else { Toast.makeText(MainActivity.this, "wrong!", 1).show(); } } }); } } ~~~ 跳转check发现果然又使用了JNI调用 ~~~java public class check { static { System.loadLibrary("checkso"); } public check() { super(); } public static native boolean checkflag(String arg0) { } } ~~~ 轻车熟路apktool解压,ida静态分析lib下的so文件,定位checkflag函数 ~~~c unsigned int __fastcall Java_com_example_p7xxtmx_1g_fakefunc_check_checkflag(int a1) { const char *v1; // r4@1 v1 = (const char *)(*(int (**)(void))(*(_DWORD *)a1 + 676))(); // 应该就是我们的输入 sub_E08(); return __clz(strcmp(v1, off_6004)) >> 5; // off_6004='c2RuaXNjc2RuaXNjYWJjZA=='看着很像base64加密,解码得到sdniscsdniscabcd } int sub_E08() { char *v0; // r4@1 size_t v1; // r1@1 v0 = off_6004; v1 = strlen(off_6004); return sub_16D8(v0, v1); // 结合里面的代码以及传入的参数可知是base64解密函数 } ~~~ 分别尝试这个字符串,以及解码的字符串均不对,联想到题目提示fake func,猜测函数发生变化 首先查看字符串发现除了上面那个还有另一个字符串,双击进去按X查看调用地方,并F5查看伪代码,以及X查看调用函数 ![image-20231217100602291.png](http://xherlock.top/usr/uploads/2023/12/2536376903.png) 可以看到strcmp被一个j_registerInlineHook做了处理,并调用了sub_E28函数,里面存储了正好是另一个特殊字符串 ~~~c int __fastcall sub_E28(int a1) { int v1; // r4@1 int v2; // r0@1 int v3; // r0@1 v1 = a1; v2 = sub_E08(); // 上面也出现过,就是base64解密 v3 = sub_1388(v1, v2); // v2是解密后的字符串,v1是我们的输入 return dword_6008(v3, "K4/7/faihmk9/WEMlfuFdpgrP86ckd4oQQ/UeAiZdx8="); // AES加密后和这个字符串比较 } int sub_EC8() { int result; // r0@2 if ( j_registerInlineHook(&strcmp, sub_E28, &dword_6008) ) { result = -1; } else { result = j_inlineHook(&strcmp); if ( result ) result = -1; } return result; } ~~~ 网上搜了下直接发现类似代码 <img src="http://xherlock.top/usr/uploads/2023/12/1995814353.png" alt="image-20231217110451833" style="zoom: 67%;" style=""> registerInlineHook传入的参数:第一个是要hook的目标函数地址,第二个是替换函数的指针,第三个是保留函数原来的指针 因此实际要分析的函数是上面这个sub_E28(),这里强烈安利ida插件findcrypt,真不是一般好用,不过配置了半天发现我这个版本ida不好用,果断换成了吾爱的ida7.7(好用滴很,还自带了python3.8) 结合插件的提示可知,这个函数属于AES加密,那上面 ![image-20231217153323606.png](http://xherlock.top/usr/uploads/2023/12/2339582024.png) 在线AES解密如下 ![image-20231217154148217.png](http://xherlock.top/usr/uploads/2023/12/1730078216.png) ## reverse_re3 ~~~c __int64 sub_940() { int v0; // eax int v2; // [rsp+8h] [rbp-218h] int v3; // [rsp+Ch] [rbp-214h] char v4[520]; // [rsp+10h] [rbp-210h] BYREF unsigned __int64 v5; // [rsp+218h] [rbp-8h] v5 = __readfsqword(0x28u); v3 = 0; memset(v4, 0, 0x200uLL); _isoc99_scanf(&unk_1278, v4, v4); while ( 1 ) // 由特征可知大概是个迷宫问题,只能按wasd { do { v2 = 0; sub_86C(); v0 = v4[v3]; if ( v0 == 'd' ) { v2 = sub_E23(); } else if ( v0 > 'd' ) { if ( v0 == 's' ) { v2 = sub_C5A(); } else if ( v0 == 'w' ) { v2 = sub_A92(); } } else { if ( v0 == 27 ) return 0xFFFFFFFFLL; if ( v0 == 'a' ) v2 = sub_FEC(); } ++v3; } while ( v2 != 1 ); if ( dword_202AB0 == 2 ) break; ++dword_202AB0; } puts("success! the flag is flag{md5(your input)}"); return 1LL; } ~~~ 再去看sub_86C函数,里面大致说明了迷宫的形状为3个(15\*15)正方形,i表示行,j表示列,且分别赋值给dword_202AB4和dword_202AB8 ~~~c unsigned __int64 sub_86C() { int i; // [rsp+0h] [rbp-10h] int j; // [rsp+4h] [rbp-Ch] unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); for ( i = 0; i <= 14; ++i ) { for ( j = 0; j <= 14; ++j ) { if ( dword_202020[225 * dword_202AB0 + 15 * i + j] == 3 ) // 联系dword_202AB0==2退出,说明走三个方形,每个225大小 { dword_202AB4 = i; // 等于3表示这是当前位置 dword_202AB8 = j; break; } } } return __readfsqword(0x28u) ^ v3; } ~~~ 再去看按下方向键的操作 ~~~c __int64 sub_E23() // 'd' { if ( dword_202AB8 != 14 ) // 首先判断dword_202AB8是否等于14,不等则返回0,继续循环按方向键 { if ( dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] == 1 ) // 先判断右移是否可以走(是否等于1) { dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] = 3; // 如果可以这个位置置3,说明这是当前位置 dword_202020[225 * dword_202AB0 + 15 * dword_202AB4 + dword_202AB8] = 1; // 原来位置置1 } else if ( dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] == 4 ) // 如果等于4就要退出循环,dword_202AB0++,说明当前这个方形迷宫走完了 { return 1LL; } // 如果不为1也不为0,说明走的不对,走的无效 } return 0LL; } __int64 sub_C5A() // 's' { if ( dword_202AB4 != 14 ) { if ( dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] == 1 ) // 按's'是直接进入下一行+15 { dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] = 3; dword_202020[225 * dword_202AB0 + 15 * dword_202AB4 + dword_202AB8] = 1; } else if ( dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] == 4 ) { return 1LL; } } return 0LL; } ~~~ 至此基本分析完成,迷宫为1的能走,为3表示当前位置,为4表示成功走到,关键要看dword_202020里的值。ida在它上面右键convert to python list(dword) 第一个:ddsssddddsssdss <img src="http://xherlock.top/usr/uploads/2023/12/3567844189.png" alt="image-20231218101726454" style="zoom: 50%;" style=""> 第二个:dddddsssddddsssaassssddds <img src="http://xherlock.top/usr/uploads/2023/12/1517325770.png" alt="image-20231218102155982" style="zoom:50%;" style=""> 第三个:ddssddwddssssssdddssssdddss <img src="http://xherlock.top/usr/uploads/2023/12/300257407.png" alt="image-20231218102038385" style="zoom:50%;" style=""> ![image-20231218102357832.png](http://xherlock.top/usr/uploads/2023/12/2588519523.png) ## crypt ida64查看main代码 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // eax int v4; // eax void *v5; // rax void *v7; // rax int i; // [rsp+24h] [rbp-D4h] _DWORD *v9; // [rsp+28h] [rbp-D0h] char v10[32]; // [rsp+30h] [rbp-C8h] BYREF char Str[128]; // [rsp+50h] [rbp-A8h] BYREF strcpy(Str, "12345678abcdefghijklmnopqrspxyz");// copy到Str变量 memset(&Str[32], 0, 0x60ui64); // 填充Str memset(v10, 0, 0x17ui64); sub_1400054D0("%s", v10); // 输入字符串 v9 = malloc(0x408ui64); // 正好是分配了(256+2)*4=0x408个字节 v3 = strlen(Str); // 获取Str长度,应该是31 sub_140001120(v9, (__int64)Str, v3); // 获取的了一个初始化的v9,长度为258,内容是0、0、0-255 v4 = strlen(v10); // 获取输入字符串长度 sub_140001240(v9, (__int64)v10, v4); // 基本可以判断是rc4加密 for ( i = 0; i < 22; ++i ) { if ( ((unsigned __int8)v10[i] ^ 0x22) != byte_14013B000[i] ) // 这里还做了个异或 { v5 = (void *)sub_1400015A0(&off_14013B020, "error"); _CallMemberFunction0(v5, sub_140001F10); return 0; } } v7 = (void *)sub_1400015A0(&off_14013B020, "nice job"); _CallMemberFunction0(v7, sub_140001F10); return 0; } ~~~ ~~~c __int64 __fastcall sub_140001120(_DWORD *a1, __int64 a2, int a3) { __int64 result; // rax int i; // [rsp+0h] [rbp-28h] int j; // [rsp+0h] [rbp-28h] int v6; // [rsp+4h] [rbp-24h] int v7; // [rsp+8h] [rbp-20h] int v8; // [rsp+Ch] [rbp-1Ch] _DWORD *v9; // [rsp+10h] [rbp-18h] *a1 = 0; a1[1] = 0; v9 = a1 + 2; for ( i = 0; i < 256; ++i ) v9[i] = i; // 初始化v9,也就是a1[2]之后的 v6 = 0; result = 0i64; LOBYTE(v7) = 0; for ( j = 0; j < 256; ++j ) { v8 = v9[j]; v7 = (unsigned __int8)(*(_BYTE *)(a2 + v6) + v8 + v7);// a2是Str字符串的地址 v9[j] = v9[v7]; v9[v7] = v8; // 很明显做了交换,结合256,这些都是RC4初始化的特征 if ( ++v6 >= a3 ) // a3是Str长度 v6 = 0; result = (unsigned int)(j + 1); } return result; } ~~~ 直接借用以前的脚本解密即可 ~~~python def get_key(key): key_len = len(key) a1, a2 = [], [] for i in range(256): a1.append(i) a2.append(ord(key[i % key_len])) v5 = 0 for i in range(256): v5 = (v5 + a1[i] + a2[i]) % 256 a1[i], a1[v5] = a1[v5], a1[i] return a1 def decrypt(enc_key, enflag): v7, v6 = 0, 0 flag = '' for i in range(len(enflag)): v7 = (v7 + 1) % 256 v6 = (v6 + enc_key[v7]) % 256 enc_key[v7], enc_key[v6] = enc_key[v6], enc_key[v7] flag += chr(enc_key[(enc_key[v6] + enc_key[v7]) % 256] ^ enflag[i]) return flag if __name__ == '__main__': enc_key = get_key('12345678abcdefghijklmnopqrspxyz') enflag = [i^0x22 for i in [0x9E, 0xE7, 0x30, 0x5F, 0xA7, 0x01, 0xA6, 0x53, 0x59, 0x1B, 0x0A, 0x20, 0xF1, 0x73, 0xD1, 0x0E, 0xAB, 0x09, 0x84, 0x0E, 0x8D, 0x2B]] flag = decrypt(enc_key, enflag) print(flag) ~~~ ## bad_python 尝试在线pyc反编译以及uncompyle6均报错编译失败,查询了下可能原因为文件头被篡改,因此尝试手动生成一个pyc文件,替换文件头 https://zhuanlan.zhihu.com/p/617737294 写一个print hi,由于原始pyc是python36编译的,我们也要用python3.6;编译获取pyc命令是python -m py_compile .\hi.py,命令会在当前目录下生成\__pycache\_\_,里面就是pyc winhex打开获取前16个字节复制到原来的上面,再使用uncompyle6即可成功反编译,获取的python如下 ~~~python # uncompyle6 version 3.9.0 # Python bytecode version base 3.6 (3379) # Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)] # Embedded file name: pyre.py # Compiled at: 2023-12-18 18:22:59 # Size of source mod 2**32: 11 bytes from ctypes import * from Crypto.Util.number import bytes_to_long from Crypto.Util.number import long_to_bytes def encrypt(v, k): v0 = c_uint32(v[0]) v1 = c_uint32(v[1]) sum1 = c_uint32(0) delta = 195935983 for i in range(32): v0.value += (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3] sum1.value += delta v1.value += (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3] return ( v0.value, v1.value) if __name__ == '__main__': flag = input('please input your flag:') k = [255, 187, 51, 68] if len(flag) != 32: print('wrong!') exit(-1) a = [] for i in range(0, 32, 8): v1 = bytes_to_long(bytes(flag[i:i + 4], 'ascii')) v2 = bytes_to_long(bytes(flag[i + 4:i + 8], 'ascii')) a += encrypt([v1, v2], k) enc = [ '4006073346', '2582197823', '2235293281', '558171287', '2425328816', '1715140098', '986348143', '1948615354'] for i in range(8): if enc[i] != a[i]: print('wrong!') exit(-1) print('flag is flag{%s}' % flag) ~~~ 只在源代码基础上稍加修改即可 ~~~python # uncompyle6 version 3.9.0 # Python bytecode version base 3.6 (3379) # Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)] # Embedded file name: pyre.py # Compiled at: 2023-12-18 18:22:59 # Size of source mod 2**32: 11 bytes from ctypes import * from Crypto.Util.number import bytes_to_long from Crypto.Util.number import long_to_bytes def decrypt(v, k): v0 = c_uint32(int(v[0])) v1 = c_uint32(int(v[1])) sum1 = c_uint32(0) delta = 195935983 sum1.value += delta * 32 # 原来到最后加到了delta * 32,因此逆向初始值为此 for i in range(32): v1.value -= (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3] # 原来加号改为减号 sum1.value -= delta v0.value -= (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3] return ( v0.value, v1.value) if __name__ == '__main__': flag = '' k = [255, 187, 51, 68] a = [] enc = [ '4006073346', '2582197823', '2235293281', '558171287', '2425328816', '1715140098', '986348143', '1948615354'] for i in range(0, 8, 2): v1 = enc[i] v2 = enc[i+1] a += decrypt([v1, v2], k) for i in range(8): flag += long_to_bytes(a[i]).decode('utf-8') print('flag is flag{%s}' % flag) ~~~ 结果:flag is flag{Th1s_1s_A_Easy_Pyth0n__R3veRse_0} ## easyre-xctf 这道题类似misc思路,将flag隐藏到里面没用的函数里。首先upx脱壳,ida64查找到特殊字符串“d_0n3_4nd_tw0}”,同时前面有个==f_part2==名称,再去看函数可以看到有一个part1,里面传递了前半部分flag(按R转为字符,同时小段存储要反转下) <img src="http://xherlock.top/usr/uploads/2023/12/1750482539.png" alt="image-20231220111248450" style="zoom:50%;" style=""> 得到flag{UPX_4nd_0n3_4nd_tw0} ## CatFly 这题真的是新手题么,太打击了,代码太多了反编译又看不出点头绪,对着wp才分析出代码 首先尝试运行(linux下,记得chmod +x赋予权限) ![image-20231221092921497.png](http://xherlock.top/usr/uploads/2023/12/1582828535.png) 可以看到第一行一直再打印字符,最后一行一直在计数,所以关键是找到打印上面这些无序字符的代码 ida64找到main,再去找printf ~~~c while ( v13 ) { if ( dword_E104 ) printf("\x1B[H"); else printf("\x1B[u"); for ( k = dword_E1EC; k < dword_E1F0; ++k ) { for ( m = dword_E1F4; m < dword_E1F8; ++m ) { if ( k <= 23 || k > 42 || m >= 0 ) { if ( m >= 0 && (unsigned int)k <= 0x3F && m <= 63 ) { v19 = off_FA20[v24][k][m]; off_FA88 = sub_6314((unsigned int)v24, k, m, (__int64)v12);// 这里是关键的赋值 } else { v19 = 44; } } else { v18 = (2 - m) % 16 / 8; if ( ((v24 >> 1) & 1) != 0 ) v18 = 1 - v18; s[128] = (__int64)",,>>&&&+++###==;;;,,"; v19 = asc_BFE3[v18 - 23 + k]; if ( !v19 ) v19 = 44; } if ( v25 ) { printf("%s", *((const char **)&unk_FCC0 + v19)); } else if ( v19 == v22 || !*((_QWORD *)&unk_FCC0 + v19) ) { printf("%s", off_FA88);// 这里是关键的打印 } else { v22 = v19; printf("%s%s", *((const char **)&unk_FCC0 + v19), off_FA88); } } sub_65E2(1LL); } if ( dword_E100 ) { time(&time1); v11 = difftime(time1, timer); v10 = sub_63FF((unsigned int)(int)v11); for ( n = (dword_E1FC - 29 - v10) / 2; n > 0; --n ) putchar(32); dword_E1E8 += printf("\x1B[1;37mYou have nyaned for %d times!\x1B[J\x1B[0m", (unsigned int)++dword_108E0); } v22 = 0; ++v23; if ( dword_104C4 && v23 == dword_104C4 ) sub_6471(); if ( !off_FA20[++v24] ) v24 = 0LL; usleep(1000 * v27); } ~~~ 重点看sub_6314函数 ~~~c char *__fastcall sub_6314(__int64 a1, int a2, int a3, __int64 a4) { if ( a2 != 18 ) // 必须a2=18 return (char *)a4; if ( a3 <= 4 || a3 > 54 ) // a3范围5~54,共50个 return (char *)a4; byte_104C9 = 32; dword_E120[a3 - 5] ^= sub_62B5(); if ( (unsigned __int8)sub_62E3(dword_E120[a3 - 5]) ) byte_104C8 = dword_E120[a3 - 5] & 0x7F; else byte_104C8 = 32; return &byte_104C8; } unsigned int dword_E1E8 = 0x00001106; unsigned int dword_E120[50] = { 0x000027FB, 0x000027A4, 0x0000464E, 0x00000E36, 0x00007B70, 0x00005E7A, 0x00001A4A, 0x000045C1, 0x00002BDF, 0x000023BD, 0x00003A15, 0x00005B83, 0x00001E15, 0x00005367, 0x000050B8, 0x000020CA, 0x000041F5, 0x000057D1, 0x00007750, 0x00002ADF, 0x000011F8, 0x000009BB, 0x00005724, 0x00007374, 0x00003CE6, 0x0000646E, 0x0000010C, 0x00006E10, 0x000064F4, 0x00003263, 0x00003137, 0x000000B8, 0x0000229C, 0x00007BCD, 0x000073BD, 0x0000480C, 0x000014DB, 0x000068B9, 0x00005C8A, 0x00001B61, 0x00006C59, 0x00005707, 0x000009E6, 0x00001FB9, 0x00002AD3, 0x000076D4, 0x00003113, 0x00007C7E, 0x000011E0, 0x00006C70 }; __int64 sub_62B5() { dword_E1E8 = 1103515245 * dword_E1E8 + 12345; return (dword_E1E8 >> 10) & 0x7FFF; } _BOOL8 __fastcall sub_62E3(char a1) { return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20; } ~~~ 解密 ~~~c++ #include<iostream> #include<string.h> #include <time.h> using namespace std; unsigned int dword_E1E8 = 0x00001106; unsigned int dword_E120[50] = { 0x000027FB, 0x000027A4, 0x0000464E, 0x00000E36, 0x00007B70, 0x00005E7A, 0x00001A4A, 0x000045C1, 0x00002BDF, 0x000023BD, 0x00003A15, 0x00005B83, 0x00001E15, 0x00005367, 0x000050B8, 0x000020CA, 0x000041F5, 0x000057D1, 0x00007750, 0x00002ADF, 0x000011F8, 0x000009BB, 0x00005724, 0x00007374, 0x00003CE6, 0x0000646E, 0x0000010C, 0x00006E10, 0x000064F4, 0x00003263, 0x00003137, 0x000000B8, 0x0000229C, 0x00007BCD, 0x000073BD, 0x0000480C, 0x000014DB, 0x000068B9, 0x00005C8A, 0x00001B61, 0x00006C59, 0x00005707, 0x000009E6, 0x00001FB9, 0x00002AD3, 0x000076D4, 0x00003113, 0x00007C7E, 0x000011E0, 0x00006C70 }; int sub_62B5() { dword_E1E8 = 1103515245 * dword_E1E8 + 12345; return (dword_E1E8 >> 10) & 0x7FFF; } bool sub_62E3(char a1) { return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20; } int calc(int cnt) { int i=0; while(cnt){ cnt=cnt/10; i++; } return i; } int main () { int cnt = 0; clock_t start, end; //定义clock_t变量 start = clock(); //开始时间 while (1) { char flag[50]; for (int i = 0; i < 50; i++) { dword_E120[i] ^= sub_62B5(); if (sub_62E3(dword_E120[i])) flag[i] = dword_E120[i] & 0x7F; else flag[i] = ' '; } if (!strncmp(flag, "CatCTF", 6)) { cout << flag << endl; break; } cnt++; dword_E1E8 += 41; dword_E1E8 += calc(cnt); // 后面记得+了printf返回值 } cout << "cnt: " << cnt << endl; end = clock(); //结束时间 cout << "took " << double(end-start)/CLOCKS_PER_SEC << "s" << endl; //输出时间(单位:s) } ~~~ ## BABYRE ida64查看main ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { char s[24]; // [rsp+0h] [rbp-20h] BYREF int v5; // [rsp+18h] [rbp-8h] int i; // [rsp+1Ch] [rbp-4h] for ( i = 0; i <= 181; ++i ) judge[i] ^= 0xCu; // 这里查看是数组形式,实际上异或完是汇编代码 printf("Please input flag:"); __isoc99_scanf("%20s", s); v5 = strlen(s); if ( v5 == 14 && (*(unsigned int (__fastcall **)(char *))judge)(s) ) // 但是这里却看起来judge是个函数 puts("Right!"); else puts("Wrong!"); return 0; } ~~~ 这道题实际上judge数组是汇编指令,但ida反编译识别成了数据 <img src="http://xherlock.top/usr/uploads/2023/12/587265212.png" alt="image-20231221172329610" style="zoom:50%;" style=""> 因此需要调试,运行到judge异或完查看其具体代码。这里学习到了如何远程连接Linux来进行调试(这里文件只能在linux上跑) https://blog.csdn.net/m0_46296905/article/details/115794076 在printf上打断点,然后F5查看judge,并按下C转换为代码,如下图,已经有函数代码的逻辑 ![image-20231221172717614.png](http://xherlock.top/usr/uploads/2023/12/875447602.png) 再按下P生成函数,F5即可查看伪代码 ~~~c __int64 __fastcall judge(__int64 a1) { char v2[5]; // [rsp+8h] [rbp-20h] BYREF char v3[9]; // [rsp+Dh] [rbp-1Bh] BYREF int i; // [rsp+24h] [rbp-4h] qmemcpy(v2, "fmcd", 4); v2[4] = 127; qmemcpy(v3, "k7d;V`;np", sizeof(v3)); for ( i = 0; i <= 13; ++i ) *(_BYTE *)(i + a1) ^= i; // 先按位异或 for ( i = 0; i <= 13; ++i ) { if ( *(_BYTE *)(i + a1) != v2[i] ) // 再和v2比较(实际上v2+v3) return 0LL; } return 1LL; } ~~~ ![image-20231221180040422.png](http://xherlock.top/usr/uploads/2023/12/2633358965.png) ## parallel-comparator-200 ~~~c #include <stdlib.h> #include <stdio.h> #include <pthread.h> #define FLAG_LEN 20 void * checking(void *arg) { char *result = malloc(sizeof(char)); char *argument = (char *)arg; *result = (argument[0]+argument[1]) ^ argument[2]; return result; } int highly_optimized_parallel_comparsion(char *user_string) { int initialization_number; int i; char generated_string[FLAG_LEN + 1]; generated_string[FLAG_LEN] = '\0'; while ((initialization_number = random()) >= 64); // 随机数大于等于64,运行代码发现固定41 int first_letter; first_letter = (initialization_number % 26) + 97; pthread_t thread[FLAG_LEN]; char differences[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7}; char *arguments[20]; for (i = 0; i < FLAG_LEN; i++) { arguments[i] = (char *)malloc(3*sizeof(char)); arguments[i][0] = first_letter; arguments[i][1] = differences[i]; arguments[i][2] = user_string[i]; pthread_create((pthread_t*)(thread+i), NULL, checking, arguments[i]); // 创建线程,调用checking函数,传入参数arguments[i] } void *result; int just_a_string[FLAG_LEN] = {115, 116, 114, 97, 110, 103, 101, 95, 115, 116, 114, 105, 110, 103, 95, 105, 116, 95, 105, 115}; // 没用 for (i = 0; i < FLAG_LEN; i++) { pthread_join(*(thread+i), &result); // 等待线程结束,result接收该线程返回值 generated_string[i] = *(char *)result + just_a_string[i]; free(result); free(arguments[i]); } int is_ok = 1; for (i = 0; i < FLAG_LEN; i++) { if (generated_string[i] != just_a_string[i]) // 要想相等,result为0 return 0; } return 1; } int main() { char *user_string = (char *)calloc(FLAG_LEN+1, sizeof(char)); fgets(user_string, FLAG_LEN+1, stdin); int is_ok = highly_optimized_parallel_comparsion(user_string); if (is_ok) printf("You win!\n"); else printf("Wrong!\n"); return 0; } ~~~ 题目很简单,解题过程中配c语言环境搞了半天,两点要注意的 * 用gcc编译而不是g++ * 再配置中加上参数-lpthread才行,不然会报错 ~~~c++ #include <iostream> using namespace std; int main() { int FLAG_LEN = 20; char differences[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7}; for (int j = 0; j < 26; j++) { for (int i = 0; i < FLAG_LEN; i++) { printf("%c", (differences[i]+j+97)^0); } printf("\n"); } } ~~~ 可以找到一个规律的字符串lucky_hacker_you_are ## secret-galaxy-300 这题类似于Misc隐藏信息的思路,题目压缩包三种文件是不同平台的可执行文件 首先ida32查到很多打印的字符串,主要都是星系,ollydbg下个断点,可以找到很多星系名 ![image-20231221215357729.png](http://xherlock.top/usr/uploads/2023/12/2469169833.png) 发现打印只打印前五个,所以第六个隐藏猫腻 ~~~c int __cdecl print_starbase(int a1) { int result; // eax const char *v2; // edx int i; // [esp+1Ch] [ebp-Ch] puts("--------------GALAXY DATABASE-------------"); printf("%10s | %s | %s\n", "Galaxy name", "Existence of life", "Distance from Earth"); result = puts("-------------------------------------------"); for ( i = 0; i <= 4; ++i ) // 前五个 { if ( *(_DWORD *)(24 * i + a1 + 8) == 1 ) v2 = "INHABITED"; else v2 = "IS NOT INHABITED"; // 都是这个选项 result = printf("%11s | %17s | %d\n", *(const char **)(24 * i + a1), v2, *(_DWORD *)(24 * i + a1 + 4)); } return result; } ~~~ 因此 ollydbg改汇编代码判断,并没有看到什么flag ![image-20231221215515304.png](http://xherlock.top/usr/uploads/2023/12/3651500754.png) 因此转变思路去找函数,发现一个没有被主函数调用的函数 ~~~c int __libc_csu_gala() { int result; // eax sc[0] = off_409014; sc[3] = &byte_40DAC0; sc[1] = 31337; sc[2] = 1; // 可以看到连续的字节被赋值,且off这些变量都是字符串数组 byte_40DAC0 = off_409004[0][8]; byte_40DAC1 = off_409010[0][7]; byte_40DAC2 = off_409008[0][4]; byte_40DAC3 = off_409004[0][6]; byte_40DAC4 = off_409004[0][1]; byte_40DAC5 = off_409008[0][2]; byte_40DAC6 = '_'; // 95正好是下划线 byte_40DAC7 = off_409004[0][8]; byte_40DAC8 = off_409004[0][3]; byte_40DAC9 = off_40900C[0][5]; byte_40DACA = 95; byte_40DACB = off_409004[0][8]; byte_40DACC = off_409004[0][3]; byte_40DACD = off_409004[0][4]; byte_40DACE = off_409010[0][6]; byte_40DACF = off_409010[0][4]; byte_40DAD0 = off_409004[0][2]; byte_40DAD1 = 95; byte_40DAD2 = off_409010[0][6]; result = *((unsigned __int8 *)off_409008[0] + 3); byte_40DAD3 = off_409008[0][3]; byte_40DAD4 = 0; return result; } ~~~ 看到字符串内容的方法就是ida断点到return result前,双击可以查看,发现特殊字符串即为flag ![image-20231221220106369.png](http://xherlock.top/usr/uploads/2023/12/3348818935.png) 最后修改:2023 年 12 月 22 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏