Loading... # Reverse(七) ## Java逆向解密 https://buuoj.cn/challenges#Java%E9%80%86%E5%90%91%E8%A7%A3%E5%AF%86 jd-gui逆向class文件为java ~~~java import java.util.ArrayList; import java.util.Scanner; public class Reverse { public static void main(String[] args) { Scanner s = new Scanner(System.in); System.out.println("Please input the flag ); String str = s.next(); System.out.println("Your input is ); System.out.println(str); char[] stringArr = str.toCharArray(); Encrypt(stringArr); } public static void Encrypt(char[] arr) { ArrayList<Integer> Resultlist = new ArrayList<>(); for (int i = 0; i < arr.length; i++) { int result = arr[i] + 64 ^ 0x20; // 转为数字格式 Resultlist.add(Integer.valueOf(result)); } int[] KEY = { 180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65 }; ArrayList<Integer> KEYList = new ArrayList<>(); for (int j = 0; j < KEY.length; j++) KEYList.add(Integer.valueOf(KEY[j])); System.out.println("Result:"); if (Resultlist.equals(KEYList)) { // 逐一匹配 System.out.println("Congratulations); } else { System.err.println("Error); } } } ~~~ python解密即可 ![image-20231202171816123.png](http://xherlock.top/usr/uploads/2023/12/2688204824.png) ## luck_guy https://buuoj.cn/challenges#[GXYCTF2019]luck_guy exeinfo确定为64位程序,找到main的关键函数 ~~~c unsigned __int64 get_flag() { unsigned int v0; // eax int i; // [rsp+4h] [rbp-3Ch] int j; // [rsp+8h] [rbp-38h] __int64 s; // [rsp+10h] [rbp-30h] BYREF char v5; // [rsp+18h] [rbp-28h] unsigned __int64 v6; // [rsp+38h] [rbp-8h] v6 = __readfsqword(0x28u); v0 = time(0LL); // 设置时间种子 srand(v0); for ( i = 0; i <= 4; ++i ) { switch ( rand() % 200 ) // 伪随机数(生成数固定,猜测个顺序) { case 1: // 最后,因为进行了打印变量 puts("OK, it's flag:"); memset(&s, 0, 0x28uLL); strcat((char *)&s, f1); // f1='GXY{do_not_' strcat((char *)&s, &f2); printf("%s", (const char *)&s); break; case 2: // 没用 printf("Solar not like you"); break; case 3: // 没用 printf("Solar want a girlfriend"); break; case 4: // 给f2做了个初始赋值,是第一步 s = 0x7F666F6067756369LL; v5 = 0; strcat(&f2, (const char *)&s); break; case 5: // 对f2的个别位做了修改,应该是第二步 for ( j = 0; j <= 7; ++j ) { if ( j % 2 == 1 ) *(&f2 + j) -= 2; else --*(&f2 + j); } break; default: puts("emmm,you can't find flag 23333"); break; } } return __readfsqword(0x28u) ^ v6; } ~~~ python解密f2:得到后半部分为hate_me},合并即为 GXY{do_not_hate_me} ~~~python s = '7F666F6067756369' f2 = bytes.fromhex(s).decode() f2 = f2[::-1] # 逆转 for i in range(8): if i % 2 == 1: print(chr(ord(f2[i])-2), end="") else: print(chr(ord(f2[i])-1), end="") print() ~~~ ## 刮开有奖1 https://buuoj.cn/challenges#%E5%88%AE%E5%BC%80%E6%9C%89%E5%A5%96 ida64+查找字符串定位核心加密函数如下,需求解String ~~~c INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4) { const char *v4; // esi const char *v5; // edi int v7[2]; // [esp+8h] [ebp-20030h] BYREF // 这里很关键,后面的v8-v16其实都是v7数组 int v8; // [esp+10h] [ebp-20028h] int v9; // [esp+14h] [ebp-20024h] int v10; // [esp+18h] [ebp-20020h] int v11; // [esp+1Ch] [ebp-2001Ch] int v12; // [esp+20h] [ebp-20018h] int v13; // [esp+24h] [ebp-20014h] int v14; // [esp+28h] [ebp-20010h] int v15; // [esp+2Ch] [ebp-2000Ch] int v16; // [esp+30h] [ebp-20008h] CHAR String[65536]; // [esp+34h] [ebp-20004h] BYREF char v18[65536]; // [esp+10034h] [ebp-10004h] BYREF if ( a2 == 272 ) return 1; if ( a2 != 273 ) return 0; if ( (_WORD)a3 == 1001 ) { memset(String, 0, 0xFFFFu); GetDlgItemTextA(hDlg, 1000, String, 0xFFFF); if ( strlen(String) == 8 ) { v7[0] = 90; v7[1] = 74; // v8 = 83; v9 = 69; v10 = 67; v11 = 97; v12 = 78; v13 = 72; v14 = 51; v15 = 110; v16 = 103; sub_4010F0(v7, 0, 10); memset(v18, 0, 0xFFFFu); v18[0] = String[5]; v18[2] = String[7]; v18[1] = String[6]; v4 = (const char *)sub_401000(v18, strlen(v18)); memset(v18, 0, 0xFFFFu); v18[1] = String[3]; v18[0] = String[2]; v18[2] = String[4]; v5 = (const char *)sub_401000(v18, strlen(v18)); if ( String[0] == v7[0] + 34 && String[1] == v10 && 4 * String[2] - 141 == 3 * v8 && String[3] / 4 == 2 * (v13 / 9) && !strcmp(v4, "ak1w") && !strcmp(v5, "V1Ax") ) { MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0); } } return 0; } if ( (_WORD)a3 != 1 && (_WORD)a3 != 2 ) return 0; EndDialog(hDlg, (unsigned __int16)a3); return 1; } ~~~ sub_4010F0函数:a1是数组(长为11)的话,可以看出来函数对a1里的值做了修改,a2=0,a3=10 ~~~c int __cdecl sub_4010F0(int a1, int a2, int a3) { int result; // eax int i; // esi int v5; // ecx int v6; // edx result = a3; // 初始为10 for ( i = a2; i <= a3; a2 = i ) // 从0到10 { v5 = 4 * i; v6 = *(_DWORD *)(4 * i + a1); // 之所以乘四是因为int长4个字节,每次往后取一个数组int值 if ( a2 < result && i < result ) { do { if ( v6 > *(_DWORD *)(a1 + 4 * result) ) { if ( i >= result ) break; ++i; *(_DWORD *)(v5 + a1) = *(_DWORD *)(a1 + 4 * result); if ( i >= result ) break; while ( *(_DWORD *)(a1 + 4 * i) <= v6 ) { if ( ++i >= result ) goto LABEL_13; } if ( i >= result ) break; v5 = 4 * i; *(_DWORD *)(a1 + 4 * result) = *(_DWORD *)(4 * i + a1); } --result; } while ( i < result ); } LABEL_13: *(_DWORD *)(a1 + 4 * result) = v6; sub_4010F0(a1, a2, i - 1); result = a3; ++i; } return result; } ~~~ 还原后的c代码,难点主要是数组的还原,其他都不用改(代码蒟蒻,只能勉强理清逻辑,大概是交换排序算法,反汇编混淆的有点厉害) ~~~c #include<stdio.h> int __cdecl sub_4010F0(int *a1, int a2, int a3) { int result; // eax int i; // esi int v5; // ecx int v6; // edx result = a3; // 初始为10 for ( i = a2; i <= a3; a2 = i ) // 从0到10 { v5 = i; v6 = a1[i]; // 之所以乘四是因为int长4个字节,每次往后取一个数组int值 if ( a2 < result && i < result ) { do { if ( v6 > a1[result] ) { if ( i >= result ) break; ++i; a1[v5] = a1[result]; if ( i >= result ) break; while ( a1[i] <= v6 ) { if ( ++i >= result ) goto LABEL_13; } if ( i >= result ) break; v5 = i; a1[result] = a1[i]; } --result; } while ( i < result ); } LABEL_13: a1[result] = v6; sub_4010F0(a1, a2, i - 1); // 又调用了 result = a3; ++i; } return result; } int main() { int str[] = {90, 74, 83, 69, 67, 97, 78, 72, 51, 110, 103}; sub_4010F0(str, 0, 10); for (int i = 0; i <= 10; i++) { printf("%d ", str[i]); } } ~~~ 得到结果为51 67 69 72 74 78 83 90 97 103 110,可以看出是按照ASCII码大小从小到大排序 sub_401000函数进入后可以看到base64的码表(0-9a-zA-Z这样),因此判断为base64加密只需逆向解密即可 ak1w——jMp,V1Ax——WP1, 求解:String[0] = v7[0] + 34 = U(85) String[1] = J(74) String[2] = (3 * 69 + 141) / 4 = W(87) String[3] = 2 * (90 / 9) * 4 = P(80) String[4] = v18[2] (第二串) = 1 String[5] = v18[0] (第一串) = j String[6] = v18[1] (第一串) = M String[7] = v18[2] (第一串) = p 综上:String=UJWP1jMp ## easy-100(LCTF) https://ctf.bugku.com/challenges/detail/id/121.html jeb反编译查看java核心代码 ~~~java public class MainActivity extends q { private String v; public MainActivity() { super(); } static String a(MainActivity arg1) { return arg1.v; // 调用私有字符串变量 } static boolean a(MainActivity arg1, String arg2, String arg3) { // d.java调用,关键是arg3参数 return arg1.a(arg2, arg3); // 调用私有函数a } private boolean a(String arg4, String arg5) { return new c().a(arg4, arg5).equals(new String(new byte[]{21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59})); // 这里新调用了一个c类的a函数 } protected void onCreate(Bundle arg3) { super.onCreate(arg3); this.setContentView(2130968602); ApplicationInfo v0 = this.getApplicationInfo(); v0.flags &= 2; this.p(); this.findViewById(2131427413).setOnClickListener(new d(this)); // 监听点击提交的函数 } private void p() { // 最为关键 try { InputStream v0_1 = this.getResources().getAssets().open("url.png"); // 获取图像 int v1 = v0_1.available(); byte[] v2 = new byte[v1]; v0_1.read(v2, 0, v1); byte[] v0_2 = new byte[16]; System.arraycopy(v2, 144, v0_2, 0, 16); this.v = new String(v0_2, "utf-8"); } catch(Exception v0) { v0.printStackTrace(); } } } ~~~ d.java如下 ~~~java class d implements View$OnClickListener { d(MainActivity arg1) { this.a = arg1; super(); } public void onClick(View arg5) { if(MainActivity.a(this.a, MainActivity.a(this.a), this.a.findViewById(2131427414).getText().toString())) { // 点击后又使用了MainActivity的a函数,提交的第二个参数是MainActivity.a返回的是一个预先构造好的字符串(在p函数里构造的),第三个参数是输入的字符串 View v0 = this.a.findViewById(2131427412); Toast.makeText(this.a.getApplicationContext(), "Congratulations!", 1).show(); ((TextView)v0).setText(2131099682); } else { Toast.makeText(this.a.getApplicationContext(), "Oh no.", 1).show(); } } } ~~~ c.java如下 ~~~java public class c { public c() { super(); } public String a(String arg5, String arg6) { String v0 = this.a(arg5); // 对v字符串调用私有方法处理 String v1 = ""; a v2 = new a(); // 又调用了a类 v2.a(v0.getBytes()); // 生成密钥 try { v0 = new String(v2.b(arg6.getBytes()), "utf-8"); // 加密输入字符串 } catch(Exception v0_1) { v0_1.printStackTrace(); v0 = v1; } return v0; } private String a(String arg4) { String v0_2; try { arg4.getBytes("utf-8"); StringBuilder v1 = new StringBuilder(); int v0_1; for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) { v1.append(arg4.charAt(v0_1 + 1)); v1.append(arg4.charAt(v0_1)); } v0_2 = v1.toString(); } catch(UnsupportedEncodingException v0) { v0.printStackTrace(); v0_2 = null; } return v0_2; } } ~~~ a.java ~~~java public class a { private SecretKeySpec a; private Cipher b; public a() { super(); } protected void a(byte[] arg4) { if(arg4 != null) { goto label_15; } try { this.a = new SecretKeySpec(MessageDigest.getInstance("MD5").digest("".getBytes("utf-8")), "AES"); this.b = Cipher.getInstance("AES/ECB/PKCS5Padding"); return; label_15: this.a = new SecretKeySpec(arg4, "AES"); // 密钥 this.b = Cipher.getInstance("AES/ECB/PKCS5Padding"); } catch(UnsupportedEncodingException v0) { v0.printStackTrace(); } catch(NoSuchAlgorithmException v0_1) { v0_1.printStackTrace(); } catch(NoSuchPaddingException v0_2) { v0_2.printStackTrace(); } } protected byte[] b(byte[] arg4) { this.b.init(1, this.a); return this.b.doFinal(arg4); } } ~~~ 至此已经知道这是个AES加密,需要分析密钥生成 这道简单题困扰着我一中午,原因: * url.png是assets里的,不是res/drawable里的; * 本来用java文件写代码,发现始终报错读不到文件,恼火,后来才发现,必须要有**抛出**,所以照着原来的代码就可以过的。。。属于java学的不精了 ![image-20231204151409802.png](http://xherlock.top/usr/uploads/2023/12/1329815434.png) 编写java解密(好久没拼这么多的java了,很不熟练,一个个报错) ~~~java import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; public class easy { public static String get_key() { // 获取密钥 try { File file = new File("url.png"); FileInputStream v0_1 = new FileInputStream(file); int v1 = v0_1.available(); byte[] v2 = new byte[v1]; v0_1.read(v2, 0, v1); byte[] v0_2 = new byte[16]; System.arraycopy(v2, 144, v0_2, 0, 16); // 144:144+16的字节串 String v = new String(v0_2, "utf-8"); System.out.println("key: "+v); return v; } catch(Exception v0) { v0.printStackTrace(); return null; } } public static String a(String arg4) { String v0_2; try { arg4.getBytes("utf-8"); StringBuilder v1 = new StringBuilder(); int v0_1; for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) { v1.append(arg4.charAt(v0_1 + 1)); v1.append(arg4.charAt(v0_1)); } v0_2 = v1.toString(); } catch(UnsupportedEncodingException v0) { v0.printStackTrace(); v0_2 = null; } return v0_2; } public static void Decrypt(String v, byte[] c) { // AES解密 SecretKeySpec a; Cipher b; try { a = new SecretKeySpec(v.getBytes(), "AES"); // 密钥 b = Cipher.getInstance("AES/ECB/PKCS5Padding"); b.init(Cipher.DECRYPT_MODE, a); byte[] input = b.doFinal(c); String flag = new String(input, "utf-8"); System.out.println(flag); } catch(Exception v0) { v0.printStackTrace(); } } public static void main(String[] args) { String key = get_key(); key = a(key); Decrypt(key, new byte[]{21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59}); } } ~~~ ![image-20231204155152976.png](http://xherlock.top/usr/uploads/2023/12/889047040.png) ## SafeBox(NJCTF) https://ctf.bugku.com/challenges/detail/id/122.html 这道题绕了个弯,把真实的代码隐藏在了androidTest里而不是MainActivity,得看AndroidManifest(算是长了个教训,卡了一会) ~~~xml <?xml version="1.0" encoding="utf-8"?> <manifest package="com.geekerchina.hi" platformBuildVersionCode="25" platformBuildVersionName="7.1.1" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="25" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:label="@string/app_name" android:name="com.geekerchina.hi.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:label="@string/app_name" android:name="com.geekerchina.hi.androidTest"> <intent-filter> <action android:name="android.intent.action.androidTest" /> </intent-filter> </activity> </application> </manifest> ~~~ 如上有两个activity标签,有一个调用的是"com.geekerchina.hi.androidTest",所以俩都得试试,最后发现是androidTest ~~~java public class androidTest extends AppCompatActivity { public androidTest() { super(); } protected void onCreate(Bundle arg4) { super.onCreate(arg4); this.setContentView(2130968604); this.findViewById(2131427415).setOnClickListener(new View$OnClickListener(this.findViewById(2131427414)) { public void onClick(View arg13) { int v11 = 3; String v6 = "NJCTF{have"; int v4 = Integer.parseInt(this.val$Et1.getText().toString()); if(v4 > 10000000 && v4 < 99999999) { int v7 = 1; int v8 = 10000000; int v3 = 1; if(Math.abs(v4 / 1000 % 100 - 36) == v11 && v4 % 1000 % 584 == 0) { int v5 = 0; while(v5 < v11) { if(v4 / v7 % 10 != v4 / v8 % 10) { v3 = 0; } else { v7 *= 10; v8 /= 10; ++v5; continue; } break; } if(v3 != 1) { return; } this.val$Et1.setText(v6 + (((char)(v4 / 1000000))) + (((char)(v4 / 10000 % 100))) + (((char)(v4 / 100 % 100 + 10))) + "f4n}"); } } } }); } } ~~~ java脚本循环验证即可: ~~~java public class safebox { public static void main(String args[]) { int v11 = 3; String v6 = "NJCTF{have"; for (int v4 = 10000000; v4 < 99999999; v4++) { int v7 = 1; int v8 = 10000000; int v3 = 1; if(Math.abs(v4 / 1000 % 100 - 36) == v11 && v4 % 1000 % 584 == 0) { int v5 = 0; while(v5 < v11) { if(v4 / v7 % 10 != v4 / v8 % 10) { v3 = 0; } else { v7 *= 10; v8 /= 10; ++v5; continue; } break; } if(v3 != 1) { continue; } System.out.println(v6 + (((char)(v4 / 1000000))) + (((char)(v4 / 10000 % 100))) + (((char)(v4 / 100 % 100 + 10))) + "f4n}"); } } } } ~~~ ![image-20231204170955395.png](http://xherlock.top/usr/uploads/2023/12/2516519007.png) ## 特殊的Base64 https://ctf.bugku.com/challenges/detail/id/153.html ida64分析,发现base64加密相关字符串,base64特殊在换了码表,对应切换下即可 ~~~python import base64 table = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+' correct_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' c = 'mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==' new_c = [] for i in range(len(c)): if c[i] != '=': new_c.append(correct_table[table.index(c[i])]) else: new_c.append(c[i]) print(base64.b64decode(''.join(new_c))) ~~~ ## Mountain climbing https://ctf.bugku.com/challenges/detail/id/125.html upx脱壳,ida32查核心代码,还有必须得装VC,试了本机win11和虚拟机winxp,装了dll也没用,没办法因为懒的装VC所以找回了旧电脑(以前上汇编课装了VC) ~~~c int __cdecl main_0(int argc, const char **argv, const char **envp) { char v4; // [esp+0h] [ebp-160h] char v5; // [esp+0h] [ebp-160h] int v6; // [esp+D0h] [ebp-90h] int j; // [esp+DCh] [ebp-84h] int v8; // [esp+DCh] [ebp-84h] int i; // [esp+E8h] [ebp-78h] int v10; // [esp+E8h] [ebp-78h] char Str[104]; // [esp+F4h] [ebp-6Ch] BYREF srand(0xCu); j_memset(&unk_423D80, 0, 0x9C40u); for ( i = 1; i <= 20; ++i ) { for ( j = 1; j <= i; ++j ) dword_41A138[100 * i + j] = rand() % 100000; } sub_41134D("input your key with your operation can get the maximum:", v4); sub_411249("%s", (char)Str); if ( j_strlen(Str) == 19 ) { sub_41114F(Str); v6 = 0; v8 = 1; v10 = 1; *(_DWORD *)dword_423D78 += dword_41A138[101]; while ( v6 < 19 ) { if ( Str[v6] == 76 ) { ++v10; *(_DWORD *)dword_423D78 += dword_41A138[100 * v10 + v8]; } else { if ( Str[v6] != 82 ) goto LABEL_8; ++v10; ++v8; *(_DWORD *)dword_423D78 += dword_41A138[100 * v10 + v8]; } ++v6; } sub_41134D("your operation can get %d points\n", dword_423D78[0]); system("pause"); return 0; } else { LABEL_8: sub_41134D("error\n", v5); system("pause"); return 0; } } ~~~ c代码逆向还原 ~~~c++ #include <bits/stdc++.h> using namespace std; int main() { char v4; // [esp+0h] [ebp-160h] char v5; // [esp+0h] [ebp-160h] int v6; // [esp+D0h] [ebp-90h] int j; // [esp+DCh] [ebp-84h] int v8; // [esp+DCh] [ebp-84h] int i; // [esp+E8h] [ebp-78h] int v10; // [esp+E8h] [ebp-78h] char Str[104]; // [esp+F4h] [ebp-6Ch] BYREF srand(12); int dword_41A138[2100]; for ( i = 1; i <= 20; ++i ) { for ( j = 1; j <= i; ++j ) dword_41A138[100 * i + j] = rand() % 100000; } int dword_423D78 = 0; // sub_41114F(Str); v6 = 0; v8 = 1; v10 = 1; dword_423D78 += dword_41A138[101]; while ( v6 < 19 ) { if ( dword_41A138[100 * (v10+1) + v8] > dword_41A138[100 * (v10+1) + v8 + 1]) { ++v10; dword_423D78 += dword_41A138[100 * v10 + v8]; Str[v6] = 'L'; } else { ++v10; ++v8; dword_423D78 += dword_41A138[100 * v10 + v8]; Str[v6] = 'R'; } ++v6; } Str[19] = '\0'; printf("input: %s\n", Str); printf("your operation can get %d points\n", dword_423D78); system("pause"); return 0; } ~~~ ![image-20231205202558693.png](http://xherlock.top/usr/uploads/2023/12/159832546.png) 上述代码得到的19位序列去跑依然报错,因为有这个sub_41114F对Str做了处理 第一次用ida调试,跟ollydbg比较相似所以用起来还是比较熟练 入口函数下断点:并定位核心代码(不是虚存处理那个函数,第二个) ![image-20231205202523658.png](http://xherlock.top/usr/uploads/2023/12/1143705461.png) push eax之后是call,即传参和调用函数,eax里的值就是我们输入字符的地址 ![image-20231205204856520.png](http://xherlock.top/usr/uploads/2023/12/2582244772.png) 调用结束后发现字符串已变化,4C56即LV ![image-20231205205412784.png](http://xherlock.top/usr/uploads/2023/12/1269351710.png) 分析该函数核心汇编代码 此时ebp存储的输入字符串的位置 ~~~ .text:0041195C mov eax, [ebp-44h] ; 起始为0 .text:0041195F add eax, 1 ; 每个循环+1 .text:00411962 mov [ebp-44h], eax ; 记录次数 .text:00411965 .text:00411965 loc_411965: ; CODE XREF: .text:0041195A↑j .text:00411965 cmp dword ptr [ebp-44h], 13h ; 比较次数和13h(19,正好是输入长度) .text:00411969 jge short sub_411994 ; 目标操作数<源操作数,则SF不等于ZF,不会跳转,如果大于等于则跳转,即下面的不再运行了,相当于for循环i=19了 .text:0041196B mov eax, [ebp-44h] ; 再存储回eax寄存器 .text:0041196E and eax, 80000001h ; 和1与运算,奇数则为1.偶数为0 .text:00411973 jns short loc_41197A ; 与运算结果如果为非负数,则SF=0,这里必跳转 .text:00411975 dec eax ; 由上可知当i=0、2等偶数时eax会置零 .text:00411976 or eax, 0FFFFFFFEh .text:00411979 inc eax .text:0041197A .text:0041197A loc_41197A: ; CODE XREF: .text:00411973↑j .text:0041197A test eax, eax ; 检查eax是否为0,为0则ZF=1 .text:0041197C jz short loc_411992 ; ZF=1则跳转 .text:0041197E mov eax, [ebp+8] ; 取ebp+两个字节的长度值到eax .text:00411981 add eax, [ebp-44h] ; +1 .text:00411984 movsx ecx, byte ptr [eax] ; 取输入的一个字符值到ecx .text:00411987 xor ecx, 4 ; ecx和4异或 .text:0041198A mov edx, [ebp+8] ; .text:0041198D add edx, [ebp-44h] .text:00411990 mov [edx], cl ; 异或完的值拷贝回去 .text:00411992 ; 总结来说i=0、2等偶数时eax=0,会跳过异或操作 .text:00411992 loc_411992: ; CODE XREF: .text:0041197C↑j .text:00411992 jmp short loc_41195C ; 无条件跳回循环 ~~~ 综上分析可得:偶数位的字符会和4异或,奇数位不变(累死了,一个个汇编分析的,因为才学了点ollydbg想尝试下,第一次汇编代码分析),如果视频的话应该可以更清楚地展示思路 修改上面c代码,加入异或操作,运行得到RVRVRHLVRVLVLVRVLVL ~~~c if (v6 % 2 == 1) Str[v6] ^= 4; ~~~ 程序验证:可以看到拿到了分数且满足最高,RVRVRHLVRVLVLVRVLVL即为flag ![image-20231205222317559.png](http://xherlock.top/usr/uploads/2023/12/1961816469.png) ## 小结 这一节学了特别多知识,也了解到了逆向方向: * 前面做了好几道安卓逆向,基本了解安卓架构,和逆向使用的工具、查找方法;大部分都是需要去收集加密算法代码,做一个逆向解密 * 后面接触到了ollydbg,因为卡在某一题上面,wp也看不懂,所以去跟着小甲鱼学习了下解密Ollydbg,B站上的课,讲的很基础很有意思,基本了解了各种快捷键和搜索方式,汇编代码也看了一点,重点关注了根据标志位判断条件句的跳转 * 最后实战做了道对我来说难度相对大些的题目,第一次尝试ida动态分析,相比ollydbg难用些,但也能做,分析了很多汇编代码,加深了各种指令、标志位、跳转等记忆,分析了一整个晚上,收获很大 最后修改:2023 年 12 月 14 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏