本文介绍了CTFPLUS逆向每周练习的两道题目。第一道涉及UPX壳被修改后脱壳失败,通过修复节区名称成功脱壳,并分析ChaCha20加密流程,动态调试提取交换后的密钥流进行异或解密。第二道为Java打包exe程序逆向,通过分析释放的jar包,使用z3求解器求解线性方程组获得flag。
1.CTF+Binary-[Re挑战题]-Ferris's Secret Scuttle[Score3]
使用DIE查看,发现是UPX壳:

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

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

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

使用IDA打开找到主函数:

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

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

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

再交换密钥流的首尾:

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

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

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

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] = tmpflag = ''index = 0for c in cipher:flag += chr(key_stream[index] ^ c)index += 1print(flag)
运行后输出flag:

2.CTF+Binary-[Re挑战题]-JvavMaster[Score5]
下载题目附件,是一个Java打包的exe程序,拖入IDA中查看发现有exe4j的关键词:

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

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

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

这里MATRIX是一个用自定义的random方法生成的伪随机数常量矩阵,key这里总共12个数,都是0-255范围,需要满足这里定义的线性方程,使用python的z3求解器进行求解:
from z3 import *def get_random(seed):x = seedx ^= x << 11x ^= x >> 4x ^= x << 5x ^= x >> 14return 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:

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

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

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

这里直接写一个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:

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

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

先看sub_400810:

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

这里会修改程序执行的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_STARTif off < 0 or off >= len(bitmap) * 8:return 0return (bitmap[off >> 3] >> (off & 7)) & 1def prev_second_one(bitmap, addr):cnt = 0a = addr - 1while a >= TEXT_START:if get_bit(bitmap, a):cnt += 1if cnt == 2:return aa -= 1return 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”:continuevaddr = seg[”p_vaddr”]memsz = seg[”p_memsz”]page_start = vaddr & ~0xfffpage_end = (vaddr + memsz + 0xfff) & ~0xfffuc.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 = inpself.input_ptr = 0self.trace = []self.libc_calls = []self.output = []self.trap_flag = Trueself.just_jumped = Falseself.canary = 0x1122334455667788self.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 = 0x700000000000self.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_ripreturn ripdef 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# 下一条(目标指令)直接执行returnself.just_jumped = Falsecode = uc.mem_read(address, size)if code and code[0] == 0xE8:# call rel32insn = 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) & 0xFFFFFFFFFFFFFFFFself.handle_libc(address, target)returntry: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 + 5if 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, rsin = min(len(self.input_data) - self.input_ptr, size - 1)chunk = self.input_data[self.input_ptr:self.input_ptr + n]self.input_ptr += nself.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 Falsedef read_cstr(self, addr):out = bytearray()while True:b = self.uc.mem_read(addr, 1)if not b or b[0] == 0:breakout.append(b[0])addr += 1return 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文件查看:

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

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

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

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


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

再结合汇编执行流里面调用的key,顺序是rsp+0x2c、rsp+0xc、rsp+0x10、rsp+0x14,对照一下上面的入栈指令:
key = [0xDEAD, 0xBEEF, 0x5A7D, 0xC0FF]
再看一下密文比较的代码段:

提取一下做比较的寄存器,一共10个4字节,40字节,跟输入字符串比较的长度0x28对应:
r9d, 2973BD37hr10d, 8662435hr11d, 0EEF6889hebx, 0AE421991hebp, 0D7B702CFhr8d, 3B98E6E6hedx, 352ADCCFhecx, 0BFC393ABhesi, 0B3C20088hedi, 1BA99AA3h
然后去指令执行流的文档里面随便找一轮进行分析,p=0(p是输入字符串按照每4字节切分的切片index)的时候是r9d:

p=1的时候是edi:

p=2的时候是esi:

p=3的时候是ecx:

p=4的时候是edx:

p=5的时候是r8d:

p=6的时候是ebx:

p=7的时候是ebp:

p=8的时候是r11d:

p=9的时候是r10d:

所以正确的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 =DELTA = 0x9E3779B9def xxtea_dec(v, k, delta=DELTA):n = len(v)if n < 2: return vr = 6 + 52 // ns = (r + 1) * delta & 0xfffffffffor _ in range(r):s = (s - delta) & 0xffffffffe = (s >> 2) & 3for 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) & 0xffffffffreturn vdecrypt = xxtea_dec(cipher, key)print(decrypt)flag_bytes = b””.join(struct.pack(”<I”, x) for x in decrypt)print(flag_bytes)
运行后成功输出flag:

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

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

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

写个c程序(python速度比较慢)来爆破获得正确的key值:
intmain(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

验证一下:

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

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

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

逐字节跟0x99异或即可,然后再跟明文加密并编码后进行比较,写个python解密脚本:
import structimport base64if __name__ == '__main__':step1_const = 5 + 3 + 9 * 4cipher =[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))) & 0xffflag += struct.pack('<B', (d ^ xor_key) & 0xff)index += 1print(flag)
运行后发现结果不对:

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

可以看到实际上还有一轮循环异或,重新修改python脚本:
import structimport base64if __name__ == '__main__':step1_const = 5 + 3 + 9 * 4cipher =[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))) & 0xffk2 = ((k1 >> 4) | ((k1 << 4) & 0xff)) & 0xffflag += struct.pack('<B', (d ^ k1 ^ k2) & 0xff)print(flag)
运行后获取flag:

原文始发于微信公众号(SPEEDCoding):CTFPLUS逆向每周练习-2026.08.31-2026.09.06
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-


评论