哎,差点就能 ak 了

最后的安卓(其实是 linux)题没做出来,其他都磕磕碰碰做出来了,24 小时比赛非常爽啊。做题速度还是太慢了,熟练度没有上来,多打比赛积累经验才行。

题目不算特别难,都是基础(传统)逆向题目,挺适合练习的。

kotlindroid

找到关键函数

/* JADX INFO: Access modifiers changed from: private */
            public static final Unit Button$lambda$7$lambda$6(String text, Context context) {
                Intrinsics.checkNotNullParameter(text, "$text");
                Intrinsics.checkNotNullParameter(context, "$context");
                byte[] key1 = {118, 99, 101, 126, 124, 114, 110, 100};
                byte[] key2 = Config.INSTANCE.getBYTE_ARRAY();
                Collection destination$iv$iv = new ArrayList(key1.length);
                for (byte item$iv$iv : key1) {
                    byte it = (byte) (item$iv$iv ^ 23);
                    destination$iv$iv.add(Byte.valueOf(it));
                }
                byte[] modifiedKey1 = CollectionsKt.toByteArray((List) destination$iv$iv);
                Collection destination$iv$iv2 = new ArrayList(key2.length);
                for (byte item$iv$iv2 : key2) {
                    byte it2 = (byte) (item$iv$iv2 ^ 8);
                    destination$iv$iv2.add(Byte.valueOf(it2));
                }
                byte[] modifiedKey2 = CollectionsKt.toByteArray((List) destination$iv$iv2);
                byte[] key = ArraysKt.plus(modifiedKey1, modifiedKey2);
                check(text, context, key);
                return Unit.INSTANCE;
            }
@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Object invokeSuspend(Object obj) {
        SearchActivityKt$sec$1 searchActivityKt$sec$1;
        Object obj2;
        Object obj3;
        Cipher createCipher;
        byte[] generateIV;
        GCMParameterSpec gCMParameterSpec;
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        SearchActivityKt$sec$1 searchActivityKt$sec$12 = this.label;
        ?? r4 = 0;
        r4 = 0;
        try {
        } catch (Exception e) {
            searchActivityKt$sec$1 = searchActivityKt$sec$12;
            Log.e("sec", "Error occurred", e);
            searchActivityKt$sec$1.label = 2;
            if (BuildersKt.withContext(Dispatchers.getMain(), new AnonymousClass2(searchActivityKt$sec$1.$onResult, r4), searchActivityKt$sec$1) == coroutine_suspended) {
                return coroutine_suspended;
            }
            obj2 = obj3;
        }
        switch (searchActivityKt$sec$12) {
            case 0:
                ResultKt.throwOnFailure(obj);
                SearchActivityKt$sec$1 searchActivityKt$sec$13 = this;
                obj3 = obj;
                createCipher = SearchActivityKt.createCipher();
                generateIV = SearchActivityKt.generateIV();
                gCMParameterSpec = SearchActivityKt.getGCMParameterSpec(generateIV);
                createCipher.init(1, searchActivityKt$sec$13.$secretKey, gCMParameterSpec);
                byte[] bytes = JNI.INSTANCE.getAt().getBytes(Charsets.UTF_8);
                Intrinsics.checkNotNullExpressionValue(bytes, "getBytes(...)");
                createCipher.updateAAD(bytes);
                String str = searchActivityKt$sec$13.$text;
                Charset UTF_8 = StandardCharsets.UTF_8;
                Intrinsics.checkNotNullExpressionValue(UTF_8, "UTF_8");
                byte[] bytes2 = str.getBytes(UTF_8);
                Intrinsics.checkNotNullExpressionValue(bytes2, "getBytes(...)");
                byte[] doFinal = createCipher.doFinal(bytes2);
                Intrinsics.checkNotNull(doFinal);
                String encode$default = Base64.encode$default(Base64.INSTANCE, ArraysKt.plus(generateIV, doFinal), 0, 0, 6, null);
                searchActivityKt$sec$13.label = 1;
                Object withContext = BuildersKt.withContext(Dispatchers.getMain(), new AnonymousClass1(searchActivityKt$sec$13.$onResult, encode$default, null), searchActivityKt$sec$13);
                searchActivityKt$sec$12 = searchActivityKt$sec$13;
                r4 = withContext;
                if (withContext == coroutine_suspended) {
                    return coroutine_suspended;
                }
                return Unit.INSTANCE;

尝试用 frida hook 中间结果:

Java.perform(function () {
    const JNI = Java.use('com.atri.ezcompose.JNI');
    JNI.getAt.implementation = function () {
        const result = this.getAt();
        console.log(`[*] getAt() returned: ${result}`);
        return result;
    };

    let SearchActivityKt = Java.use("com.atri.ezcompose.SearchActivityKt");
    SearchActivityKt["check"].implementation = function (text, context, key) {
        console.log(`SearchActivityKt.check is called: text=${text}, context=${context}, key=${key}`);
        this["check"](text, context, key);
    };
});
[*] getAt() returned: mysecretadd
SearchActivityKt.check is called: text=a123414, context=com.atri.ezcompose.SearchActivity@1f581ea, key=97,116,114,105,107,101,121,115,115,121,101,107,105,114,116,97

mode 是 AES/CGM/nopadding,cipher 是 97,116,114,105,107,101,121,115,115,121,101,107,105,114,116,97

generateIV = SearchActivityKt.generateIV();
                gCMParameterSpec = SearchActivityKt.getGCMParameterSpec(generateIV);
                createCipher.init(1, searchActivityKt$sec$13.$secretKey, gCMParameterSpec);

generateIV 为 UTF8 下的“114514”.tobytes updateAAD(bytes); bytes 为字符串 mysecretadd 转化成 bytes

String encode$default = Base64.encode$default(Base64.INSTANCE, ArraysKt.plus(generateIV, doFinal), 0, 0, 6, null);

最后的结果是

MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==

注意要分成两部分

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
 
public class AESGCMDecrypt {
    public static void main(String[] args) {
        try {
            // Base64 编码的密文
            String encryptedBase64 = "MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==";
 
            // 解析 Base64
            byte[] encryptedData = Base64.getDecoder().decode(encryptedBase64);
 
            // 分离 IV 和实际密文
            byte[] iv = Arrays.copyOfRange(encryptedData, 0, 6);  // 前 6 字节是 IV
            byte[] cipherText = Arrays.copyOfRange(encryptedData, 6, encryptedData.length); // 剩下的是密文
 
            // AES 16 字节密钥(cipher)
            byte[] keyBytes = new byte[]{97, 116, 114, 105, 107, 101, 121, 115, 115, 121, 101, 107, 105, 114, 116, 97};
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
 
            // 初始化 Cipher
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); // GCM Tag 长度 128-bit
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
 
            // 设置 AAD
            byte[] aadData = "mysecretadd".getBytes(StandardCharsets.UTF_8);
            cipher.updateAAD(aadData);
 
            // 解密
            byte[] decryptedBytes = cipher.doFinal(cipherText);
            String decryptedText = new String(decryptedBytes, StandardCharsets.UTF_8);
 
            // 输出解密结果
            System.out.println("解密后的明文: " + decryptedText);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
VNCTF{Y0U_@re_th3_Ma5t3r_0f_C0mp0s3}

钓鱼佬

很神奇,手机和 arm 模拟器上都跑不起来,x64 模拟器倒是可以用

// frida_hook_fish.js
Java.perform(function () {
 
    var TargetClass = Java.use("com.example.hihitt.MainActivity");
 
 
    TargetClass.fish.overload('java.lang.String').implementation = function (fileUrl) {
        console.log("[*] 捕获到 fish() 方法调用");
        console.log("[*] 原始 fileUrl 参数: " + fileUrl);
        // 调用原始方法
        var ret = this.fish(fileUrl);
        console.log("[*] fish() 方法调用结束");
        return ret;
    };
});
[*] 捕获到 fish() 方法调用
[*] 原始 fileUrl 参数: http://47.121.211.23/hook_fish.dex
[*] fish() 方法调用结束

之后算法逆向

import java.util.HashMap;
 
/* loaded from: C:\Users\Cyril\Downloads\hook_fish.dex */
public class hook_fish {
    private HashMap<String, Character> fish_dcode;
    private HashMap<Character, String> fish_ecode;
    private String strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji";
 
    public hook_fish() {
        encode_map();
        decode_map();
    }
 
    public void encode_map() {
        HashMap<Character, String> hashMap = new HashMap<>();
        this.fish_ecode = hashMap;
        hashMap.put('a', "iiijj");
        this.fish_ecode.put('b', "jjjii");
        this.fish_ecode.put('c', "jijij");
        this.fish_ecode.put('d', "jjijj");
        this.fish_ecode.put('e', "jjjjj");
        this.fish_ecode.put('f', "ijjjj");
        this.fish_ecode.put('g', "jjjji");
        this.fish_ecode.put('h', "iijii");
        this.fish_ecode.put('i', "ijiji");
        this.fish_ecode.put('j', "iiiji");
        this.fish_ecode.put('k', "jjjij");
        this.fish_ecode.put('l', "jijji");
        this.fish_ecode.put('m', "ijiij");
        this.fish_ecode.put('n', "iijji");
        this.fish_ecode.put('o', "ijjij");
        this.fish_ecode.put('p', "jiiji");
        this.fish_ecode.put('q', "ijijj");
        this.fish_ecode.put('r', "jijii");
        this.fish_ecode.put('s', "iiiii");
        this.fish_ecode.put('t', "jjiij");
        this.fish_ecode.put('u', "ijjji");
        this.fish_ecode.put('v', "jiiij");
        this.fish_ecode.put('w', "iiiij");
        this.fish_ecode.put('x', "iijij");
        this.fish_ecode.put('y', "jjiji");
        this.fish_ecode.put('z', "jijjj");
        this.fish_ecode.put('1', "iijjl");
        this.fish_ecode.put('2', "iiilj");
        this.fish_ecode.put('3', "iliii");
        this.fish_ecode.put('4', "jiili");
        this.fish_ecode.put('5', "jilji");
        this.fish_ecode.put('6', "iliji");
        this.fish_ecode.put('7', "jjjlj");
        this.fish_ecode.put('8', "ijljj");
        this.fish_ecode.put('9', "iljji");
        this.fish_ecode.put('0', "jjjli");
    }
 
    public void decode_map() {
        HashMap<String, Character> hashMap = new HashMap<>();
        this.fish_dcode = hashMap;
        hashMap.put("iiijj", 'a');
        this.fish_dcode.put("jjjii", 'b');
        this.fish_dcode.put("jijij", 'c');
        this.fish_dcode.put("jjijj", 'd');
        this.fish_dcode.put("jjjjj", 'e');
        this.fish_dcode.put("ijjjj", 'f');
        this.fish_dcode.put("jjjji", 'g');
        this.fish_dcode.put("iijii", 'h');
        this.fish_dcode.put("ijiji", 'i');
        this.fish_dcode.put("iiiji", 'j');
        this.fish_dcode.put("jjjij", 'k');
        this.fish_dcode.put("jijji", 'l');
        this.fish_dcode.put("ijiij", 'm');
        this.fish_dcode.put("iijji", 'n');
        this.fish_dcode.put("ijjij", 'o');
        this.fish_dcode.put("jiiji", 'p');
        this.fish_dcode.put("ijijj", 'q');
        this.fish_dcode.put("jijii", 'r');
        this.fish_dcode.put("iiiii", 's');
        this.fish_dcode.put("jjiij", 't');
        this.fish_dcode.put("ijjji", 'u');
        this.fish_dcode.put("jiiij", 'v');
        this.fish_dcode.put("iiiij", 'w');
        this.fish_dcode.put("iijij", 'x');
        this.fish_dcode.put("jjiji", 'y');
        this.fish_dcode.put("jijjj", 'z');
        this.fish_dcode.put("iijjl", '1');
        this.fish_dcode.put("iiilj", '2');
        this.fish_dcode.put("iliii", '3');
        this.fish_dcode.put("jiili", '4');
        this.fish_dcode.put("jilji", '5');
        this.fish_dcode.put("iliji", '6');
        this.fish_dcode.put("jjjlj", '7');
        this.fish_dcode.put("ijljj", '8');
        this.fish_dcode.put("iljji", '9');
        this.fish_dcode.put("jjjli", '0');
    }
 
    public String encode(String str) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            sb.append(this.fish_ecode.get(Character.valueOf(str.charAt(i))));
        }
        return sb.toString();
    }
 
    public String decode(String str) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        int i2 = 0;
        while (i2 < str.length() / 5) {
            int i3 = i + 5;
            sb.append(this.fish_dcode.get(str.substring(i, i3)));
            i2++;
            i = i3;
        }
        return sb.toString();
    }
 
    public boolean check(String str) {
        if (str.equals(this.strr)) {
            return true;
        }
        return false;
    }
 
    public static void main(String[] strArr) {
        // get decoded fish string
        hook_fish hook_fish = new hook_fish();
        // System.out.println(hook_fish.decode(hook_fish.strr));
        String fish = hook_fish.decode(hook_fish.strr);
        System.out.println(fish);
 
        // decrypt fish string
        String decrypted = decrypt(fish);
        System.out.println(decrypted);
    }
 
 
    public static String decrypt(String encrypted) {
        // 1. 将密文转换为字符数组
        char[] arr = encrypted.toCharArray();
    
        // 2. 逆转加密时对字符进行的转换操作
        for (int i = 0; i < arr.length; i++) {
            // 判断依据:加密时分支1(原字符为a~f)输出的字符值在48~56之间,
            // 而分支2(原字符为0~9)输出的字符值从103开始。
            if (arr[i] < 'g') { // 来自分支1
                arr[i] = (char) (arr[i] - (i % 4) + '1');
            } else { // 来自分支2
                arr[i] = (char) (arr[i] - '7' - (i % 10));
            }
        }
    
        // 3. 逆转 XOR 交换操作(交换相邻字符,本身就是可逆的)
        code(arr, 0);
        // 经过这一步后,arr中存放的就是加密时生成的十六进制字符串
    
        // 4. 将十六进制字符串转换回字节数组
        String hexString = new String(arr);
        int len = hexString.length();
        byte[] bytes = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            String byteHex = hexString.substring(i, i + 2);
            bytes[i / 2] = (byte) Integer.parseInt(byteHex, 16);
        }
    
        // 5. 逆转初始字节转换(每个字节减去68)
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) (bytes[i] - 68);
        }
    
        // 6. 将字节数组转换为明文字符串
        return new String(bytes);
    }
    
    // 与加密中相同的递归交换函数
    private static void code(char[] a, int index) {
        if (index >= a.length - 1) {
            return;
        }
        a[index] = (char) (a[index] ^ a[index + 1]);
        a[index + 1] = (char) (a[index] ^ a[index + 1]);
        a[index] = (char) (a[index] ^ a[index + 1]);
        code(a, index + 2);
    }
    
}
0qksrtuw0x74r2n3s2x3ooi4ps54r173k2os12r32pmqnu73r1h432n301twnq43prruo2h5
VNCTF{u_re4l1y_kn0w_H0Ok_my_f1Sh!1l}

Fuko’s starfish

前面两个回合快速作弊做掉()

第三回合要求输入一个 flag

搜索内存,发现在栈上没用。开调试跟踪

发现这个函数有花指令,IDA 静态没有显示出来,nop 掉前面一大段 eax 和 jnz 就行

有 s 盒,好像多出来一些异或 0x17 的操作

VNCTF{114514114514114514114514}

第一个参数: 4114514114514} 先后面,再前面

16x8=128 个一组,更像 AES 了,就是 AES,这个 S 盒可以用 signsrch 搜出来了(鬼知道我 findcrypt 炸了多久了一直没修)

得找一找密钥在哪里

好像是 byte_18000E1E0, byte_18000E1F0, byte_18000E1F2, byte_18000E200, byte_18000E204, byte_18000E210, byte_18000E950, byte_18000E220, byte_18000E228, byte_18000E230, byte_18000E232, byte_18000E240, byte_18000E244, byte_18000E960, byte_18000E962, byte_18000E970 里面,可能需要异或 17

看我发现了什么,一个伪随机数生成器!

__int64 __fastcall ThreadProc(LPVOID lpThreadParameter)
{
  int v1; // eax
  int v2; // eax
  int v3; // eax
  int v4; // eax
  int v5; // eax
  int v6; // eax
  int v7; // eax
  int v8; // eax
  int v9; // eax
  int v10; // eax
  int v11; // eax
  int v12; // eax
  int v13; // eax
  int v14; // eax
  int v15; // eax
  int v16; // eax
 
  v1 = rand();
  byte_18000E1E0 = v1 + v1 / 255;
  v2 = rand();
  byte_18000E1F0 = v2 + v2 / 255;
  v3 = rand();
  byte_18000E1F2 = v3 + v3 / 255;
  v4 = rand();
  byte_18000E200 = v4 + v4 / 255;
  v5 = rand();
  byte_18000E204 = v5 + v5 / 255;
  v6 = rand();
  byte_18000E210 = v6 + v6 / 255;
  v7 = rand();
  byte_18000E950 = v7 + v7 / 255;
  v8 = rand();
  byte_18000E220 = v8 + v8 / 255;
  v9 = rand();
  byte_18000E228 = v9 + v9 / 255;
  v10 = rand();
  byte_18000E230 = v10 + v10 / 255;
  v11 = rand();
  byte_18000E232 = v11 + v11 / 255;
  v12 = rand();
  byte_18000E240 = v12 + v12 / 255;
  v13 = rand();
  byte_18000E244 = v13 + v13 / 255;
  v14 = rand();
  byte_18000E960 = v14 + v14 / 255;
  v15 = rand();
  byte_18000E962 = v15 + v15 / 255;
  v16 = rand();
  byte_18000E970 = v16 + v16 / 255;
  return 6i64;
}

那就应该是这个了,没得跑了

动调调起来

final = [
  0x3D, 0x01, 0x1C, 0x19, 0x0B, 0xA0, 0x90, 0x81, 0x5F, 0x67, 
  0x27, 0x31, 0xA8, 0x9A, 0xA4, 0x74, 0x97, 0x36, 0x21, 0x67, 
  0xAB, 0x2E, 0xB4, 0xA0, 0x94, 0x18, 0xD3, 0x7D, 0x93, 0xE6, 
  0x46, 0xE7]
 
s_box = [
	[0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76],
	[0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0],
	[0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15],
	[0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75],
	[0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84],
	[0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf],
	[0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8],
	[0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2],
	[0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73],
	[0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb],
	[0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79],
	[0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08],
	[0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a],
	[0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e],
	[0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf],
	[0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]
]
 
s_box_inv = [
	[0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb],
	[0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb],
	[0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e],
	[0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25],
	[0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92],
	[0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84],
	[0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06],
	[0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b],
	[0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73],
	[0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e],
	[0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b],
	[0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4],
	[0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f],
	[0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef],
	[0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61],
	[0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]
]
 
rc = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d]
 
def sub_bytes(grid, inv=False):
	for i, v in enumerate(grid):
		if inv:  # for decryption
			grid[i] = s_box_inv[v >> 4][v & 0xf]
		else:
			grid[i] = s_box[v >> 4][v & 0xf]
 
def shift_rows(grid, inv=False):
	for i in range(4):
		if inv:  # for decryption
			grid[i::4] = grid[i::4][-i:] + grid[i::4][:-i]
		else:
			grid[i::4] = grid[i::4][i:] + grid[i::4][:i]
 
def mix_columns(grid):
	def mul_by_2(n):
		s = (n << 1) & 0xff
		if n & 128:
			s ^= 0x1b
		return s
 
	def mul_by_3(n):
		return n ^ mul_by_2(n)
 
	def mix_column(c):
		return [
			mul_by_2(c[0]) ^ mul_by_3(c[1]) ^ c[2] ^ c[3],  # [2 3 1 1]
			c[0] ^ mul_by_2(c[1]) ^ mul_by_3(c[2]) ^ c[3],  # [1 2 3 1]
			c[0] ^ c[1] ^ mul_by_2(c[2]) ^ mul_by_3(c[3]),  # [1 1 2 3]
			mul_by_3(c[0]) ^ c[1] ^ c[2] ^ mul_by_2(c[3]),  # [3 1 1 2]
		]
 
	for i in range(0, 16, 4):
		grid[i:i + 4] = mix_column(grid[i:i + 4])
 
def key_expansion(grid):
	for i in range(10 * 4):
		r = grid[-4:]
		if i % 4 == 0:  # 对上一轮最后4字节自循环、S-box置换、轮常数异或,从而计算出当前新一轮最前4字节
			for j, v in enumerate(r[1:] + r[:1]):
				r[j] = s_box[v >> 4][v & 0xf] ^ (rc[i // 4] if j == 0 else 0)
 
		for j in range(4):
			grid.append(grid[-16] ^ r[j])
 
	return grid
 
def add_round_key(grid, round_key):
	for i in range(16):
		grid[i] ^= round_key[i]
 
def encrypt(b, expanded_key):
	# First round
	add_round_key(b, expanded_key)
 
	for i in range(1, 10):
		sub_bytes(b)
		shift_rows(b)
		mix_columns(b)
		add_round_key(b, expanded_key[i * 16:])
 
	# Final round
	sub_bytes(b)
	shift_rows(b)
	add_round_key(b, expanded_key[-16:])
	return b
 
def decrypt(b, expanded_key):
	# First round
	add_round_key(b, expanded_key[-16:])
 
	for i in range(9, 0, -1):
		shift_rows(b, True)
		sub_bytes(b, True)
		add_round_key(b, expanded_key[i * 16:])
		for _ in range(3): mix_columns(b)
 
	# Final round
	shift_rows(b, True)
	sub_bytes(b, True)
	add_round_key(b, expanded_key)
	return b
 
def aes(typ, key, msg):
	expanded = key_expansion(bytearray(key))
 
	# Pad the message to a multiple of 16 bytes
	b = bytearray(msg)
	if typ == 0:  # only for encryption
		b = bytearray(msg + b'\x00' * (16 - len(msg) % 16))
 
	# Encrypt/decrypt the message
	for i in range(0, len(b), 16):
		if typ == 0:
			b[i:i + 16] = encrypt(b[i:i + 16], expanded)
		else:
			b[i:i + 16] = decrypt(b[i:i + 16], expanded)
	return bytes(b)
 
 
key = [0x1e,0xf2,0xea,0xfc,0x7f,0x26,0x62,0xa1,0xa6,0x2c,0x93,0x1f,0x86,0xfc,0x6f,0xc5]
key = [i^0x17 for i in key]
flag = aes(1, key, final)
print(flag)
 
VNCTF{W0w_u_g0t_Fuk0's_st4rf1sh}

抽奖转盘

hap 逆向

找到一个反编译器看看 https://github.com/ohos-decompiler/abc-decompiler

根据网上的资料,先找 page 看看

abcde 逆向工具包

Index 中找到关键调用函数(lib 关键字)

public Object #~@0>@1*^d*#(Object functionObject, Object newTarget, Index this) {
        libHello = import { default as libHello } from "@normalized:Y&&&libhello.so&";
        MyCry = libHello.MyCry(_lexenv_0_1_.numX, 24, _lexenv_0_1_.pw);
        router = import { default as router } from "@ohos:router";
        obj = router.pushUrl;
        obj2 = createobjectwithbuffer(["url", "pages/MyPage", "params", 0]);
        obj3 = createobjectwithbuffer(["result", 0]);
        obj3.result = MyCry;
        obj2.params = obj3;
        obj(obj2);
        return null;
    }
 

然后看 so 层,MyCry 函数有注册,摸到一个看起来就是人写的函数

__int64 __fastcall mainn(__int64 a1, __int64 a2)
{
  __int64 v2; // rax
  size_t v4; // [rsp+28h] [rbp-178h]
  double v5; // [rsp+40h] [rbp-160h]
  __int64 v6; // [rsp+88h] [rbp-118h]
  __int64 v7; // [rsp+90h] [rbp-110h] BYREF
  __int64 v8[3]; // [rsp+98h] [rbp-108h] BYREF
  char v9; // [rsp+B7h] [rbp-E9h] BYREF
  __int64 v10; // [rsp+B8h] [rbp-E8h] BYREF
  __int64 v11; // [rsp+C0h] [rbp-E0h] BYREF
  _QWORD v12[3]; // [rsp+C8h] [rbp-D8h] BYREF
  char v13[24]; // [rsp+E0h] [rbp-C0h] BYREF
  double y; // [rsp+F8h] [rbp-A8h] BYREF
  double x; // [rsp+100h] [rbp-A0h] BYREF
  __int64 v16; // [rsp+108h] [rbp-98h] BYREF
  char dest[112]; // [rsp+110h] [rbp-90h] BYREF
  __int64 s[4]; // [rsp+180h] [rbp-20h] BYREF
 
  s[3] = __readfsqword(0x28u);
  if ( a1 && a2 )
  {
    v16 = 3LL;
    memset(s, 0, 0x18uLL);
    if ( (unsigned int)napi_get_cb_info(a1, a2, &v16, s, 0LL) )
    {
      OH_LOG_Print(0LL, 6LL, 65280LL, "MyCry", "api_get_cb_info failed");
      return 0LL;
    }
    else
    {
      x = 0.0;
      y = 0.0;
      if ( (unsigned int)napi_get_value_double(a1, s[0], &x) || (unsigned int)napi_get_value_double(a1, s[1], &y) )
      {
        OH_LOG_Print(0LL, 6LL, 65280LL, "MyCry", "napi_get_value failed");
        return 0LL;
      }
      else
      {
        get_value_string_utf8((__int64)v13, a1, s[2]);
        v2 = sub_288E0(v13);
        OH_LOG_Print(0LL, 6LL, 65280LL, "MyCry", "ts_putString str = %{public}s", v2);
        init_string(v12);
        v11 = sub_28990((__int64)v13);
        v10 = sub_28A00(v13);
        while ( (sub_28A80(&v11, &v10) & 1) != 0 )// 全部+3
        {
          v9 = *(_BYTE *)sub_28AB0(&v11) + 3;
          sub_28AD0((__int64)v12, (__int64)&v9);
          sub_28B30(&v11);
        }
        v5 = _mm_cvtepi32_pd(_mm_cvttpd_epi32((__m128d)COERCE_UNSIGNED_INT64(hypot(x, y)))).m128d_f64[0]; // sqrt(x*x+y*y)==40
        std::__n1::basic_string<char,std::__n1::char_traits<char>,std::__n1::allocator<char>>::basic_string[abi:v15004]<decltype(nullptr)>(
          v8,
          "Take_it_easy");
        OH_LOG_Print(0LL, 6LL, 65280LL, "MyCry", "Result: %{public}f", v5);
        if ( v5 == 40.0 )
          RC4_0(v12, (__int64)v8, (int)v5);
        else
          RC4_1(v12, (__int64)v8);
        memset(dest, 0, 0x64uLL);
        if ( (unsigned __int64)len_Perhaps(v12) < 0x5A )
          base64_encode((__int64)v12, dest);
        else
          strcpy(dest, "oh!you_are_toooooo_long!!!!!!");
        v4 = strlen(dest);
        if ( (unsigned int)napi_create_string_utf8(a1, dest, v4, &v7) )
        {
          OH_LOG_Print(0LL, 6LL, 65280LL, "MyCry", "napi_create_double failed");
          v6 = 0LL;
        }
        else
        {
          v6 = v7;
        }
        std::__n1::basic_string<char,std::__n1::char_traits<char>,std::__n1::allocator<char>>::~basic_string(v8);
        sub_28BD0(v12);
        std::__n1::basic_string<char,std::__n1::char_traits<char>,std::__n1::allocator<char>>::~basic_string(v13);
      }
    }
  }
  else
  {
    OH_LOG_Print(0LL, 6LL, 65280LL, "MyCry", "env or exports is null");
    return 0LL;
  }
  return v6;
}

base64 是标准的,rc4 也是标准的,额外的第三个参数表示最后全部异或上一个值,这里就是 40,RC4 的 key 是 Take_it_easy,加密前需要加上 3,最后 base64。但这个很明显不是 base64 的结果,仔细查看比较的 buf 附近

#~@0>@4*# 这些都是 lamda,foreach 后比较

/* JADX WARN: Type inference failed for: r13v38, types: [int] */
public Object #~@0>#startAnimator(Object functionObject, Object newTarget, MyPage this) {
	newlexenvwithname([6, "array", 0, "ay", 1, "tempangle", 2, "randomAngle", 3, "4newTarget", 4, "this", 5], 6);
	_lexenv_0_4_ = newTarget;
	_lexenv_0_5_ = this;
	_lexenv_0_3_ = null;
	_lexenv_0_2_ = null;
	buffer = import { default as buffer } from "@ohos:buffer";
	_lexenv_0_0_ = Uint8Array(buffer.from(_lexenv_0_5_.result).buffer);
	_lexenv_0_1_ = createarraywithbuffer([101, 74, 76, 49, 101, 76, 117, 87, 55, 69, 118, 68, 118, 69, 55, 67, 61, 83, 62, 111, 81, 77, 115, 101, 53, 73, 83, 66, 68, 114, 109, 108, 75, 66, 97, 117, 93, 127, 115, 124, 109, 82, 93, 115]);
	ldlexvar = _lexenv_0_0_;
	ldlexvar.forEach(#~@0>@4*#);
	ldlexvar2 = _lexenv_0_0_;
	ldlexvar2.forEach(#~@0>@4*#^1);
	round = Math.round(104 - _lexenv_0_2_);
	_lexenv_0_3_;
	_lexenv_0_3_ = round;
	console.log("当前角度", _lexenv_0_3_);
	ldlexvar3 = _lexenv_0_5_;
	obj = _lexenv_0_5_.drawModel;
	ldlexvar3.prizeData = obj.showPrizeData(_lexenv_0_3_);
	obj2 = Context.animateTo;
	obj3 = createobjectwithbuffer(["duration", 0, "curve", 0, "delay", 0, "iterations", 1, "playMode", 0, "onFinish", 0]);
	obj3.duration = import { default as CommonConstants } from "@normalized:N&&&entry/src/main/common/constants/CommonConstants&".DURATION;
	obj3.curve = Curve.Ease;
	obj3.playMode = PlayMode.Normal;
	obj3.onFinish = #~@0>@4*#onFinish;
	obj2(obj3, #~@0>@4*#^2);
	return null;
}
 
 
/* JADX WARN: Multi-variable type inference failed */
public Object #~@0>@4*#(Object functionObject, Object newTarget, MyPage this, Object arg0, Object arg1) {
	_lexenv_0_0_[arg1] = (arg0 + 1) ^ 7;
	return null;
}
    
public Object #~@0>@4*#^1(Object functionObject, Object newTarget, MyPage this, Object arg0, Object arg1) {
	if ((_lexenv_0_1_[arg1] == _lexenv_0_0_[arg1] ? 1 : 0) == 0) {
		return null;
	}
	r14 = tonumer(_lexenv_0_2_) + 1;
	_lexenv_0_2_;
	_lexenv_0_2_ = r14;
	return null;
}
    
public Object #~@0>@4*#^2(Object functionObject, Object newTarget, MyPage this) {
	_lexenv_0_5_.rotateDegree = ((import { default as CommonConstants } from "@normalized:N&&&entry/src/main/common/constants/CommonConstants&".CIRCLE * import { default as CommonConstants } from "@normalized:N&&&entry/src/main/common/constants/CommonConstants&".FIVE) + import { default as CommonConstants } from "@normalized:N&&&entry/src/main/common/constants/CommonConstants&".ANGLE) - _lexenv_0_3_;
	return null;
}
 

感觉大概就是越接近转盘转的越靠近大奖吧,x 和 y 盲猜是手机的方向?

y 固定 24,那么 x 就是 32 了(好像没什么用)。pw 是输入的内容。

python 脚本写不对?还得是赛博大厨

#recipe=From_Decimal('Space',false)XOR(%7B'option':'Hex','string':'7'%7D,'Standard',false)ADD(%7B'option':'Decimal','string':'-1'%7D)From_Base64('A-Za-z0-9%2B/%3D',true,false)XOR(%7B'option':'Decimal','string':'40'%7D,'Standard',false)RC4(%7B'option':'UTF8','string':'Take_it_easy'%7D,'Latin1','Latin1')ADD(%7B'option':'Decimal','string':'-3'%7D)&input=MTAxLCA3NCwgNzYsIDQ5LCAxMDEsIDc2LCAxMTcsIDg3LCA1NSwgNjksIDExOCwgNjgsIDExOCwgNjksIDU1LCA2NywgNjEsIDgzLCA2MiwgMTExLCA4MSwgNzcsIDExNSwgMTAxLCA1MywgNzMsIDgzLCA2NiwgNjgsIDExNCwgMTA5LCAxMDgsIDc1LCA2NiwgOTcsIDExNywgOTMsIDEyNywgMTE1LCAxMjQsIDEwOSwgODIsIDkzLCAxMTUNCg0K&ieol=CRLF&oeol=VT

VNCTF{JUst_$ne_Iast_dance_2025!}

AndroidLux

好多魔法少女牙

目的是构造给 client 的输入使得 response 为 ok

第一次请求:调用到 public void m1699x1fc1b698(String data)

通过 “mahoshojo” 通信

emu64a:/ # netstat -x | grep mahoshojo
unix  3      [ ]         STREAM     CONNECTED     295657   @mahoshojo
|emu64a:/ # netstat -anp | grep unix
unix  3      [ ]         STREAM     CONNECTED     295657   29061/env            @mahoshojo
unix  2      [ ACC ]     STREAM     LISTENING     295656   29061/env            @mahoshojo
emu64a:/ # cat /proc/29061/cmdline           
./env
emu64a:/ # ls -l /proc/29061/fd
total 0
lr-x------ 1 u0_a146 u0_a146 64 2025-02-09 01:15 0 -> pipe:[295531]
l-wx------ 1 u0_a146 u0_a146 64 2025-02-09 01:15 1 -> pipe:[295532]
l-wx------ 1 u0_a146 u0_a146 64 2025-02-09 01:15 2 -> pipe:[295532]
lrwx------ 1 u0_a146 u0_a146 64 2025-02-09 01:15 3 -> socket:[295656]
lrwx------ 1 u0_a146 u0_a146 64 2025-02-09 01:15 4 -> socket:[295657]
emu64a:/ # ls -l /proc/29061/exe
lrwxrwxrwx 1 u0_a146 u0_a146 0 2025-02-09 01:21 /proc/29061/exe -> /data/user/0/work.pangbai.androidlux/files/tmp/prooted-29057-RrelgH

通过 proot 与 busybox 通信?

emu64a:/data/user/0/work.pangbai.androidlux/files # ls
basename  busybox  etc               lib    opt               root  sh   tmp
bin       dev      gnu_linux_loader  media  proc              run   srv  usr
boot      env      home              mnt    profileInstalled  sbin  sys  var

PRoot 是 chrootmount --bind 和 binfmt_misc 的用户态实现。用户不需要拥有系统特权就可以在任意目录建立一个新的根文件系统。从而在建立的根文件系统内做任何事情。也可以借助 QEMU user-mode 甚至能够运行其他 CPU 构架的程序。

从技术上来说,PRoot 是依靠 ptrace 机制实现的。ptrace 允许程序在没有拿到系统特权(root)时,父进程观察并修改子进程的系统调用,这也是 PRoot 的核心原理。本文主要也是分析这一机制。

找到 assets 中的 env 解压

tar -xJf env

然后在 root 中发现了 env(root 的 home 吗)

ida 打开,爆红了,用 bn 打开正常

这个 base64 绝对有问题,算法上是不对的

#include <stdint.h>
#include <string.h>
#include <stdio.h>
 
// 验证字母表是否合法(需要严格按64字符顺序排列)
const char base64Alphabet[64] = "TUVWXYZabcdefghijABCDEF456789GHIJKLMNOPQRSklmnopqrstuvwxyz0123+/";
 
void decode_custom(const uint8_t* encoded, int32_t encoded_len, uint8_t* decoded, int32_t* decoded_len) {
    // 构建反向映射表(字符 -> 6位值)
    uint8_t reverse_table[256];
    memset(reverse_table, 0xFF, sizeof(reverse_table));
    for (int i = 0; i < 64; i++) {
        reverse_table[(uint8_t)base64Alphabet[i]] = i;
    }
    reverse_table['='] = 0; // 处理填充字符
 
    // // 检查输入合法性
    // if (encoded_len % 4 != 0) {
    //     *decoded_len = -1;
    //     return;
    // }
 
    int32_t output_idx = 0;
    int32_t padding = 0;
    for (int32_t i = 0; i < encoded_len; i += 4) {
        // 处理尾部填充
        padding = 0;
        if (encoded[i+2] == '=') padding = 2;
        else if (encoded[i+3] == '=') padding = 1;
 
        // 获取6位值(忽略填充字符)
        uint8_t v0 = reverse_table[encoded[i]];
        uint8_t v1 = reverse_table[encoded[i+1]];
        uint8_t v2 = (padding >= 2) ? 0 : reverse_table[encoded[i+2]];
        uint8_t v3 = (padding >= 1) ? 0 : reverse_table[encoded[i+3]];
 
        // 检查无效字符
        if (v0 == 0xFF || v1 == 0xFF || v2 == 0xFF || v3 == 0xFF) {
            *decoded_len = -1;
            printf("Invalid character\n");
            return;
        }
 
        // 解码为3字节
        decoded[output_idx++] = (v0 << 2) | ((v1) & 0x03);
        decoded[output_idx++] = ((v1 & 0x3c) << 2) | ((v2) & 0x0F);
        decoded[output_idx++] = ((v2 & 0xf0) << 2) | v3;
    }
 
    // 根据填充调整输出长度
    *decoded_len = output_idx - padding;
}
 
uint8_t mm[0x35] = "RPVIRN40R9PU67ue6RUH88Rgs65Bp8td8VQm4SPAT8Kj97QgVG==";
 
 
int main() {
    uint8_t decoded[0x100];
    int32_t decoded_len;
    decode_custom(mm, 0x34, decoded, &decoded_len);
    printf("%s\n", decoded);
    // 输出为hex
    for (int i = 0; i < decoded_len; i++) {
        printf("\\x""%02x", decoded[i]);
    }
    return 0;
}
����z�v�fd�d�oh��hR�c�n�]��a�rg�        p
\xa2\x92\x1f\xa0\x97\x7a\xa0\x76\x81\x66\x64\xcb\x64\xa1\x1e\x6f\x68\x8d\xc9\x68\x52\xbf\x63\xca\x6e\x07\xac\x5d\xa6\x91\x03\x61\x90\x72\x67\x8d\x09 

已经验证了这个结果对于 env 来说是对了,但是是乱码。中间应该有什么东西转发了,然后给改了。linker 是自己写的。

提示: libc.so.6 没有改动

不懂怎么用这个提示,是要我 hook socket 吗?

注意到链接 socket 的时候用的是 #mahoshojo 这里有一个 sharp,这个可能是区别?

我试一下直接调用 proot 行不行?


最后还是没有做出来,问出题人才知道这个 ld 配置被修改过了,所以 read 被魔改了(原来提示暗示要往动态库想,我只考虑了其他线程的想法)

通过解压后对文件夹排序可以注意到 /etc 目录下的 ld.so.preload 比其他文件创建时间都要新,或者掌握这个:linux动态链接库查找路径

然后就能找到目录下 /usr/libexec/libexec.so 文件了,打开可以发现 strncmp read 函数都是自己写的,其他则都是导入标准库

uint64_t strncmp(void* arg1, char* arg2, int64_t arg3)
{
    int64_t x0 = dlsym(-1, "strncmp");
    int32_t var_4 = 0;
    void var_118;
    
    while (((int64_t)var_4) < strlen(arg2))
    {
        if ((((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) > 0x40 && ((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) <= 0x4d))
            *(uint8_t*)(&var_118 + ((int64_t)var_4)) = (*(uint8_t*)((char*)arg1 + ((int64_t)var_4)) + 0xd);
        else if ((((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) > 0x4d && ((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) <= 0x5a))
            *(uint8_t*)(&var_118 + ((int64_t)var_4)) = (*(uint8_t*)((char*)arg1 + ((int64_t)var_4)) - 0xd);
        else if ((((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) > 0x60 && ((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) <= 0x6d))
            *(uint8_t*)(&var_118 + ((int64_t)var_4)) = (*(uint8_t*)((char*)arg1 + ((int64_t)var_4)) + 0xd);
        else if ((((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) <= 0x6d || ((uint32_t)*(uint8_t*)((char*)arg1 + ((int64_t)var_4))) > 0x7a))
            *(uint8_t*)(&var_118 + ((int64_t)var_4)) = *(uint8_t*)((char*)arg1 + ((int64_t)var_4));
        else
            *(uint8_t*)(&var_118 + ((int64_t)var_4)) = (*(uint8_t*)((char*)arg1 + ((int64_t)var_4)) - 0xd);
        
        var_4 += 1;
    }
    
    return ((uint64_t)x0(&var_118, arg2, arg3));
}
int64_t read(int32_t arg1, void* arg2, int64_t arg3)
{
    int64_t i = dlsym(-1, "read")(((uint64_t)arg1), arg2, arg3);
    int32_t var_4 = 0;
    
    while (i > ((int64_t)var_4))
    {
        *(uint8_t*)((char*)arg2 + ((int64_t)var_4)) ^= 1;
        var_4 += 1;
    }
    
    return i;
}

read 的时候就先一个异或,最后比较的部分对不同大小的做替换,再写脚本逆回去就行了

#include <stdint.h>
#include <stdio.h>
#include <string.h>
 
const char base64Alphabet[65] =
    "TUVWXYZabcdefghijABCDEF456789GHIJKLMNOPQRSklmnopqrstuvwxyz0123+/";
 
void decode_custom(const uint8_t *encoded, int32_t encoded_len,
                   uint8_t *decoded, int32_t *decoded_len) {
    // 构建反向映射表(字符 -> 6位值)
    uint8_t reverse_table[256];
    memset(reverse_table, 0xFF, sizeof(reverse_table));
    for (int i = 0; i < 64; i++) {
        reverse_table[(uint8_t)base64Alphabet[i]] = i;
    }
    reverse_table['='] = 0;
 
    int32_t output_idx = 0;
    int32_t padding = 0;
    for (int32_t i = 0; i < encoded_len; i += 4) {
        // 处理尾部填充
        padding = 0;
        if (encoded[i + 2] == '=')
            padding = 2;
        else if (encoded[i + 3] == '=')
            padding = 1;
 
        // 获取6位值(忽略填充字符)
        uint8_t v0 = reverse_table[encoded[i]];
        uint8_t v1 = reverse_table[encoded[i + 1]];
        uint8_t v2 = (padding >= 2) ? 0 : reverse_table[encoded[i + 2]];
        uint8_t v3 = (padding >= 1) ? 0 : reverse_table[encoded[i + 3]];
 
        // 解码为3字节
        decoded[output_idx++] = (v0 << 2) | ((v1) & 0x03);
        decoded[output_idx++] = ((v1 & 0x3c) << 2) | ((v2) & 0x0F);
        decoded[output_idx++] = ((v2 & 0xf0) << 2) | v3;
    }
 
    // 根据填充调整输出长度
    *decoded_len = output_idx - padding;
}
 
char mm[0x35] = "RPVIRN40R9PU67ue6RUH88Rgs65Bp8td8VQm4SPAT8Kj97QgVG==";
 
int main() {
    for (int i = 1; i < 0x35; i++) {
        if (mm[i] > 0x40 and mm[i] <= 0x4d) {
            mm[i] += 0xd;
            continue;
        }
        if (mm[i] > 0x4d and mm[i] <= 0x5a) {
            mm[i] -= 0xd;
            continue;
        }
        if (mm[i] > 0x60 and mm[i] <= 0x6d) {
            mm[i] += 0xd;
            continue;
        }
        if (mm[i] > 0x6d and mm[i] <= 0x7a) {
            mm[i] -= 0xd;
            continue;
        }
    }
 
    uint8_t decoded[0x100];
    int32_t decoded_len;
    decode_custom((uint8_t *)mm, 0x34, decoded, &decoded_len);
 
    for (int i = 0; i < decoded_len; i++) {
        decoded[i] ^= 0x1;
    }
 
    printf("%s\n", decoded);
    return 0;
}
VNCTF{Ur_go0d_@ndr0id&l1nux_Reve7ser}

唉,想到的话几分钟的事。这脑子没转过来没办法 x