LOADING

加载过慢请开启缓存 浏览器默认开启

2025VNCTF_Re Writeup

hook_fish

运行应用提示输入一串字符并要求联网。

jadx分析,发现在main中,在点击按钮后会调用encrypt()方法对我们输入的数据加密,然后调用fish()方法下载了一个“hook_fish.dex”文件。通过下面的registerReceiver方法创注册了一个广播接收器,只要收到系统下载完成的广播时,就会调用onReceive()方法。

registerReceiver(this.downloadCompleteReceiver, new IntentFilter("android.intent.action.DOWNLOAD_COMPLETE"));

在onReceive()方法中把加密后的的输入传入了MainActivity.this.loadClass();,加载下载来的.dex文件并反射调用了encode(input0);对输入再次加密,最后调用check()方法检查结果。然后删除本地的“hook_fish.dex”文件。

我们先hook file.delete()方法,在删除前转移出dex文件(脚本1);

在savedDexFile.dex中我们可以发现是一个自定义的加密方法,并且还附带了解密函数,和密文,我们直接拿下来用,用java写解题脚本就行(脚本2)。(在官方的脚本中通过hook调用decode方法先解密密文,再逆向写出decrypt解密)。

脚本1

Java.perform(function(){ 
//     var File = Java.use('java.io.File');
// File.delete.implementation = function() {
//     var filePath = this.getAbsolutePath();  // 获取文件路径
//     console.log("文件将在删除之前被保存: " + filePath);

//     // 在删除之前复制文件到其他位置
//     var newFile = Java.use('java.io.File');
//     var destPath = "/data/data/com.example.hihitt/files/savedDexFile.dex";  // 新文件路径
//     var sourceFile = this;
//     var inputStream = Java.use('java.io.FileInputStream').$new(sourceFile);
//     var outputStream = Java.use('java.io.FileOutputStream').$new(destPath);
    
//     var buffer = Java.array('byte', [1024]);  // 缓冲区
//     var bytesRead;
//     while ((bytesRead = inputStream.read(buffer)) !== -1) {
//         outputStream.write(buffer, 0, bytesRead);
//     }
//     inputStream.close();
//     outputStream.close();
//     console.log("文件已保存到: " + destPath);

//     // 执行原本的删除操作
//     return this.delete();
// };

let MainActivity = Java.use("com.example.hihitt.MainActivity");
MainActivity["code"].implementation = function (a, index) {
    console.log(`MainActivity.code is called: a=${a}, index=${index}`);
    this["code"](a, index);
};

MainActivity["encrypt"].implementation = function (str) {
    console.log(`MainActivity.encrypt is called: str=${str}`);
    let result = this["encrypt"](str);
    console.log(`MainActivity.encrypt result=${result}`);
    return result;
};
})

脚本2(不太会处理java的数据,最后用python来进行最后的异或)

import java.util.HashMap;

public class Main {
    public static void main(String[] args) {

        String strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji";
        hookfish hf = new hookfish();
        String result = hf.decode(strr);

        // 输出解码后的结果
        System.out.println("解码结果: " + result);
        String decryptedString = decrypt(result);
        System.out.println("解密后的字符串: " + decryptedString);
    }
    // 解密方法
    public static String decrypt(String str) {
        // 将加密后的字符串转回字符数组
        char[] str3 = str.toCharArray();

        // 逆操作字符数组变换
        for (int i = 0; i < str3.length; i++) {
            if ((char) ((str3[i] - (i % 4)) + '1') >= 'a' && (char) ((str3[i] - (i % 4)) + '1')<= 'f') {
                str3[i] = (char) ((str3[i] - (i % 4)) + '1');
            } else {
                str3[i] = (char) (str3[i] - ('7' + (i % 10)));
            }
        }
        // 恢复异或操作
        code(str3, 0);

        // 将字符数组转为十六进制字符串
        String str2= new String(str3);

        // 将字节数组转换为原始字符串并返回
        return str2;
    }

    // 逆操作异或
    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);

    }


}

public class hookfish {
    private HashMap<String, Character> fish_dcode;
    private HashMap<Character, String> fish_ecode;
    private String strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji";

    public hookfish() {
        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;
    }
}


//VNCTF{u_re4l1y_kn0w_H0Ok_my_f1Sh!1l}

hex_str = "9a9287988abfb9a3b6a978b075bda3afb274bba38c7493afa3b1bda3aa7597ac6575b0c1"

# 将十六进制字符串按每8字节(即16个字符)分块
chunk_size = 2
chunks = [hex_str[i:i+chunk_size] for i in range(0, len(hex_str), chunk_size)]

# 将每个块转换为十进制并存入列表
decimal_list = [int(chunk, 16) for chunk in chunks]

# 打印结果
print(decimal_list)
print(chunks)

for i in decimal_list:
    print(chr(i - 68),end='')


kotlindroid

在SearchActivityKt初始化加密并验证flag。密文是”MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==”,iv是”114514”,加密方式是”AES/GCM/NoPadding”。密钥在Button$lambda$7$lambda$6方法内由两部分异或加密后拼接而成,后面传入了check函数中,hook一下就能拿到(也可以自己解)。但是没看到加密的位置,hook一下check的参数,发现输入的数据在经过sec()方法后就被加密了,查看sec方法后发现,数据被传入了SearchActivityKt$sec$1类。

加密逻辑在SearchActivityKt$sec$1类里面,在SearchActivityKt$sec$1方法下我们可以看到,被传入的函数被作为结果函数在最后执行,并接受明文和密钥。加密逻辑在invokeSuspend()方法里。

image-20250214003720064

这里还用JNI.INSTANCE.getAt()方法获取了一个从so层传来的字符串并在updateAAD(bytes)中作为加密的ADD传入。之后进行AES-gcm的加密,最后再转为base64。hook一下JNI.INSTANCE.getAt()获得ADD。从题目提示得知tag也是密文的一部分。AES-GCM的密文结果一般是”iv | encrypt | tag(16bit)”。我们把密文base64解码后获取中间的密文部分。再提取tag,输入数据解密就行。

from base64 import b64decode
from Crypto.Cipher import AES

key = b"atrikeyssyekirta"
data = "MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA=="
aad = b"mysecretadd"

decoded_data = b64decode(data)

iv = decoded_data[:6]
ciphertext = decoded_data[6:-16]
tag = decoded_data[-16:]

enc = AES.new(key, AES.MODE_GCM, nonce=iv)
enc.update(aad)

flag = enc.decrypt_and_verify(ciphertext, tag)

print(flag)

hook脚本

Java.perform(function(){ 
    let TextKt = Java.use("androidx.compose.material3.TextKt");
TextKt["Text--4IGK_g"].overload('java.lang.String', 'androidx.compose.ui.Modifier', 'long', 'long', 'androidx.compose.ui.text.font.FontStyle', 'androidx.compose.ui.text.font.FontWeight', 'androidx.compose.ui.text.font.FontFamily', 'long', 'androidx.compose.ui.text.style.TextDecoration', 'androidx.compose.ui.text.style.TextAlign', 'long', 'int', 'boolean', 'int', 'int', 'kotlin.jvm.functions.Function1', 'androidx.compose.ui.text.TextStyle', 'androidx.compose.runtime.Composer', 'int', 'int', 'int').implementation = function (text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, maxLines, minLines, function1, style, $composer, $changed, $changed1, i) {
    console.log(`TextKt.m2466Text4IGK_g is called: text=${text}, modifier=${modifier}, color=${color}, fontSize=${fontSize}, fontStyle=${fontStyle}, fontWeight=${fontWeight}, fontFamily=${fontFamily}, letterSpacing=${letterSpacing}, textDecoration=${textDecoration}, textAlign=${textAlign}, lineHeight=${lineHeight}, overflow=${overflow}, softWrap=${softWrap}, maxLines=${maxLines}, minLines=${minLines}, function1=${function1}, style=${style}, $composer=${$composer}, $changed=${$changed}, $changed1=${$changed1}, i=${i}`);
    this["Text--4IGK_g"](text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, maxLines, minLines, function1, style, $composer, $changed, $changed1, i);
};


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);
};

SearchActivityKt["sec"].implementation = function (context, secretKey, text, function1) {
    console.log(`SearchActivityKt.sec is called: context=${context}, secretKey=${secretKey}, text=${text}, function1=${function1}`);
    this["sec"](context, secretKey, text, function1);
};

SearchActivityKt["check$lambda$14"].implementation = function (context, flag) {
    console.log(`SearchActivityKt.check$lambda$14 is called: context=${context}, flag=${flag}`);

    let result = this["check$lambda$14"](context, flag);
    console.log(`SearchActivityKt.check$lambda$14 result=${result}`);
    return result;
};

let JNI = Java.use("com.atri.ezcompose.JNI");
JNI["getAt"].implementation = function () {
    console.log(`JNI.getAt is called`);
    let result = this["getAt"]();
    console.log(`JNI.getAt result=${result}`);
    return result;
};
let Intrinsics = Java.use("kotlin.jvm.internal.Intrinsics");
Intrinsics["checkNotNullExpressionValue"].implementation = function (value, expression) {
    console.log(`Intrinsics.checkNotNullExpressionValue is called: value=${value}, expression=${expression}`);
    this["checkNotNullExpressionValue"](value, expression);
};
})

幸运转盘

鸿蒙逆向。用abc编译器反编译.hap解压后的etc\modules.abc文件。被混淆了,不过不影响分析,用搜索寻找函数的声明与引用就行。

我们可以看到一个Index页面,这是第一个界面,里面有一个#~@0>@1^d#()方法。

image-20250214012747907

这里先加载了一个lib库,里面传入了一些数据,我们ctrl+f搜索一下名字就会发现_lexenv_0_1_.numX是输入数据的长度,lexenv_0_1.pw是我们输入的数据,还有一个常量24也被传入。然后把返回的结果传参传给了pages/MyPage。

我们来看MyPage,往下看会看到一串密文。

image-20250214013405155

在lexenv_0_0_ = Uint8Array(buffer.from(lexenv_0_5.result).buffer);中我们发现_lexenv_0_5就是MyPage这个对象,从中我们获取了result这个属性并转为字符串。这就是从Index传来的参数。

往下看ldlexvar.forEach()这个方法对我们的输入进行了一些操作,第一个是(result+1)^7,第二个就是在和_lexenv_0_1也就是密文进行比较。

现在我们看c层对我们的输入进行的操作,ida打开libhello.so搜索MyCry,这是在调用lib库的时候传入参数的的函数。其实在你第一步解密密文时就会发现这是一个base64加密编码,也可以去字符串表找base64的表用交叉引用定位函数。

image-20250214015928368

有一个+3还有两个RC4魔改加密,试两次就行。估计是等于50的那个。

import base64

def KSA(key):
    """ Key-Scheduling Algorithm (KSA) """
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]
    return S


def PRGA(S):
    """ Pseudo-Random Generation Algorithm (PRGA) """
    i, j = 0, 0
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        K = S[(S[i] + S[j]) % 256]
        yield K


def RC4(key, text):
    """ RC4 encryption/decryption """
    S = KSA(key)
    keystream = PRGA(S)
    res = []
    for char in text:
        res.append(char ^ next(keystream))
    return bytes(res)

s = [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]
enc=""

for i in range(len(s)):
    enc += chr((s[i]^7) -1)
print(enc)

dec = base64.b64decode(enc.encode())

enc_1 = []
for i in dec:
    b = i ^ 0x28
    enc_1.append(b)

key = b'Take_it_easy'

ciphertext = RC4(key, enc_1)
flag = ""
for i in ciphertext:
    flag += chr(i-3)
print(flag)

# aLJ5aJqO/ApBpA/C9S8gUIsa1MSDBtijKDeqYwsziTYs
# VNCTF{JUst_$ne_Iast_dance_2025!} 

AndroidLux

程序在java层用socket与linux虚拟环境进行通信,把输入传给linux进行判断。在Init类下发现解压了assert中的env文件进行初始化,我们解压下来就可以看到linux的rootfs文件。在Service类下可以看到启动了一个服务,就是启动了一个linux虚拟环境并设置执行目录为/root并执行了/root下的env文件。我们在rootfs文件中找到env文件,ida打开分析。里面有些用B和B.IM构成的花指令,用E0 03 00 AA字节码修改掉。发现main函数和一个魔改的base64的加密逻辑。main函数实现了对数据的接受,进行base64编码和最后的比较。直接写出解密脚本发现解密不出。感觉是什么地方被修改了。于是我提取env文件在手机上自启linux虚拟环境运行,同时运行frida进行hook。发现输入的数据并没有被修改。这就纳闷了。那么数据的修改不在env文件里,也不再java层里,那么就在虚拟linux系统上了。

最后看官方的文档,原来是修改了/etc/ld.so.preload配置文件加载了另一个so文件(参考),对read()和strcmp()做了手脚。至于怎么找到的,官方文档说用文件的修改时间对env内所有文件排序就可以看到了。

find . -type f -printf "%T@ %p\n" | sort -nr | head -20 | cut -d' ' -f2-

/etc/ld.so.preload,里面指向的是”\usr\libexec\libexec.so”,ida打开就可以看到逻辑。


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

unsigned char* base64_table = (unsigned char*)"TUVWXYZabcdefghijABCDEF456789GHIJKLMNOPQRSklmnopqrstuvwxyz0123+/";

unsigned char* base64_decode(unsigned char* code)
{
    int table[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 58, 59, 60, 61, 23, 24, 25, 26, 27, 28, 255, 255, 255, 255, 255, 255, 255, 17, 18, 19, 20, 21, 22, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 1, 2, 3, 4, 5, 6, 255, 255, 255, 255, 255, 255, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255};

    long len;
    long str_len;
    unsigned char* res;
    int i, j;

    //计算解码后的字符串长度  
    len = strlen((const char*)code);
    //判断编码后的字符串后是否有=  
    if (strstr((const char*)code, "=="))
        str_len = len / 4 * 3 - 2;
    else if (strstr((const char*)code, "="))
        str_len = len / 4 * 3 - 1;
    else
        str_len = len / 4 * 3;

    res = (unsigned char*)malloc(sizeof(unsigned char) * str_len + 1);
    res[str_len] = '\0';
    unsigned char a, b,c;

    //以4个字符为一位进行解码  
    for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
    {
        res[j] = (((unsigned char)table[code[i]]) << 2) | (((unsigned char)table[code[i + 1]]) & 3); 
        res[j + 1] = ((((unsigned char)table[code[i + 1]]) & 0x3c) << 2) | (((unsigned char)table[code[i + 2]]) & 0xf); 
        a = (unsigned char)table[code[i + 2]];
        c = a >> 4 << 6;
        b = ((unsigned char)table[code[i + 3]]);
        res[j + 2] =  c|b;   
    }

    return res;

}

int recmp(unsigned char* data) {
    unsigned char* tmp[100];
    unsigned char tmp_a13,tmp_d13;
    
    for (int i = 0; i < 53; i++) {
        tmp_a13 = data[i] + 13;
        tmp_d13 = data[i] - 13;
        if (tmp_d13 <= 64 || tmp_d13 > 77) {
            if (tmp_a13 <= 77 || tmp_a13 > 90) {
                if (tmp_d13 <= 96 || tmp_d13 > 109) {
                    if (tmp_a13 <= 109 || tmp_a13 > 122) {
                        continue;
                    }
                    else data[i] = tmp_a13;
                }
                else  data[i] = tmp_d13;
            }
            else data[i] = tmp_a13;
        }
        else  data[i] = tmp_d13;
    }
    return 0;
}
int rexor(unsigned char* data) {
    for (int i = 0; i < 53; i++) {
        data[i] ^= 1;
    }
    return 0;
}

int main() {
    unsigned char m[] = "RPVIRN40R9PU67ue6RUH88Rgs65Bp8td8VQm4SPAT8Kj97QgVG==";
    
    recmp(m);

    for (int i = 0; i < 53; i++) {
        printf("%c", m[i]);
    }

    unsigned char* result = result = base64_decode(m);;
    
    rexor(result);

    printf("\n");
    for (int i = 0; i < 37; i++) {
        printf("%c", result[i]);
    }
    printf("\n");
    
}

这题的逻辑是proot通过ptrace拦截系统调用,并修改调用指向虚拟环境的位置,从而在虚拟机中执行root命令。同时也限制了调试。参考

Fuko’s_starfish

有些多余的东西可以pacth掉,最后有个反调试,在汇编里把判断改成jz就行。前面的游戏都可以patch一下,比如把猜数字的值改成0,贪吃蛇的分数改成0就通过。

image-20250303132754704

搜索字符串可以找到最终的逻辑,里面有两个aes加密。开头加载密钥,交叉引用一下可以看到密钥初始的位置。

image-20250303132830708

下面还有一个CheckRemoteDebuggerPresent函数,如果不处于调试,密钥异或0x17。写脚本解拿到密钥解AES即可。密文在比较的位置。

查找srand的引用可以找到种子114514image-20250303133324237

#include <stdio.h>
#include <stdlib.h>

int main() {
    srand(114514);

    for (int i = 0; i < 16; i++) {
        int a = rand();
        printf("%.2x", (a % 255)^0x17);
    }
    
}
//09e5fdeb683175b6b13b840891eb78d2

密文(3D011C190BA090815F672731A89AA47497362167AB2EB4A09418D37D93E646E7)

image-20250303134804011