CTFPLUS逆向每周练习-2026.08.31-2026.09.06

admin 2026年9月1日23:18:22评论41 views字数 23871阅读79分34秒阅读模式

本文介绍了CTFPLUS逆向每周练习的两道题目。第一道涉及UPX壳被修改后脱壳失败,通过修复节区名称成功脱壳,并分析ChaCha20加密流程,动态调试提取交换后的密钥流进行异或解密。第二道为Java打包exe程序逆向,通过分析释放的jar包,使用z3求解器求解线性方程组获得flag。

1.CTF+Binary-[Re挑战题]-Ferris's Secret Scuttle[Score3]

使用DIE查看,发现是UPX壳:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

使用upx -d脱壳,脱壳失败:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

查看一下节区,发现本来应该是UPX节,被修改成了Cha节,导致upx无法定位:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

这里把Cha修改成UPX,在进行upx -d命令,发现脱壳成功

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

使用IDA打开找到主函数:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

点进去这个main,非常长一段,一段一段分析,发现首先获取用户输入字符串:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

然后调用函数crab_magic生成一串密钥,从后面可以知道是chacha20的加密,所以这里生成了一个32字节密钥和12字节的Nonce,动态调试到这里可以看到:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

然后再往下调用函数apply_keystream生成密钥流:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

再交换密钥流的首尾:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

最后进行异或加密以及和密文做比较:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

这里因为多了一步密钥流的首尾交换,所以没法直接用cyberchef解密,直接解密出来发现中间是对的,但是首尾不对:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

所以动态调试到交换完密钥流,然后提取出来在python脚本里面直接拿密文进行异或就行:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06
if __name__ == '__main__':    cipher =[        0x76, 0x34, 0x7f, 0x4f, 0xce, 0x93, 0xd5, 0x1b, 0x09, 0x0b, 0x69, 0x13, 0x2e, 0x4c, 0x52, 0x32,        0xcb, 0x35, 0x3f, 0x8a, 0xf8, 0xef, 0x4f, 0x40, 0xc5, 0x76, 0x9c, 0xd9, 0x41, 0xf7, 0xcb, 0x1d,        0x9e, 0x3d, 0xca, 0x52, 0x93, 0x2a, 0x60    ]    key_stream = [        0x1D, 0x58, 0x1E, 0x28, 0xB5, 0xD5, 0xE6, 0x69, 0x7B, 0x3A, 0x1A, 0x4C, 0x4A, 0x7C, 0x37, 0x41,        0x94, 0x5B, 0x0F, 0xFE, 0xA7, 0x8B, 0x7B, 0x0E, 0xA6, 0x45, 0xC3, 0x9B, 0x38, 0xA8, 0xBF, 0x75,        0xAD, 0x62, 0xA8, 0x1D, 0xA3, 0x41, 0x10    ]    tmp = key_stream[0]    key_stream[0] = key_stream[len(key_stream) - 1]    key_stream[len(key_stream)-1] = tmp    flag = ''    index = 0    for c in cipher:        flag += chr(key_stream[index] ^ c)        index += 1    print(flag)

运行后输出flag:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

2.CTF+Binary-[Re挑战题]-JvavMaster[Score5]

下载题目附件,是一个Java打包的exe程序,拖入IDA中查看发现有exe4j的关键词:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

运行一下程序,然后找一下释放出来的jar包:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

在Temp目录下,提取出来后导入jd-gui中查看:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

主函数首先读取用户输入,然后调用checkKey方法:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

这里MATRIX是一个用自定义的random方法生成的伪随机数常量矩阵,key这里总共12个数,都是0-255范围,需要满足这里定义的线性方程,使用python的z3求解器进行求解:

from z3 import *def get_random(seed):    x = seed    x ^= x << 11    x ^= x >> 4    x ^= x << 5    x ^= x >> 14    return x & 0xFFif __name__ == '__main__':    MATRIX = [[0] * 11 for i in range(11)]    for i in range(11):        for j in range(11):            MATRIX[i][j] = get_random(i * 11 + j)    TARGET = [140286, 171210, 157332, 135722, 144003, 174006, 144135, 152772, 144501, 155988, 156148]    vars = [Int(f”k{i}”) for i in range(11)]    solver = Solver()    for var in vars:        solver.add(var>=0,var<=255)    for i in range(11):        expr = sum(MATRIX[i][j]*vars[j] for j in range(11))        solver.add(expr == TARGET[i])    if solver.check() == sat:        model = solver.model()        result = [model[v].as_long() for v in vars]        key = ””        for r in result:            key += chr(r)        print(key)

运行后成功解出key:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

接下来会要求输入flag,flag使用rc4加密,加密密钥就是刚才的key,然后把加密后的密文传入方法FlagChecker.Checker,返回true则正确

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

查看这里的RC4加密算法,发现也是经过修改的:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

再查看Checker方法,也是需要满足一定条件

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

这里直接写一个java的脚本:

import java.nio.charset.StandardCharsets;import java.util.Arrays;public class Main {    public static byte rol(byte b, int shift) {        shift &= 0x7;        int unsigned = b & 0xFF;        return (byte)((unsigned << shift | unsigned >> 8 - shift) & 0xFF);    }    public static byte ror(byte b, int shift) {        shift &= 0x7;        int unsigned = b & 0xFF;        return (byte)((unsigned >> shift | unsigned << 8 - shift) & 0xFF);    }    public static void getInstruction(int index, byte[] opcode) {        switch (index) {            case 0:                opcode[0] = 3;                opcode[1] = 24;                opcode[2] = -43;                break;            case 1:                opcode[0] = -52;                opcode[1] = 41;                opcode[2] = 29;                break;            case 3:                opcode[0] = 5;                opcode[1] = 9;                opcode[2] = 4;                break;            case 12:                opcode[0] = 2;                opcode[1] = 15;                opcode[2] = 2;                break;            case 13:                opcode[0] = -52;                opcode[1] = 25;                opcode[2] = -119;                break;            case 14:                opcode[0] = -52;                opcode[1] = 30;                opcode[2] = 18;                break;            case 21:                opcode[0] = 4;                opcode[1] = 7;                opcode[2] = 3;                break;            case 22:                opcode[0] = 2;                opcode[1] = 23;                opcode[2] = -49;                break;            case 23:                opcode[0] = -52;                opcode[1] = 46;                opcode[2] = 79;                break;            case 24:                opcode[0] = -52;                opcode[1] = 26;                opcode[2] = -113;                break;            case 26:                opcode[0] = 1;                opcode[1] = 11;                opcode[2] = -31;                break;            case 27:                opcode[0] = -52;                opcode[1] = 33;                opcode[2] = 121;                break;            case 37:                opcode[0] = -52;                opcode[1] = 34;                opcode[2] = -35;                break;            case 38:                opcode[0] = -52;                opcode[1] = 28;                opcode[2] = 77;                break;            case 39:                opcode[0] = 1;                opcode[1] = 47;                opcode[2] = -64;                break;            case 40:                opcode[0] = 2;                opcode[1] = 4;                opcode[2] = 30;                break;            case 42:                opcode[0] = -52;                opcode[1] = 6;                opcode[2] = -126;                break;            case 43:                opcode[0] = 2;                opcode[1] = 1;                opcode[2] = -39;                break;            case 48:                opcode[0] = -52;                opcode[1] = 2;                opcode[2] = -45;                break;            case 49:                opcode[0] = 4;                opcode[1] = 35;                opcode[2] = 4;                break;            case 50:                opcode[0] = -1;                opcode[1] = 0;                opcode[2] = 0;                break;            case 51:                opcode[0] = -52;                opcode[1] = 19;                opcode[2] = 59;                break;            case 60:                opcode[0] = -52;                opcode[1] = 29;                opcode[2] = -105;                break;            case 61:                opcode[0] = 4;                opcode[1] = 36;                opcode[2] = 6;                break;            case 62:                opcode[0] = 5;                opcode[1] = 13;                opcode[2] = 1;                break;            case 68:                opcode[0] = -52;                opcode[1] = 5;                opcode[2] = 73;                break;            case 70:                opcode[0] = 5;                opcode[1] = 22;                opcode[2] = 1;                break;            case 71:                opcode[0] = -52;                opcode[1] = 44;                opcode[2] = 108;                break;            case 73:                opcode[0] = 1;                opcode[1] = 21;                opcode[2] = -106;                break;            case 74:                opcode[0] = 1;                opcode[1] = 27;                opcode[2] = 51;                break;            case 75:                opcode[0] = -52;                opcode[1] = 3;                opcode[2] = 76;                break;            case 80:                opcode[0] = 1;                opcode[1] = 12;                opcode[2] = 40;                break;            case 81:                opcode[0] = -52;                opcode[1] = 39;                opcode[2] = 35;                break;            case 82:                opcode[0] = -52;                opcode[1] = 38;                opcode[2] = -78;                break;            case 92:                opcode[0] = 4;                opcode[1] = 14;                opcode[2] = 5;                break;            case 93:                opcode[0] = -52;                opcode[1] = 43;                opcode[2] = -43;                break;            case 95:                opcode[0] = 2;                opcode[1] = 20;                opcode[2] = 111;                break;            case 96:                opcode[0] = -52;                opcode[1] = 40;                opcode[2] = 61;                break;            case 97:                opcode[0] = 5;                opcode[1] = 18;                opcode[2] = 2;                break;            case 98:                opcode[0] = 5;                opcode[1] = 10;                opcode[2] = 2;                break;            case 108:                opcode[0] = -52;                opcode[1] = 16;                opcode[2] = -77;                break;            case 109:                opcode[0] = 3;                opcode[1] = 31;                opcode[2] = -1;                break;            case 111:                opcode[0] = -52;                opcode[1] = 37;                opcode[2] = 44;                break;            case 116:                opcode[0] = 2;                opcode[1] = 17;                opcode[2] = -23;                break;            case 118:                opcode[0] = -52;                opcode[1] = 45;                opcode[2] = 4;                break;            case 119:                opcode[0] = 1;                opcode[1] = 8;                opcode[2] = 84;                break;            case 121:                opcode[0] = -52;                opcode[1] = 0;                opcode[2] = 114;                break;            case 122:                opcode[0] = -52;                opcode[1] = 42;                opcode[2] = -21;                break;            case 123:                opcode[0] = 2;                opcode[1] = 32;                opcode[2] = 11;                break;            case 128:                opcode[0] = 4;                opcode[1] = 34;                opcode[2] = 6;                break;            case 130:                opcode[0] = -52;                opcode[1] = 47;                opcode[2] = 4;                break;            case 131:                opcode[0] = 1;                opcode[1] = 28;                opcode[2] = 125;                break;            case 141:                opcode[0] = -52;                opcode[1] = 4;                opcode[2] = 46;                break;            case 142:                opcode[0] = -52;                opcode[1] = 1;                opcode[2] = -44;                break;            case 143:                opcode[0] = 1;                opcode[1] = 6;                opcode[2] = -84;                break;            case 148:                opcode[0] = -52;                opcode[1] = 35;                opcode[2] = 94;                break;            case 149:                opcode[0] = 3;                opcode[1] = 2;                opcode[2] = -93;                break;            case 150:                opcode[0] = 4;                opcode[1] = 19;                opcode[2] = 4;                break;            case 152:                opcode[0] = -52;                opcode[1] = 36;                opcode[2] = 27;                break;            case 153:                opcode[0] = 1;                opcode[1] = 29;                opcode[2] = 116;                break;            case 155:                opcode[0] = -52;                opcode[1] = 13;                opcode[2] = -37;                break;            case 164:                opcode[0] = 4;                opcode[1] = 41;                opcode[2] = 7;                break;            case 165:                opcode[0] = -52;                opcode[1] = 24;                opcode[2] = 119;                break;            case 166:                opcode[0] = -52;                opcode[1] = 9;                opcode[2] = -98;                break;            case 168:                opcode[0] = 3;                opcode[1] = 25;                opcode[2] = -37;                break;            case 169:                opcode[0] = -52;                opcode[1] = 15;                opcode[2] = 12;                break;            case 171:                opcode[0] = 1;                opcode[1] = 30;                opcode[2] = 14;                break;            case 176:                opcode[0] = -52;                opcode[1] = 7;                opcode[2] = 6;                break;            case 178:                opcode[0] = 3;                opcode[1] = 46;                opcode[2] = 41;                break;            case 179:                opcode[0] = -52;                opcode[1] = 23;                opcode[2] = -72;                break;            case 189:                opcode[0] = 2;                opcode[1] = 26;                opcode[2] = 61;                break;            case 190:                opcode[0] = 3;                opcode[1] = 33;                opcode[2] = -37;                break;            case 191:                opcode[0] = -52;                opcode[1] = 11;                opcode[2] = -113;                break;            case 196:                opcode[0] = -52;                opcode[1] = 18;                opcode[2] = 24;                break;            case 197:                opcode[0] = 5;                opcode[1] = 40;                opcode[2] = 4;                break;            case 199:                opcode[0] = -52;                opcode[1] = 10;                opcode[2] = -95;                break;            case 200:                opcode[0] = -52;                opcode[1] = 31;                opcode[2] = -57;                break;            case 201:                opcode[0] = 1;                opcode[1] = 16;                opcode[2] = -7;                break;            case 202:                opcode[0] = 1;                opcode[1] = 37;                opcode[2] = -1;                break;            case 209:                opcode[0] = -52;                opcode[1] = 17;                opcode[2] = -46;                break;            case 210:                opcode[0] = -52;                opcode[1] = 8;                opcode[2] = -94;                break;            case 211:                opcode[0] = 5;                opcode[1] = 45;                opcode[2] = 3;                break;            case 220:                opcode[0] = 4;                opcode[1] = 0;                opcode[2] = 4;                break;            case 222:                opcode[0] = -52;                opcode[1] = 32;                opcode[2] = -56;                break;            case 223:                opcode[0] = 5;                opcode[1] = 42;                opcode[2] = 3;                break;            case 225:                opcode[0] = 3;                opcode[1] = 5;                opcode[2] = 11;                break;            case 226:                opcode[0] = 4;                opcode[1] = 44;                opcode[2] = 1;                break;            case 227:                opcode[0] = -52;                opcode[1] = 22;                opcode[2] = 32;                break;            case 236:                opcode[0] = -52;                opcode[1] = 21;                opcode[2] = 72;                break;            case 238:                opcode[0] = 5;                opcode[1] = 3;                opcode[2] = 4;                break;            case 239:                opcode[0] = -52;                opcode[1] = 27;                opcode[2] = -9;                break;            case 244:                opcode[0] = 3;                opcode[1] = 39;                opcode[2] = 120;                break;            case 245:                opcode[0] = -52;                opcode[1] = 12;                opcode[2] = -119;                break;            case 247:                opcode[0] = 5;                opcode[1] = 38;                opcode[2] = 2;                break;            case 248:                opcode[0] = 2;                opcode[1] = 43;                opcode[2] = 83;                break;            case 249:                opcode[0] = -52;                opcode[1] = 14;                opcode[2] = -25;                break;            case 250:                opcode[0] = -52;                opcode[1] = 20;                opcode[2] = 105;                break;        }    }    public static int random(int seed) {        int x = seed;        x ^= x << 11;        x ^= x >>> 4;        x ^= x << 5;        x ^= x >>> 14;        return x & 0xFF;    }    public static byte[] encrypt(byte[] plaintext, byte[] key) {        int[] box = new int[256];        for (int i = 0; i < 256; i++)            box[i] = 255 - i ^ 0x83;        int x = 0;        for (int j = 0; j < 256; j++) {            x = (x + box[j] + key[(j + 72) % key.length]) % 256;            int temp = box[j];            box[j] = box[x];            box[x] = temp;        }        x = 0;        int y = 0;        byte[] output = new byte[plaintext.length];        for (int k = 0; k < plaintext.length; k++) {            x = (x + 3) % 256;            y = y - box[x] & 0xFF;            int temp = box[x];            box[x] = box[y];            box[y] = temp;            int index = (box[x] ^ box[y]) % 256;            output[k] = (byte)((plaintext[k]^ 0x77) - box[index] );        }        return output;    }    public static void main(String[] args) {        byte[] opcode = new byte[3];        byte[] results = new byte[48];        int i = 0;        int param1 = 0;        int opcode1 = 0;        while (true) {            getInstruction(random(i), opcode);            if (opcode[0] == -1)                break;            int op = opcode[0] & 0xFF;            int idx = opcode[1] & 0xFF;            int param = opcode[2] & 0xFF;            switch (op) {                case 1, 2, 3, 4, 5:                    opcode1 = op;                    param1 = param;                    break;                case 204:                    if(opcode1 == 1) {                        results[idx] = (byte) (param - param1);                    } else if (opcode1 == 2) {                        results[idx] = (byte) (param + param1);                    }else if (opcode1 == 3) {                        results[idx] = (byte) (param ^ param1);                    }else if (opcode1 == 4) {                        results[idx] = ror((byte) param, param1);                    }else if (opcode1 == 5) {                        results[idx] = rol((byte) param, param1);                    }                    break;            }            i++;        }        byte[] keys = ”jvav_master”.getBytes(StandardCharsets.UTF_8);        byte[] flag = encrypt(results, keys);        String str = new String(flag, StandardCharsets.UTF_8);        System.out.println(str);    }}

运行后输出flag:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

3.reReverse

下载题目附件,是一个elf64位程序,拖入IDA中静态分析,发现主函数什么都没有,在init函数里面找到了线索:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

这里相当于调用了两个函数:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

先看sub_400810:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

这里利用sigaction函数注册了信号拦截处理机制,处理函数就是sub_400EF0:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

这里会修改程序执行的rip,也就是每次执行一条指令都会触发,然后修改程序执行流,导致无法动态调试,以及干扰静态分析,这里使用unicorn模拟执行,记录下程序的实际执行指令流:

from elftools.elf.elffile import ELFFilefrom unicorn import *from unicorn.x86_const import *import structfrom capstone import Cs, CS_ARCH_X86, CS_MODE_64BINARY = ”../sample/new_re”INPUT = b”A” * 40 + b”n”BITMAP_ADDR = 0x403080STACK_SIZE = 0x20000STACK_TOP = 0x7FFFFFFFE000TEXT_START = 0x400800TEXT_END = 0x400FF5ENTRY = 0x400DF1PLT_PUTS = 0x401010PLT_SIGACTION = 0x401020PLT_STACK_CHK = 0x401030PLT_FGETS = 0x401040PLT_PRINTF_CHK = 0x401050md = Cs(CS_ARCH_X86, CS_MODE_64)def read_bitmap(path):    with open(path, ”rb”) as f:        elf = ELFFile(f)        for seg in elf.iter_segments():            if seg[”p_type”] == ”PT_LOAD” and seg[”p_vaddr”] <= BITMAP_ADDR < seg[”p_vaddr”] + seg[”p_filesz”]:                data = seg.data()                off = BITMAP_ADDR - seg[”p_vaddr”]                return data[off:off + 0x200]    raise RuntimeError(”bitmap not found”)def get_bit(bitmap, addr):    off = addr - TEXT_START    if off < 0 or off >= len(bitmap) * 8:        return 0    return (bitmap[off >> 3] >> (off & 7)) & 1def prev_second_one(bitmap, addr):    cnt = 0    a = addr - 1    while a >= TEXT_START:        if get_bit(bitmap, a):            cnt += 1            if cnt == 2:                return a        a -= 1    return Nonedef load_elf(uc, path):    with open(path, ”rb”) as f:        elf = ELFFile(f)        for seg in elf.iter_segments():            if seg[”p_type”] != ”PT_LOAD”:                continue            vaddr = seg[”p_vaddr”]            memsz = seg[”p_memsz”]            page_start = vaddr & ~0xfff            page_end = (vaddr + memsz + 0xfff) & ~0xfff            uc.mem_map(page_start, page_end - page_start)            uc.mem_write(vaddr, seg.data())            if memsz > seg[”p_filesz”]:                uc.mem_write(vaddr + seg[”p_filesz”], b”x00” * (memsz - seg[”p_filesz”]))class UnicornTracer:    def __init__(self, path, inp):        self.bitmap = read_bitmap(path)        self.input_data = inp        self.input_ptr = 0        self.trace = []        self.libc_calls = []        self.output = []        self.trap_flag = True        self.just_jumped = False        self.canary = 0x1122334455667788        self.uc = Uc(UC_ARCH_X86, UC_MODE_64)        load_elf(self.uc, path)        self.uc.mem_map(STACK_TOP - STACK_SIZE, STACK_SIZE + 0x1000)        self.uc.reg_write(UC_X86_REG_RSP, STACK_TOP)        FS_BASE = 0x700000000000        self.uc.msr_write(0xC0000100, FS_BASE)        self.uc.mem_map(FS_BASE, 0x1000)        self.uc.mem_write(FS_BASE + 0x28, struct.pack(”<Q”, self.canary))        self.uc.reg_write(UC_X86_REG_RDI, 1)        self.uc.reg_write(UC_X86_REG_RSI, 0)        self.uc.hook_add(UC_HOOK_CODE, self.hook_code)        self.uc.hook_add(UC_HOOK_MEM_UNMAPPED, self.hook_unmapped)    def sigtrap_handler(self, rip):        if TEXT_START <= rip < TEXT_END and get_bit(self.bitmap, rip):            new_rip = prev_second_one(self.bitmap, rip)            if new_rip is not None:                return new_rip        return rip    def hook_code(self, uc, address, size, user_data):        if self.trap_flag and not self.just_jumped:            new_rip = self.sigtrap_handler(address)            if new_rip != address:                uc.reg_write(UC_X86_REG_RIP, new_rip)                self.just_jumped = True# 下一条(目标指令)直接执行                return        self.just_jumped = False        code = uc.mem_read(address, size)        if code and code[0] == 0xE8:# call rel32            insn = next(md.disasm(code, address))            self.trace.append((address, insn.mnemonic, insn.op_str))            rel = struct.unpack(”<i”, code[1:5])[0]            target = (address + size + rel) & 0xFFFFFFFFFFFFFFFF            self.handle_libc(address, target)            return        try:            insn = next(md.disasm(code, address))            self.trace.append((address, insn.mnemonic, insn.op_str))        except Exception:            self.trace.append((address, ”??”, ””))    def handle_libc(self, call_addr, target):        rdi = self.uc.reg_read(UC_X86_REG_RDI)        rsi = self.uc.reg_read(UC_X86_REG_RSI)        ret_after = call_addr + 5        if target == PLT_PRINTF_CHK:            s = self.read_cstr(rsi)            self.output.append(f”printf: {s!r}”)            self.libc_calls.append((”printf”, s))            self.uc.reg_write(UC_X86_REG_RAX, len(s))            self.uc.reg_write(UC_X86_REG_RIP, ret_after)        elif target == PLT_FGETS:            buf, size = rdi, rsi            n = min(len(self.input_data) - self.input_ptr, size - 1)            chunk = self.input_data[self.input_ptr:self.input_ptr + n]            self.input_ptr += n            self.uc.mem_write(buf, chunk + b”x00”)            self.output.append(f”fgets: {chunk!r}”)            self.libc_calls.append((”fgets”, chunk))            self.uc.reg_write(UC_X86_REG_RAX, buf)            self.uc.reg_write(UC_X86_REG_RIP, ret_after)        elif target == PLT_PUTS:            s = self.read_cstr(rdi)            self.output.append(f”puts: {s!r}”)            self.libc_calls.append((”puts”, s))            self.uc.reg_write(UC_X86_REG_RAX, len(s) + 1)            self.uc.reg_write(UC_X86_REG_RIP, ret_after)        elif target == PLT_SIGACTION:            self.libc_calls.append((”sigaction”, ””))            self.uc.reg_write(UC_X86_REG_RAX, 0)            self.uc.reg_write(UC_X86_REG_RIP, ret_after)        elif target == PLT_STACK_CHK:            self.output.append(”!! stack_chk_fail”)            self.uc.emu_stop()        else:            self.libc_calls.append((hex(target), ””))    def hook_unmapped(self, uc, access, address, size, value, user_data):        print(f”!! unmapped: access={access:#x} addr={address:#x} size={size}”)        uc.emu_stop()        return False    def read_cstr(self, addr):        out = bytearray()        while True:            b = self.uc.mem_read(addr, 1)            if not b or b[0] == 0:                break            out.append(b[0])            addr += 1        return bytes(out)    def run(self, max_insns=200000):        try:            self.uc.emu_start(ENTRY, 0, count=max_insns)        except UcError as e:            print(f”!! Unicorn stopped: {e}”)    def summarize(self):        print(f”n===== trace 统计 =====”)        print(f”执行指令数: {len(self.trace)}”)        addrs = {a for a, _, _ in self.trace}        print(f”不同地址数: {len(addrs)}”)        print(f”地址范围: {min(addrs):#x} - {max(addrs):#x}”)        print(f”n===== 模拟输出 =====”)        for line in self.output:            print(f”  {line}”)        with open(”trace.txt”, ”w”, encoding='utf-8') as f:            for i, (a, m, o) in enumerate(self.trace[:]):                row = f”{i:4d} {a:#x}: {m} {o}”                f.write(f”{row}n”)if __name__ == '__main__':    t = UnicornTracer(BINARY, INPUT)    t.run()    t.summarize()

然后打开保存的trace.txt文件查看:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

每条查看一下,发现跟静态分析出来的结果的倒序的:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

只能硬看汇编代码,首先读取进来用户输入,然后判断输入长度:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

再往下看到一个magic number和几个2字节数:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

再往下看就是一堆左右移位和异或操作,结合这几个常数,应该是标准的XXTea加密:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06
CTFPLUS逆向每周练习-2026.08.31-2026.09.06

接下来需要确定key以及key的顺序,还有密文和密文的顺序,首先在自定义实现的标准XXTEA加密算法里面输出一下key里面的index顺序:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

再结合汇编执行流里面调用的key,顺序是rsp+0x2c、rsp+0xc、rsp+0x10、rsp+0x14,对照一下上面的入栈指令:

key = [0xDEAD, 0xBEEF, 0x5A7D, 0xC0FF] 

再看一下密文比较的代码段:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

提取一下做比较的寄存器,一共10个4字节,40字节,跟输入字符串比较的长度0x28对应:

r9d, 2973BD37hr10d, 8662435hr11d, 0EEF6889hebx, 0AE421991hebp, 0D7B702CFhr8d, 3B98E6E6hedx, 352ADCCFhecx, 0BFC393ABhesi, 0B3C20088hedi, 1BA99AA3h

然后去指令执行流的文档里面随便找一轮进行分析,p=0(p是输入字符串按照每4字节切分的切片index)的时候是r9d:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=1的时候是edi:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=2的时候是esi:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=3的时候是ecx:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=4的时候是edx:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=5的时候是r8d:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=6的时候是ebx:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=7的时候是ebp:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=8的时候是r11d:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

p=9的时候是r10d:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

所以正确的cipher顺序就是:

cipher = [    0x2973bd37, 0x1ba99aa3, 0xb3c20088, 0xbfc393ab, 0x352adccf,    0x3b98e6e6, 0xae421991, 0xd7b702cf, 0x0eef6889, 0x08662435]

写个python脚本进行XXTEA解密:

import structcipher = [    0x2973bd37, 0x1ba99aa3, 0xb3c20088, 0xbfc393ab, 0x352adccf,    0x3b98e6e6, 0xae421991, 0xd7b702cf, 0x0eef6889, 0x08662435]key = [0xDEAD, 0xBEEF, 0x5A7D, 0xC0FF]DELTA = 0x9E3779B9def xxtea_dec(v, k, delta=DELTA):    n = len(v)    if n < 2: return v    r = 6 + 52 // n    s = (r + 1) * delta & 0xffffffff    for _ in range(r):        s = (s - delta) & 0xffffffff        e = (s >> 2) & 3        for i in range(n-1, -1, -1):            y = v[(i+1) % n]            z = v[(i-1) % n]            mx = (((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((s ^ y) + (k[(i&3) ^ e] ^ z)))            v[i] = (v[i] - mx) & 0xffffffff    return vdecrypt = xxtea_dec(cipher, key)print(decrypt)flag_bytes = b””.join(struct.pack(”<I”, x) for x in decrypt)print(flag_bytes)

运行后成功输出flag:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

4.花鸟风月

下载题目附件,用IDA打开静态分析,发现主函数存在花指令:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

用IDA开始手动修复,把从test eax,eax到Call指令的第一个字节,都修改为nop指令(90h),然后按“C”重新分析,再到函数开头创建函数,就可以F5反编译了:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

首先需要用户输入一个key进行判断:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

写个c程序(python速度比较慢)来爆破获得正确的key值:

#include#includeintmain(void){    for (uint32_t v10 = 0; v10 <= 0xffffffff; v10++) {        uint32_t v11 = 0x81428113;        for (int i = 0; i < 10; i++) {            v11 = 0x1421043 * v11 + 0x43298815;            uint32_t shifts = (13 * i) % 32;            v11 = v11 ^ ((v10 << shifts) | (v10 >> (32 - shifts)));        }        if (v11 == 0xA745DA5D) {            printf(”the right key is :0x%08xn”, v10);            break;        }    }    return 0;}

运行等待一小会儿获得正确的key为:0x98bf3b77

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

验证一下:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

然后开始看下一步,拿到正确的key后,还会继续要求输入flag,传入到函数sub_4011D0中:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

可以看到是逐字节的一系列异或,然后下面又调用函数sub_401070:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

可以看到是一个base64编码,但是3字节变4字节的时候,第2个字节和第3个字节互换了位置,然后再调用函数sub_401000解密密文:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

逐字节跟0x99异或即可,然后再跟明文加密并编码后进行比较,写个python解密脚本:

import structimport base64if __name__ == '__main__':    step1_const = 5 + 3 + 9 * 4    cipher =[        0xd4, 0xd8, 0xc9, 0xdd, 0xfa, 0xaa, 0xde, 0xed, 0xc9, 0xc8, 0xaa, 0xad, 0xef, 0xd4, 0xaa, 0xe9,        0xd7, 0xdb, 0xfb, 0xa8, 0xc9, 0xce, 0xde, 0xe1, 0xfa, 0xfa, 0xd1, 0xed, 0xef, 0xe1, 0xd1, 0xa8,        0xd7, 0xcf, 0xf8, 0xa0, 0xd3, 0xc9, 0xcd, 0xed, 0xf8, 0xfa, 0xca, 0xac, 0xae, 0xd0, 0xf0, 0xf5,        0xd7, 0xac, 0xec, 0xe1, 0xd3, 0xca, 0xce, 0xac, 0xd0, 0xee, 0xf4, 0xa4]    decrypt1 = b''    for i in range(len(cipher)):        decrypt1 += struct.pack('> (8*(index % 4))) & 0xff        flag += struct.pack('<B', (d ^ xor_key) & 0xff)        index += 1    print(flag)

运行后发现结果不对:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

再返回查看一下发现问题出在函数sub_4011D0,表面看确实反编译成伪代码了,但是汇编代码里面存在花指令干扰了静态分析,还是一步步去除花指令:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

可以看到实际上还有一轮循环异或,重新修改python脚本:

import structimport base64if __name__ == '__main__':    step1_const = 5 + 3 + 9 * 4    cipher =[        0xd4, 0xd8, 0xc9, 0xdd, 0xfa, 0xaa, 0xde, 0xed, 0xc9, 0xc8, 0xaa, 0xad, 0xef, 0xd4, 0xaa, 0xe9,        0xd7, 0xdb, 0xfb, 0xa8, 0xc9, 0xce, 0xde, 0xe1, 0xfa, 0xfa, 0xd1, 0xed, 0xef, 0xe1, 0xd1, 0xa8,        0xd7, 0xcf, 0xf8, 0xa0, 0xd3, 0xc9, 0xcd, 0xed, 0xf8, 0xfa, 0xca, 0xac, 0xae, 0xd0, 0xf0, 0xf5,        0xd7, 0xac, 0xec, 0xe1, 0xd3, 0xca, 0xce, 0xac, 0xd0, 0xee, 0xf4, 0xa4]    decrypt1 = b''    for i in range(len(cipher)):        decrypt1 += struct.pack('> (8 * (index & 3))) & 0xff        k2 = ((k1 >> 4) | ((k1 << 4) & 0xff)) & 0xff        flag += struct.pack('<B', (d ^ k1 ^ k2) & 0xff)    print(flag)

运行后获取flag:

CTFPLUS逆向每周练习-2026.08.31-2026.09.06

原文始发于微信公众号(SPEEDCoding):CTFPLUS逆向每周练习-2026.08.31-2026.09.06

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2026年9月1日23:18:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CTFPLUS逆向每周练习-2026.08.31-2026.09.06http://cn-sec.com/archives/5416111.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息