Reverse(七)

Java逆向解密

https://buuoj.cn/challenges#Java%E9%80%86%E5%90%91%E8%A7%A3%E5%AF%86

jd-gui逆向class文件为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

luck_guy

https://buuoj.cn/challenges#[GXYCTF2019]luck_guy

exeinfo确定为64位程序,找到main的关键函数

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}

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

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

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代码,难点主要是数组的还原,其他都不用改(代码蒟蒻,只能勉强理清逻辑,大概是交换排序算法,反汇编混淆的有点厉害)

#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核心代码

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如下

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如下

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

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

编写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

SafeBox(NJCTF)

https://ctf.bugku.com/challenges/detail/id/122.html

这道题绕了个弯,把真实的代码隐藏在了androidTest里而不是MainActivity,得看AndroidManifest(算是长了个教训,卡了一会)

<?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

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脚本循环验证即可:

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

特殊的Base64

https://ctf.bugku.com/challenges/detail/id/153.html

ida64分析,发现base64加密相关字符串,base64特殊在换了码表,对应切换下即可

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)

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代码逆向还原

#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

上述代码得到的19位序列去跑依然报错,因为有这个sub_41114F对Str做了处理

第一次用ida调试,跟ollydbg比较相似所以用起来还是比较熟练

入口函数下断点:并定位核心代码(不是虚存处理那个函数,第二个)

image-20231205202523658.png

push eax之后是call,即传参和调用函数,eax里的值就是我们输入字符的地址

image-20231205204856520.png

调用结束后发现字符串已变化,4C56即LV

image-20231205205412784.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

if (v6 % 2 == 1)
    Str[v6] ^= 4;

程序验证:可以看到拿到了分数且满足最高,RVRVRHLVRVLVLVRVLVL即为flag

image-20231205222317559.png

小结

这一节学了特别多知识,也了解到了逆向方向:

  • 前面做了好几道安卓逆向,基本了解安卓架构,和逆向使用的工具、查找方法;大部分都是需要去收集加密算法代码,做一个逆向解密
  • 后面接触到了ollydbg,因为卡在某一题上面,wp也看不懂,所以去跟着小甲鱼学习了下解密Ollydbg,B站上的课,讲的很基础很有意思,基本了解了各种快捷键和搜索方式,汇编代码也看了一点,重点关注了根据标志位判断条件句的跳转
  • 最后实战做了道对我来说难度相对大些的题目,第一次尝试ida动态分析,相比ollydbg难用些,但也能做,分析了很多汇编代码,加深了各种指令、标志位、跳转等记忆,分析了一整个晚上,收获很大
最后修改:2023 年 12 月 14 日
如果觉得我的文章对你有用,请随意赞赏