【wp】2020XCTF_逆向

前几天的XCTF最后一场终于打完了,三场比赛下来对逆向部分的大概感觉是从第一场的啥都不会做到后来的终于能有参与度了TvT,至少后两场的题目都是pc逆向,虽然特殊架构但好歹能做(tcl

本文是三场XCTF所有逆向题目的wp+复现整理。赛中拿到了7/10的flag,很多是跟着队里的大佬做出来的(tqltql),这边就试着独立复现一下,至少打完应该长长记性(

官方wp出了,借助wp提供的思路复现填坑√。(9/10)

1220那场的apk逆向还没复现完,因为博客要更新点配置所以就先放上来了qvq。

比赛官网:XCTF高校网络安全专题挑战赛

官方wp:

  1. XCTF高校网络安全专题挑战赛-华为云专场 官方Writeup - XCTF社区
  2. XCTF高校网络安全专题挑战赛-鲲鹏计算专场 官方Writeup - XCTF社区
  3. XCTF高校网络安全专题挑战赛-HarmonyOS和HMS专场 官方Writeup - XCTF社区

[12.20] 华为云专场

Weird_lua

根据官方wp,说是改了loadbyte和opcode顺序。这里假装没看过wp,看能不能分析出怎么看出来是这里魔改的。

首先看附件是luacheck_license_out.lua,可以用file分别看一下

image-20210225152906252

image-20210225152915828

单独运行./lua,可以看到版本号是5.3.3

image-20210225160651288

这里运行目标文件的方式是./lua ./check_license_out.lua

image-20210225153920925

感觉这个.lua文件实际可能是.luac文件,即lua字节码文件。

于是先随便写一个lua源码(除了不知名check以外,大致对目标文件check_license_out.lua进行复刻),用v5.3.3的lua(可以在lua官网Lua: download下载)编译成字节码,看看正常版和魔改版两个文件的区别。

-- test.lua function check(s) local len=#s if len==42 then return "Check!" else return "length error" end end io.write("input: ") str=io.read() print(check(str))

lua test.lua运行:(注意这里是用正常的lua解释器而不是题目给的)

image-20210225154050280

然后用luac -o test.luac test.lua得到test.luac字节码文件,然后把check_license_out.lua(上)和test.luac(下)用010Editor打开对比十六进制码。

image-20210225154521444

image-20210225154542779

可以看到,除了最开始的一字节为"1B"和"1C"以外,其余文件头中不同的字节都相差较大,感觉是某个特定的换算关系,位置看起来却没什么规律。

定位到lua-5.3.3/src文件夹中,有两个执行luac dump相关操作的头文件,分别是ldump.h(save precompiled Lua chunks)和lundump.h(load precompiled Lua chunks),这里猜测是用于load的lundump.c被魔改。

image-20210302235800922

从这里(类似于主函数的函数)开始是整个的load步骤,可以看到上面对比中第一个差距比较大的字节是在0004h的位置,应该是在checkHeader部分,跟进这个checkHeader()函数。

image-20210303000036934

237行是check文件头的"\x1bLua"部分(题目开头0x1c这个很容易看出就是硬改),而下面开始是check LUAC_VERSION即lua的版本号,本来应该是0x53,但是题目文件对应位置是0xAC,而且往后对照可以看到,不同字节部分都用了LoadByte()函数,猜测是在加载字节的时候有魔改,至于具体怎么魔改那可能得靠直觉了吧(。),大概能靠猜?(或者看lua文件的反编译= =

在官方wp里,魔改的具体信息已经给了:

static lu_byte LoadByte (LoadState *S) { lu_byte x; LoadVar(S, x); //x ^= 0xff; 此行为魔改部分 return x; }

跟着官方wp走不通的思路

本部分可跳过,最后因为刚刚接触lua,对这个语言不熟悉,卡在创造不出帖子里提到的 “用于 dump 的函数,尽量覆盖所有的指令操作,否则只能提取出已有的指令” 的lua函数,帖子里的test无法在5.3版本的lua中提取出所有字节码,需要自己动手创造(。

因为这里LoadByte的魔改会影响到下一步的opcode顺序还原,所以要先把这些错误字节xor回去。

又因为LoadByte的更改只影响到部分字节,我们并不清楚具体哪些字节遭到了祸害,所以想到了一个比较笨的方法就是重新编译一个lua解释器,这样我们就有了opcode顺序魔改前的解释器和魔改后的解释器(题目所给的)。

(如果有师傅有更好的办法求告知><)

将之前的lua-5.3.3文件夹复制到一个新路径,cd到文件夹中,执行make clean将文件中上一次的编译临时文件清空。其实理论上说不复制出来也应该不会影响到原来的lua,但为了保险起见还是这么干吧。

然后把lua-5.3.3/src/ldump.c(注意文件名!!)的52-56行即DumpByte()函数部分改为:

static void DumpByte (int y, DumpState *D) { lu_byte x = (lu_byte)y; x^=0xff;//增加此行 DumpVar(x, D); }

保存,退回到lua-5.3.3根目录下,在WSL中用make linux编译。

至于为什么是改ldump.c而不是改官方wp指定魔改的lundump.c,因为ldump.c的作用是保存字节码,而lundump.c的作用是加载字节码。这里参照[原创]用 Lua 简单还原 OpCode 顺序文中的方法进行还原。

帖子里的lua版本为v5.1.5,对这里v5.3.3并不能直接用(opcode有增加+有些5.3中新增的指令),需要对源码(Check_OpCode/check_opcode.lua)做一些修改,修改后的文件如下。

-- check_opcode.lua local bit = require('bit') local test = require('test') -- 加载用正常 lua 的 dump 文件 local fp = io.open("test.luac","rb") local ori_data = fp:read("*all") fp:close() -- print('data len '.. #data) -- print('ori_data len ' .. #ori_data) local ori_op_name = { "MOVE", "LOADK", "LOADKX", "LOADBOOL", "LOADNIL", "GETUPVAL", "GETTABUP", "GETTABLE", "SETTABUP", "SETUPVAL", "SETTABLE", "NEWTABLE", "SELF", "ADD", "SUB", "MUL", "MOD", "POW", "DIV", "IDIV", "BAND", "BOR", "BXOR", "SHL", "SHR", "UNM", "BNOT", "NOT", "LEN", "CONCAT", "JMP", "EQ", "LT", "LE", "TEST", "TESTSET", "CALL", "TAILCALL", "RETURN", "FORLOOP", "FORPREP", "TFORCALL", "TFORLOOP", "SETLIST", "CLOSURE", "VARARG", "EXTRAARG", } local data = string.dump(test) -- dump local new_op = {} -- 用目标 lua 和正常 lua 的 dump 数据对比 for i = 2, #data do local by_ori = string.byte(ori_data,i) local by_new = string.byte(data,i) if by_ori ~= by_new then -- 字节码被xor 0xff处理 local op_name = ori_op_name[by_ori + 1] local op_idx = by_new -- if by_ori>47 then -- op_name = ori_op_name[(by_ori~0xff) + 1] -- op_idx = (by_new~0xff) -- end if op_name~=nil then if new_op[op_name]~=nil and new_op[op_name]~=op_idx then print(op_name,new_op[op_name]) end new_op[op_name] = op_idx end end end print("old \t new \t name") local cnt=0 for idx, op_name in pairs(ori_op_name) do local tmp = '' if new_op[op_name] ~= nil then tmp = new_op[op_name] cnt=cnt+1 end print((idx - 1) .. "\t" .. tmp .. "\t" .. op_name ) end print(cnt)

将题目附件的lua重命名为lua_problem并复制到Check_OpCode文件夹中,同时将刚刚xor了字节的lua-5.3.3/src/lua也复制过来。

image-20210303223220203

P.S. 由于帖子中给出的test.lua与前文中最开始用到的lua文件重名,故以下test.lua均代表为帖子中的lua文件,前文中的test.lua不再被引用。

依次在WSL中运行:

./lua dump_test_luac.lua ./lua_problem check_opcode.lua

(然后发现写不出能覆盖所有字节码的test(),遂放弃该思路。

自创思路

我们知道在lua源码里,opcode是按顺序存放在lopcodes.hlopcodes.c里的,故顺序魔改其实可以追踪lua解释器在ida中的反编译即可。(这里的lua就是题目给的那个。

源码中的opcode处:

image-20210304125916447

image-20210304125958675

lua加载进ida中,用shift+F12提取字符串,并用ctrl+F搜索“MOVE”

image-20210304130048988

双击,按x找到交叉引用

image-20210304130133374

image-20210304130146457

这里就是魔改后的opcode顺序!

那么我们现在就掌握了所有魔改的信息,开始恢复还原。

这里用到的是著名的lua反汇编/反编译项目viruscamp/luadec: Lua Decompiler for lua 5.1 , 5.2 and 5.3,由于lua解释器被魔改(xor 0xff和opcode顺序),所以我们需要在原安装步骤的基础上做些调整。

git clone https://github.com/viruscamp/luadec cd luadec git submodule update --init lua-5.3 cd lua-5.3 ###### 此处在make之前需要调整源码! make linux cd ../luadec make LUAVER=5.3

调整源码如下:

/* luadec/lua-5.3/src/lopcodes.h 原line165-232 */ typedef enum { OP_MOVE, OP_LOADK, OP_LOADKX, OP_LOADBOOL, OP_LOADNIL, OP_GETUPVAL, OP_LT, OP_RETURN, OP_GETTABLE, OP_TESTSET, OP_SELF, OP_NEWTABLE, OP_LE, OP_ADD, OP_SUB, OP_MUL, OP_MOD, OP_POW, OP_DIV, OP_IDIV, OP_BAND, OP_BOR, OP_BXOR, OP_SHL, OP_SHR, OP_UNM, OP_BNOT, OP_NOT, OP_LEN, OP_GETTABUP, OP_JMP, OP_TEST, OP_SETTABUP, OP_TFORCALL, OP_FORPREP, OP_EXTRAARG, OP_CALL, OP_FORLOOP, OP_SETLIST, OP_CONCAT, OP_TAILCALL, OP_TFORLOOP, OP_VARARG, OP_SETTABLE, OP_SETUPVAL, OP_CLOSURE, OP_EQ, } OpCode;
/* luadec/lua-5.3/src/lopcodes.c 原line20-69 */ LUAI_DDEF const char *const luaP_opnames[NUM_OPCODES+1] = { "MOVE", "LOADK", "LOADKX", "LOADBOOL", "LOADNIL", "GETUPVAL", "LT", "RETURN", "GETTABLE", "TESTSET", "SELF", "NEWTABLE", "LE", "ADD", "SUB", "MUL", "MOD", "POW", "DIV", "IDIV", "BAND", "BOR", "BXOR", "SHL", "SHR", "UNM", "BNOT", "NOT", "LEN", "GETTABUP", "JMP", "TEST", "SETTABUP", "TFORCALL", "FORPREP", "EXTRAARG", "CALL", "FORLOOP", "SETLIST", "CONCAT", "TAILCALL", "TFORLOOP", "VARARG", "SETTABLE", "SETUPVAL", "CLOSURE", "EQ", NULL };
/* luadec/lua-5.3/src/lundump.c 原line60-64 */ static lu_byte LoadByte (LoadState *S) { lu_byte x; LoadVar(S, x); x^=0xff; return x; }
/* luadec/lua-5.3/src/Makefile 原line9 */ CC= gcc -std=gnu99 -m32 //因为题目所给的lua解释器为32位
/* luadec/luadec/Makefile 原line16 */ CC= gcc -m32 //同上

修改完源码后接着make。

走完流程后把check_license_out.lua的第一个字节改为"\x1b",以对上LUA_SIGNATURE,并复制到luadec/luadec文件夹中。

image-20210304133241178

然后在luadec/luadec下运行:

./luadec -dis check_license_out.lua > discheck

(如wp所说,反编译会报错,只能反汇编,估计是乱魔改的原因哈哈哈哈。

得到反汇编的discheck。

; Disassembled using luadec 2.2 rev: 895d923 for Lua 5.3 from https://github.com/viruscamp/luadec ; Command line: -dis check_license_out.lua ; Function: 0 ; Defined at line: 0 ; #Upvalues: 1 ; #Parameters: 0 ; Is_vararg: 2 ; Max Stack Size: 53 0 [-]: __eq R0 0 ; R0 := closure(Function #0_0) 1 [-]: SETTABUP U0 K0 R0 ; U0["to_v"] := R0 2 [-]: NEWTABLE R0 21 0 ; R0 := {} (size = 21,0) 3 [-]: LOADK R1 K1 ; R1 := 172 4 [-]: LOADK R2 K2 ; R2 := 25 5 [-]: LOADK R3 K3 ; R3 := 60 6 [-]: LOADK R4 K4 ; R4 := 95 7 [-]: LOADK R5 K5 ; R5 := 5 8 [-]: LOADK R6 K6 ; R6 := 27 9 [-]: LOADK R7 K7 ; R7 := 49 10 [-]: LOADK R8 K8 ; R8 := 58 11 [-]: LOADK R9 K9 ; R9 := 171 12 [-]: LOADK R10 K5 ; R10 := 5 13 [-]: LOADK R11 K10 ; R11 := 253 14 [-]: LOADK R12 K11 ; R12 := 45 15 [-]: LOADK R13 K12 ; R13 := 87 16 [-]: LOADK R14 K13 ; R14 := 246 17 [-]: LOADK R15 K14 ; R15 := 197 18 [-]: LOADK R16 K15 ; R16 := 12 19 [-]: LOADK R17 K16 ; R17 := 97 20 [-]: LOADK R18 K17 ; R18 := 234 21 [-]: LOADK R19 K18 ; R19 := 159 22 [-]: LOADK R20 K19 ; R20 := 119 23 [-]: LOADK R21 K20 ; R21 := 157 24 [-]: LOADK R22 K21 ; R22 := 169 25 [-]: LOADK R23 K22 ; R23 := 121 26 [-]: LOADK R24 K23 ; R24 := 54 27 [-]: LOADK R25 K24 ; R25 := 242 28 [-]: (null) R0 25 1 ; R0[0] to R0[24] := R1 to R25 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=0, b=25, c=1, FPF=50 29 [-]: NEWTABLE R1 21 0 ; R1 := {} (size = 21,0) 30 [-]: LOADK R2 K25 ; R2 := 94 31 [-]: LOADK R3 K26 ; R3 := 117 32 [-]: LOADK R4 K27 ; R4 := 57 33 [-]: LOADK R5 K28 ; R5 := 37 34 [-]: LOADK R6 K23 ; R6 := 54 35 [-]: LOADK R7 K29 ; R7 := 110 36 [-]: LOADK R8 K30 ; R8 := 15 37 [-]: LOADK R9 K31 ; R9 := 223 38 [-]: LOADK R10 K32 ; R10 := 163 39 [-]: LOADK R11 K33 ; R11 := 133 40 [-]: LOADK R12 K34 ; R12 := 99 41 [-]: LOADK R13 K35 ; R13 := 237 42 [-]: LOADK R14 K36 ; R14 := 8 43 [-]: LOADK R15 K37 ; R15 := 128 44 [-]: LOADK R16 K6 ; R16 := 27 45 [-]: LOADK R17 K23 ; R17 := 54 46 [-]: LOADK R18 K38 ; R18 := 233 47 [-]: LOADK R19 K39 ; R19 := 181 48 [-]: LOADK R20 K24 ; R20 := 242 49 [-]: LOADK R21 K40 ; R21 := 55 50 [-]: LOADK R22 K41 ; R22 := 230 51 [-]: LOADK R23 K42 ; R23 := 62 52 [-]: LOADK R24 K43 ; R24 := 42 53 [-]: LOADK R25 K44 ; R25 := 252 54 [-]: LOADK R26 K45 ; R26 := 116 55 [-]: (null) R1 25 1 ; R1[0] to R1[24] := R2 to R26 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=1, b=25, c=1, FPF=50 56 [-]: NEWTABLE R2 48 0 ; R2 := {} (size = 48,0) 57 [-]: LOADK R3 K46 ; R3 := 81 58 [-]: LOADK R4 K47 ; R4 := 138 59 [-]: LOADK R5 K48 ; R5 := 85 60 [-]: LOADK R6 K49 ; R6 := 142 61 [-]: LOADK R7 K50 ; R7 := 185 62 [-]: LOADK R8 K51 ; R8 := 35 63 [-]: LOADK R9 K52 ; R9 := 229 64 [-]: LOADK R10 K53 ; R10 := 83 65 [-]: LOADK R11 K36 ; R11 := 8 66 [-]: LOADK R12 K54 ; R12 := 225 67 [-]: LOADK R13 K55 ; R13 := 92 68 [-]: LOADK R14 K31 ; R14 := 223 69 [-]: LOADK R15 K56 ; R15 := 222 70 [-]: LOADK R16 K57 ; R16 := 47 71 [-]: LOADK R17 K58 ; R17 := 182 72 [-]: LOADK R18 K59 ; R18 := 158 73 [-]: LOADK R19 K60 ; R19 := 17 74 [-]: LOADK R20 K61 ; R20 := 74 75 [-]: LOADK R21 K62 ; R21 := 34 76 [-]: LOADK R22 K63 ; R22 := 100 77 [-]: LOADK R23 K64 ; R23 := 43 78 [-]: LOADK R24 K65 ; R24 := 103 79 [-]: LOADK R25 K66 ; R25 := 102 80 [-]: LOADK R26 K67 ; R26 := 147 81 [-]: LOADK R27 K35 ; R27 := 237 82 [-]: LOADK R28 K68 ; R28 := 88 83 [-]: LOADK R29 K69 ; R29 := 73 84 [-]: LOADK R30 K70 ; R30 := 28 85 [-]: LOADK R31 K71 ; R31 := 224 86 [-]: LOADK R32 K72 ; R32 := 23 87 [-]: LOADK R33 K73 ; R33 := 44 88 [-]: LOADK R34 K74 ; R34 := 40 89 [-]: LOADK R35 K75 ; R35 := 154 90 [-]: LOADK R36 K76 ; R36 := 127 91 [-]: LOADK R37 K77 ; R37 := 16 92 [-]: LOADK R38 K21 ; R38 := 169 93 [-]: LOADK R39 K78 ; R39 := 160 94 [-]: LOADK R40 K79 ; R40 := 118 95 [-]: LOADK R41 K80 ; R41 := 51 96 [-]: LOADK R42 K81 ; R42 := 194 97 [-]: LOADK R43 K82 ; R43 := 31 98 [-]: LOADK R44 K83 ; R44 := 68 99 [-]: LOADK R45 K84 ; R45 := 89 100 [-]: LOADK R46 K85 ; R46 := 65 101 [-]: LOADK R47 K86 ; R47 := 162 102 [-]: LOADK R48 K87 ; R48 := 13 103 [-]: LOADK R49 K88 ; R49 := 141 104 [-]: LOADK R50 K89 ; R50 := 0 105 [-]: LOADK R51 K90 ; R51 := 244 106 [-]: LOADK R52 K19 ; R52 := 119 107 [-]: (null) R2 50 1 ; R2[0] to R2[49] := R3 to R52 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=2, b=50, c=1, FPF=50 108 [-]: LOADK R3 K91 ; R3 := 161 109 [-]: LOADK R4 K92 ; R4 := 198 110 [-]: LOADK R5 K93 ; R5 := 228 111 [-]: LOADK R6 K4 ; R6 := 95 112 [-]: LOADK R7 K94 ; R7 := 10 113 [-]: LOADK R8 K95 ; R8 := 78 114 [-]: LOADK R9 K28 ; R9 := 37 115 [-]: LOADK R10 K22 ; R10 := 121 116 [-]: LOADK R11 K96 ; R11 := 236 117 [-]: LOADK R12 K97 ; R12 := 59 118 [-]: LOADK R13 K3 ; R13 := 60 119 [-]: LOADK R14 K98 ; R14 := 91 120 [-]: LOADK R15 K99 ; R15 := 146 121 [-]: LOADK R16 K100 ; R16 := 46 122 [-]: LOADK R17 K101 ; R17 := 77 123 [-]: LOADK R18 K102 ; R18 := 218 124 [-]: LOADK R19 K103 ; R19 := 66 125 [-]: LOADK R20 K104 ; R20 := 200 126 [-]: LOADK R21 K105 ; R21 := 61 127 [-]: LOADK R22 K106 ; R22 := 241 128 [-]: LOADK R23 K107 ; R23 := 70 129 [-]: LOADK R24 K40 ; R24 := 55 130 [-]: LOADK R25 K108 ; R25 := 39 131 [-]: LOADK R26 K109 ; R26 := 227 132 [-]: LOADK R27 K43 ; R27 := 42 133 [-]: LOADK R28 K110 ; R28 := 2 134 [-]: LOADK R29 K111 ; R29 := 231 135 [-]: LOADK R30 K112 ; R30 := 235 136 [-]: LOADK R31 K113 ; R31 := 122 137 [-]: LOADK R32 K114 ; R32 := 135 138 [-]: LOADK R33 K115 ; R33 := 152 139 [-]: LOADK R34 K116 ; R34 := 137 140 [-]: LOADK R35 K117 ; R35 := 173 141 [-]: LOADK R36 K118 ; R36 := 232 142 [-]: LOADK R37 K119 ; R37 := 101 143 [-]: LOADK R38 K120 ; R38 := 75 144 [-]: LOADK R39 K38 ; R39 := 233 145 [-]: LOADK R40 K121 ; R40 := 21 146 [-]: LOADK R41 K44 ; R41 := 252 147 [-]: LOADK R42 K30 ; R42 := 15 148 [-]: LOADK R43 K33 ; R43 := 133 149 [-]: LOADK R44 K122 ; R44 := 111 150 [-]: LOADK R45 K123 ; R45 := 205 151 [-]: LOADK R46 K27 ; R46 := 57 152 [-]: LOADK R47 K124 ; R47 := 132 153 [-]: LOADK R48 K125 ; R48 := 187 154 [-]: LOADK R49 K126 ; R49 := 96 155 [-]: LOADK R50 K7 ; R50 := 49 156 [-]: LOADK R51 K127 ; R51 := 124 157 [-]: LOADK R52 K128 ; R52 := 86 158 [-]: (null) R2 50 2 ; R2[50] to R2[99] := R3 to R52 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=2, b=50, c=2, FPF=50 159 [-]: LOADK R3 K129 ; R3 := 19 160 [-]: LOADK R4 K130 ; R4 := 188 161 [-]: LOADK R5 K131 ; R5 := 80 162 [-]: LOADK R6 K132 ; R6 := 213 163 [-]: LOADK R7 K133 ; R7 := 106 164 [-]: LOADK R8 K134 ; R8 := 214 165 [-]: LOADK R9 K135 ; R9 := 203 166 [-]: LOADK R10 K136 ; R10 := 177 167 [-]: LOADK R11 K137 ; R11 := 56 168 [-]: LOADK R12 K138 ; R12 := 104 169 [-]: LOADK R13 K139 ; R13 := 82 170 [-]: LOADK R14 K29 ; R14 := 110 171 [-]: LOADK R15 K140 ; R15 := 196 172 [-]: LOADK R16 K141 ; R16 := 113 173 [-]: LOADK R17 K142 ; R17 := 155 174 [-]: LOADK R18 K143 ; R18 := 170 175 [-]: LOADK R19 K144 ; R19 := 150 176 [-]: LOADK R20 K26 ; R20 := 117 177 [-]: LOADK R21 K145 ; R21 := 26 178 [-]: LOADK R22 K146 ; R22 := 140 179 [-]: LOADK R23 K147 ; R23 := 144 180 [-]: LOADK R24 K148 ; R24 := 11 181 [-]: LOADK R25 K1 ; R25 := 172 182 [-]: LOADK R26 K149 ; R26 := 67 183 [-]: LOADK R27 K150 ; R27 := 209 184 [-]: LOADK R28 K151 ; R28 := 125 185 [-]: LOADK R29 K23 ; R29 := 54 186 [-]: LOADK R30 K8 ; R30 := 58 187 [-]: LOADK R31 K37 ; R31 := 128 188 [-]: LOADK R32 K152 ; R32 := 204 189 [-]: LOADK R33 K153 ; R33 := 186 190 [-]: LOADK R34 K154 ; R34 := 199 191 [-]: LOADK R35 K155 ; R35 := 189 192 [-]: LOADK R36 K156 ; R36 := 208 193 [-]: LOADK R37 K157 ; R37 := 239 194 [-]: LOADK R38 K158 ; R38 := 143 195 [-]: LOADK R39 K159 ; R39 := 249 196 [-]: LOADK R40 K13 ; R40 := 246 197 [-]: LOADK R41 K160 ; R41 := 1 198 [-]: LOADK R42 K161 ; R42 := 139 199 [-]: LOADK R43 K162 ; R43 := 33 200 [-]: LOADK R44 K12 ; R44 := 87 201 [-]: LOADK R45 K163 ; R45 := 64 202 [-]: LOADK R46 K45 ; R46 := 116 203 [-]: LOADK R47 K164 ; R47 := 84 204 [-]: LOADK R48 K165 ; R48 := 254 205 [-]: LOADK R49 K166 ; R49 := 126 206 [-]: LOADK R50 K167 ; R50 := 202 207 [-]: LOADK R51 K168 ; R51 := 148 208 [-]: LOADK R52 K169 ; R52 := 76 209 [-]: (null) R2 50 3 ; R2[100] to R2[149] := R3 to R52 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=2, b=50, c=3, FPF=50 210 [-]: LOADK R3 K170 ; R3 := 247 211 [-]: LOADK R4 K171 ; R4 := 115 212 [-]: LOADK R5 K172 ; R5 := 109 213 [-]: LOADK R6 K173 ; R6 := 3 214 [-]: LOADK R7 K174 ; R7 := 238 215 [-]: LOADK R8 K175 ; R8 := 114 216 [-]: LOADK R9 K176 ; R9 := 156 217 [-]: LOADK R10 K177 ; R10 := 195 218 [-]: LOADK R11 K32 ; R11 := 163 219 [-]: LOADK R12 K18 ; R12 := 159 220 [-]: LOADK R13 K178 ; R13 := 52 221 [-]: LOADK R14 K179 ; R14 := 36 222 [-]: LOADK R15 K180 ; R15 := 245 223 [-]: LOADK R16 K181 ; R16 := 240 224 [-]: LOADK R17 K182 ; R17 := 63 225 [-]: LOADK R18 K183 ; R18 := 153 226 [-]: LOADK R19 K184 ; R19 := 166 227 [-]: LOADK R20 K185 ; R20 := 167 228 [-]: LOADK R21 K186 ; R21 := 175 229 [-]: LOADK R22 K187 ; R22 := 9 230 [-]: LOADK R23 K188 ; R23 := 151 231 [-]: LOADK R24 K9 ; R24 := 171 232 [-]: LOADK R25 K189 ; R25 := 216 233 [-]: LOADK R26 K190 ; R26 := 207 234 [-]: LOADK R27 K191 ; R27 := 179 235 [-]: LOADK R28 K192 ; R28 := 72 236 [-]: LOADK R29 K193 ; R29 := 176 237 [-]: LOADK R30 K194 ; R30 := 48 238 [-]: LOADK R31 K195 ; R31 := 178 239 [-]: LOADK R32 K20 ; R32 := 157 240 [-]: LOADK R33 K196 ; R33 := 20 241 [-]: LOADK R34 K39 ; R34 := 181 242 [-]: LOADK R35 K197 ; R35 := 149 243 [-]: LOADK R36 K198 ; R36 := 53 244 [-]: LOADK R37 K199 ; R37 := 184 245 [-]: LOADK R38 K200 ; R38 := 4 246 [-]: LOADK R39 K201 ; R39 := 136 247 [-]: LOADK R40 K202 ; R40 := 165 248 [-]: LOADK R41 K203 ; R41 := 217 249 [-]: LOADK R42 K204 ; R42 := 50 250 [-]: LOADK R43 K205 ; R43 := 190 251 [-]: LOADK R44 K206 ; R44 := 191 252 [-]: LOADK R45 K207 ; R45 := 192 253 [-]: LOADK R46 K208 ; R46 := 193 254 [-]: LOADK R47 K209 ; R47 := 98 255 [-]: LOADK R48 K210 ; R48 := 215 256 [-]: LOADK R49 K42 ; R49 := 62 257 [-]: LOADK R50 K211 ; R50 := 112 258 [-]: LOADK R51 K212 ; R51 := 38 259 [-]: LOADK R52 K213 ; R52 := 90 260 [-]: (null) R2 50 4 ; R2[150] to R2[199] := R3 to R52 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=2, b=50, c=4, FPF=50 261 [-]: LOADK R3 K214 ; R3 := 123 262 [-]: LOADK R4 K215 ; R4 := 105 263 [-]: LOADK R5 K25 ; R5 := 94 264 [-]: LOADK R6 K216 ; R6 := 221 265 [-]: LOADK R7 K34 ; R7 := 99 266 [-]: LOADK R8 K217 ; R8 := 201 267 [-]: LOADK R9 K218 ; R9 := 206 268 [-]: LOADK R10 K219 ; R10 := 251 269 [-]: LOADK R11 K220 ; R11 := 14 270 [-]: LOADK R12 K221 ; R12 := 211 271 [-]: LOADK R13 K222 ; R13 := 220 272 [-]: LOADK R14 K223 ; R14 := 131 273 [-]: LOADK R15 K224 ; R15 := 212 274 [-]: LOADK R16 K225 ; R16 := 130 275 [-]: LOADK R17 K226 ; R17 := 134 276 [-]: LOADK R18 K10 ; R18 := 253 277 [-]: LOADK R19 K227 ; R19 := 120 278 [-]: LOADK R20 K228 ; R20 := 145 279 [-]: LOADK R21 K229 ; R21 := 18 280 [-]: LOADK R22 K230 ; R22 := 219 281 [-]: LOADK R23 K231 ; R23 := 79 282 [-]: LOADK R24 K232 ; R24 := 129 283 [-]: LOADK R25 K15 ; R25 := 12 284 [-]: LOADK R26 K233 ; R26 := 93 285 [-]: LOADK R27 K5 ; R27 := 5 286 [-]: LOADK R28 K234 ; R28 := 183 287 [-]: LOADK R29 K235 ; R29 := 107 288 [-]: LOADK R30 K236 ; R30 := 71 289 [-]: LOADK R31 K237 ; R31 := 226 290 [-]: LOADK R32 K238 ; R32 := 180 291 [-]: LOADK R33 K239 ; R33 := 24 292 [-]: LOADK R34 K17 ; R34 := 234 293 [-]: LOADK R35 K240 ; R35 := 7 294 [-]: LOADK R36 K241 ; R36 := 108 295 [-]: LOADK R37 K242 ; R37 := 174 296 [-]: LOADK R38 K243 ; R38 := 6 297 [-]: LOADK R39 K11 ; R39 := 45 298 [-]: LOADK R40 K244 ; R40 := 29 299 [-]: LOADK R41 K245 ; R41 := 32 300 [-]: LOADK R42 K246 ; R42 := 168 301 [-]: LOADK R43 K41 ; R43 := 230 302 [-]: LOADK R44 K14 ; R44 := 197 303 [-]: LOADK R45 K247 ; R45 := 41 304 [-]: LOADK R46 K2 ; R46 := 25 305 [-]: LOADK R47 K248 ; R47 := 255 306 [-]: LOADK R48 K249 ; R48 := 164 307 [-]: LOADK R49 K6 ; R49 := 27 308 [-]: LOADK R50 K250 ; R50 := 210 309 [-]: LOADK R51 K251 ; R51 := 248 310 [-]: LOADK R52 K16 ; R52 := 97 311 [-]: (null) R2 50 5 ; R2[200] to R2[249] := R3 to R52 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=2, b=50, c=5, FPF=50 312 [-]: LOADK R3 K252 ; R3 := 250 313 [-]: LOADK R4 K253 ; R4 := 22 314 [-]: LOADK R5 K24 ; R5 := 242 315 [-]: LOADK R6 K254 ; R6 := 243 316 [-]: LOADK R7 K255 ; R7 := 30 317 [-]: LOADK R8 K256 ; R8 := 69 318 [-]: (null) R2 6 6 ; R2[250] to R2[255] := R3 to R8 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=2, b=6, c=6, FPF=50 319 [-]: LOADK R3 K257 ; R3 := "encrypt" 320 [-]: __eq R4 1 ; R4 := closure(Function #0_1) 321 [-]: SETTABUP U0 R3 R4 ; U0[R3] := R4 322 [-]: LOADK R3 K258 ; R3 := "check_license" 323 [-]: __eq R4 2 ; R4 := closure(Function #0_2) 324 [-]: SETTABUP U0 R3 R4 ; U0[R3] := R4 325 [-]: LOADK R3 K259 ; R3 := "io" 326 [-]: GETTABUP R3 U0 R3 ; R3 := U0[R3] 327 [-]: LOADK R4 K260 ; R4 := "write" 328 [-]: GETTABLE R3 R3 R4 ; R3 := R3[R4] 329 [-]: LOADK R4 K261 ; R4 := "input: " 330 [-]: CALL R3 2 1 ; := R3(R4) 331 [-]: LOADK R3 K262 ; R3 := "license" 332 [-]: LOADK R4 K259 ; R4 := "io" 333 [-]: GETTABUP R4 U0 R4 ; R4 := U0[R4] 334 [-]: LOADK R5 K263 ; R5 := "read" 335 [-]: GETTABLE R4 R4 R5 ; R4 := R4[R5] 336 [-]: LOADK R5 K264 ; R5 := "*l" 337 [-]: CALL R4 2 2 ; R4 := R4(R5) 338 [-]: SETTABUP U0 R3 R4 ; U0[R3] := R4 339 [-]: LOADK R3 K258 ; R3 := "check_license" 340 [-]: GETTABUP R3 U0 R3 ; R3 := U0[R3] 341 [-]: LOADK R4 K262 ; R4 := "license" 342 [-]: GETTABUP R4 U0 R4 ; R4 := U0[R4] 343 [-]: CALL R3 2 1 ; := R3(R4) 344 [-]: RETURN R0 1 ; return ; Function: 0_0 ; Defined at line: 2 ; #Upvalues: 1 ; #Parameters: 1 ; Is_vararg: 0 ; Max Stack Size: 10 0 [-]: NEWTABLE R1 0 0 ; R1 := {} (size = 0,0) 1 [-]: LOADK R2 K0 ; R2 := 1 2 [-]: GETTABUP R3 U0 K1 ; R3 := U0["string"] 3 [-]: GETTABLE R3 R3 K2 ; R3 := R3["len"] 4 [-]: MOVE R4 R0 ; R4 := R0 5 [-]: CALL R3 2 2 ; R3 := R3(R4) 6 [-]: LOADK R4 K0 ; R4 := 1 7 [-]: FORPREP R2 8 ; R2 -= R4; pc += 8 (goto 16) 8 [-]: LEN R6 R1 ; R6 := #R1 9 [-]: ADD R6 R6 K0 ; R6 := R6 + 1 10 [-]: GETTABUP R7 U0 K1 ; R7 := U0["string"] 11 [-]: GETTABLE R7 R7 K3 ; R7 := R7["byte"] 12 [-]: MOVE R8 R0 ; R8 := R0 13 [-]: MOVE R9 R5 ; R9 := R5 14 [-]: CALL R7 3 2 ; R7 := R7(R8 to R9) 15 [-]: __mode R1 R6 R7 ; R1[R6] := R7 16 [-]: (null) R2 -9 ; R2 += R4; if R2 <= R3 then R5 := R2; PC += -9 , goto 8 end 17 [-]: RETURN R1 2 ; return R1 18 [-]: RETURN R0 1 ; return ; Function: 0_1 ; Defined at line: 15 ; #Upvalues: 4 ; #Parameters: 1 ; Is_vararg: 0 ; Max Stack Size: 10 0 [-]: GETTABUP R1 U0 K0 ; R1 := U0["to_v"] 1 [-]: MOVE R2 R0 ; R2 := R0 2 [-]: CALL R1 2 2 ; R1 := R1(R2) 3 [-]: NEWTABLE R2 0 0 ; R2 := {} (size = 0,0) 4 [-]: LOADK R3 K1 ; R3 := 1 5 [-]: LEN R4 R1 ; R4 := #R1 6 [-]: LOADK R5 K1 ; R5 := 1 7 [-]: FORPREP R3 10 ; R3 -= R5; pc += 10 (goto 18) 8 [-]: GETTABLE R7 R1 R6 ; R7 := R1[R6] 9 [-]: GETTABUP R8 U1 R6 ; R8 := U1[R6] 10 [-]: BXOR R7 R7 R8 ; R7 := R7 ~ R8 11 [-]: __mode R1 R6 R7 ; R1[R6] := R7 12 [-]: LEN R7 R2 ; R7 := #R2 13 [-]: ADD R7 R7 K1 ; R7 := R7 + 1 14 [-]: GETTABLE R8 R1 R6 ; R8 := R1[R6] 15 [-]: ADD R8 R8 K1 ; R8 := R8 + 1 16 [-]: GETTABUP R8 U2 R8 ; R8 := U2[R8] 17 [-]: __mode R2 R7 R8 ; R2[R7] := R8 18 [-]: (null) R3 -11 ; R3 += R5; if R3 <= R4 then R6 := R3; PC += -11 , goto 8 end 19 [-]: LOADK R3 K2 ; R3 := 0 20 [-]: LOADK R4 K1 ; R4 := 1 21 [-]: LEN R5 R1 ; R5 := #R1 22 [-]: LOADK R6 K1 ; R6 := 1 23 [-]: FORPREP R4 7 ; R4 -= R6; pc += 7 (goto 31) 24 [-]: GETTABLE R8 R2 R7 ; R8 := R2[R7] 25 [-]: GETTABUP R9 U3 R7 ; R9 := U3[R7] 26 [-]: __add 0 R8 R9 ; if R8 == R9 then goto 28 else goto 30 27 [-]: JMP R0 2 ; PC += 2 (goto 30) 28 [-]: ADD R3 R3 K1 ; R3 := R3 + 1 29 [-]: JMP R0 1 ; PC += 1 (goto 31) 30 [-]: ADD R3 R3 K3 ; R3 := R3 + 2 31 [-]: (null) R4 -8 ; R4 += R6; if R4 <= R5 then R7 := R4; PC += -8 , goto 24 end 32 [-]: LEN R4 R1 ; R4 := #R1 33 [-]: __add 1 R3 R4 ; if R3 ~= R4 then goto 35 else goto 39 34 [-]: JMP R0 4 ; PC += 4 (goto 39) 35 [-]: GETTABUP R4 U0 K4 ; R4 := U0["print"] 36 [-]: LOADK R5 K5 ; R5 := "bad" 37 [-]: CALL R4 2 1 ; := R4(R5) 38 [-]: JMP R0 3 ; PC += 3 (goto 42) 39 [-]: GETTABUP R4 U0 K4 ; R4 := U0["print"] 40 [-]: LOADK R5 K6 ; R5 := "good" 41 [-]: CALL R4 2 1 ; := R4(R5) 42 [-]: RETURN R0 1 ; return ; Function: 0_2 ; Defined at line: 38 ; #Upvalues: 1 ; #Parameters: 1 ; Is_vararg: 0 ; Max Stack Size: 3 0 [-]: LEN R1 R0 ; R1 := #R0 1 [-]: SETTABUP U0 K0 R1 ; U0["size"] := R1 2 [-]: GETTABUP R1 U0 K0 ; R1 := U0["size"] 3 [-]: __add 1 R1 K1 ; if R1 ~= 25 then goto 5 else goto 9 4 [-]: JMP R0 4 ; PC += 4 (goto 9) 5 [-]: GETTABUP R1 U0 K2 ; R1 := U0["print"] 6 [-]: LOADK R2 K3 ; R2 := "length error" 7 [-]: CALL R1 2 1 ; := R1(R2) 8 [-]: RETURN R0 1 ; return 9 [-]: GETTABUP R1 U0 K4 ; R1 := U0["encrypt"] 10 [-]: MOVE R2 R0 ; R2 := R0 11 [-]: CALL R1 2 1 ; := R1(R2) 12 [-]: RETURN R0 1 ; return

说是反汇编其实还是有一点反编译注释的吼。

根据注释来走就很容易理顺逻辑啦(可以参考深入理解 Lua 虚拟机_腾讯技术工程 - MdEditor),算法用python还原是:

# Function 0_0: to_v() # Function 0_1: encrypt() # Function 0_2: check_license() def to_v(s): return [ord(c) for c in s] def encrypt(s): global tb0,tb1,tb2 u1,u2,u3=tb0,tb2,tb1 #这个upvalue对应我也不知道是怎么看出来的,可能靠试吧 l1=to_v(s) size=len(l1) l2=[] for i in range(size): l1[i]^=u1[i] l2.append(u2[l1[i]+1]) r3=0 for i in range(size): if l2[i]==u3[i]: r3+=1 else: r3+=2 if r3!=size: print("bad") else: print("good") return def check_license(s): if len(s)!=25: print("length error") else: encrypt(s) return if __name__ == '__main__': tb0=[172, 25, 60, 95, 5, 27, 49, 58, 171, 5, 253, 45, 87, 246, 197, 12, 97, 234, 159, 119, 157, 169, 121, 54, 242] tb1=[94, 117, 57, 37, 54, 110, 15, 223, 163, 133, 99, 237, 8, 128, 27, 54, 233, 181, 242, 55, 230, 62, 42, 252, 116] tb2=[81, 138, 85, 142, 185, 35, 229, 83, 8, 225, 92, 223, 222, 47, 182, 158, 17, 74, 34, 100, 43, 103, 102, 147, 237, 88, 73, 28, 224, 23, 44, 40, 154, 127, 16, 169, 160, 118, 51, 194, 31, 68, 89, 65, 162, 13, 141, 0, 244, 119, 161, 198, 228, 95, 10, 78, 37, 121, 236, 59, 60, 91, 146, 46, 77, 218, 66, 200, 61, 241, 70, 55, 39, 227, 42, 2, 231, 235, 122, 135, 152, 137, 173, 232, 101, 75, 233, 21, 252, 15, 133, 111, 205, 57, 132, 187, 96, 49, 124, 86, 19, 188, 80, 213, 106, 214, 203, 177, 56, 104, 82, 110, 196, 113, 155, 170, 150, 117, 26, 140, 144, 11, 172, 67, 209, 125, 54, 58, 128, 204, 186, 199, 189, 208, 239, 143, 249, 246, 1, 139, 33, 87, 64, 116, 84, 254, 126, 202, 148, 76, 247, 115, 109, 3, 238, 114, 156, 195, 163, 159, 52, 36, 245, 240, 63, 153, 166, 167, 175, 9, 151, 171, 216, 207, 179, 72, 176, 48, 178, 157, 20, 181, 149, 53, 184, 4, 136, 165, 217, 50, 190, 191, 192, 193, 98, 215, 62, 112, 38, 90, 123, 105, 94, 221, 99, 201, 206, 251, 14, 211, 220, 131, 212, 130, 134, 253, 120, 145, 18, 219, 79, 129, 12, 93, 5, 183, 107, 71, 226, 180, 24, 234, 7, 108, 174, 6, 45, 29, 32, 168, 230, 197, 41, 25, 255, 164, 27, 210, 248, 97, 250, 22, 242, 243, 30, 69] print("input: ",end='') license=input() check_license(license)

就可以根据逻辑写出exp:

tb0=[172, 25, 60, 95, 5, 27, 49, 58, 171, 5, 253, 45, 87, 246, 197, 12, 97, 234, 159, 119, 157, 169, 121, 54, 242] tb1=[94, 117, 57, 37, 54, 110, 15, 223, 163, 133, 99, 237, 8, 128, 27, 54, 233, 181, 242, 55, 230, 62, 42, 252, 116] tb2=[81, 138, 85, 142, 185, 35, 229, 83, 8, 225, 92, 223, 222, 47, 182, 158, 17, 74, 34, 100, 43, 103, 102, 147, 237, 88, 73, 28, 224, 23, 44, 40, 154, 127, 16, 169, 160, 118, 51, 194, 31, 68, 89, 65, 162, 13, 141, 0, 244, 119, 161, 198, 228, 95, 10, 78, 37, 121, 236, 59, 60, 91, 146, 46, 77, 218, 66, 200, 61, 241, 70, 55, 39, 227, 42, 2, 231, 235, 122, 135, 152, 137, 173, 232, 101, 75, 233, 21, 252, 15, 133, 111, 205, 57, 132, 187, 96, 49, 124, 86, 19, 188, 80, 213, 106, 214, 203, 177, 56, 104, 82, 110, 196, 113, 155, 170, 150, 117, 26, 140, 144, 11, 172, 67, 209, 125, 54, 58, 128, 204, 186, 199, 189, 208, 239, 143, 249, 246, 1, 139, 33, 87, 64, 116, 84, 254, 126, 202, 148, 76, 247, 115, 109, 3, 238, 114, 156, 195, 163, 159, 52, 36, 245, 240, 63, 153, 166, 167, 175, 9, 151, 171, 216, 207, 179, 72, 176, 48, 178, 157, 20, 181, 149, 53, 184, 4, 136, 165, 217, 50, 190, 191, 192, 193, 98, 215, 62, 112, 38, 90, 123, 105, 94, 221, 99, 201, 206, 251, 14, 211, 220, 131, 212, 130, 134, 253, 120, 145, 18, 219, 79, 129, 12, 93, 5, 183, 107, 71, 226, 180, 24, 234, 7, 108, 174, 6, 45, 29, 32, 168, 230, 197, 41, 25, 255, 164, 27, 210, 248, 97, 250, 22, 242, 243, 30, 69] flag="" for i in range(25): flag+=chr(tb2.index(tb1[i])^tb0[i]) print(flag)

得到flag

image-20210304150822710

flag{th15_15_v3r7_c0mm3n}

divination[TO DO]

先用模拟器跑一下,发现是一个输入字符串判断的逻辑。

因为物理机里装了WSL2,所以用的是能兼容Hyper-V的BlueStacks模拟器。

image-20210221004556852

走流程,先用pxb1988/dex2jar: Tools to work with android .dex and java .class files这个工具把将apk解压后文件夹中的classes.dex文件转换成jar文件,再用Java Decompiler中的JD-GUI查看反编译出的java源代码。

然后可以看到MainActivity.class这里也有一句“大事不妙”,找到主函数。

image-20210221005218142

看了一下逻辑就是调用libdivination.so(在apk解压后的路径divination/lib/arm64-v8a/libdivination.so),只要返回值为真就行。

所以其实做安卓逆向看so硬逆也能逆出来hhh。

于是现在转去逆so。

image-20210221102702774

关键入口函数显而易见,就是这个跟包同名的函数。

image-20210221110434855

为了方便看反编译代码,按y把a1-a3三个参数的变量类型进行更改(ida无法自动识别。

image-20210221110645265

下边的函数也一目了然。

从后往前看,先拿到目标数组unk_3000:

image-20210221111251068


[12.23] 鲲鹏计算专场

mips

真·送分题,可惜当时要上课没来得及抢一血(下午2点放题绝了

老传统走迷宫

mips架构。

ida反编译以后可以看到

image-20201229125551113

v4是我们输入的字符串,很明显是迷宫逻辑,上下左右用wasd走,迷宫存在dword_100111F0里。

sub_10000744()这个初始函数是用来找起点用的(就是迷宫中3所在的地方,在后面可以看到3其实表示的是当前位置)。

image-20201229125822518

这里也可以看到应该有多个迷宫(dword_10011D10是用来表示第几个迷宫的,且<=2,一个迷宫有225个数)+一个迷宫宽为15=三个迷宫,每个迷宫为15*15。

然后就是下面的四个函数,随便挑一个出来(比如sub_10000D28())可以看到

image-20201229130146475

很明显是个往右走的函数,3表示当前位置,并把上一个当前位置标为1(可走路径)。并且可以看到终点是4,就是说我们要把每个迷宫从3走到4。

dump迷宫数组,写脚本打印迷宫:

aMap=[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 3, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0] for i in range(45): for j in range(15): if aMap[i*15+j]==0: tmp='*' elif aMap[i*15+j]==1: tmp='.' elif aMap[i*15+j]==3: tmp='@' else: tmp='#' print(tmp,end='') print() if i==14 or i==29: print()

可以看到打印出了三个迷宫,为了看得清楚所以选用几个特定字符打印。

.....********** .....*@*.****** .....*.*.****** .....*.*.****** .....*.*.....** .....*.*****.** .....*.*****.** .....*.*****..* .....*........* .....********#* ............... ............... ............... ............... ............... #sssssssddddddds ..************* ..*@*....****** ..*.****.****** ..*.****.****** ..*..***.....** ..*..*******.** ..*..*******.** ..*..*****....* ..*..*****.**.* ..*..*****.**** ..*......*.*..* ..*...........* ..***********#* ............... ............... #ssssssssssdddddddddds *************** *@..*********** ***.*...******* ***...*.******* ****.**.******* *..*.**.******* **...**.******* *******.******* *******....**** **********.**** **********.**** **********.**** **********....* *************.* *************#* #ddssddwddssssssdddssssdddss

走迷宫,然后把路径拼起来,根据提示转md5,get flag。

(有个疑惑哈,第二个迷宫理论上说就算是最短路也有多解?是题目出锅了还是我哪里看漏了= =

(再补一句,题目似乎甚至没要求最短路???神奇.jpg

image-20201229163439245

import hashlib s=b"sssssssdddddddsssssssssssddddddddddsddssddwddssssssdddssssdddss" print("flag{%s}"%hashlib.md5(s).hexdigest())

flag{999ea6aa6c365ab43eec2a0f0e5968d5}

pypy

把题目文件拖进ida,搜索字符串能看到

image-20201229164109092

猜测是pyinstaller打包的文件。

也就是这个题让我突然发现pyinstaller还能打包成elf的,于是比赛结束以后赶紧把之前总结的解包指南更新了:RE套路 - 关于pyinstaller打包文件的复原 | c10udlnk_Log

走流程解包,得到python源码。

image-20201229165610468

看到这种混淆变量名,果断替换成ida style变量名(。

放一下源码:

# uncompyle6 version 3.7.4 # Python bytecode 3.8 (3413) # Decompiled from: Python 2.7.18 (v2.7.18:8d21aa21f2, Apr 20 2020, 13:25:05) [MSC v.1500 64 bit (AMD64)] # Warning: this version of Python has problems handling the Python 3 "byte" type in constants properly. # Embedded file name: main.py # Compiled at: 1995-09-28 00:18:56 # Size of source mod 2**32: 257 bytes import random, codecs, sys, time, pygame from pygame.locals import * from collections import deque SCREEN_WIDTH = 600 SCREEN_HEIGHT = 480 SIZE = 20 LINE_WIDTH = 1 flag = 'flag{this is a fake flag}' SCOPE_X = (0, SCREEN_WIDTH // SIZE - 1) SCOPE_Y = (2, SCREEN_HEIGHT // SIZE - 1) FOOD_STYLE_LIST = [(10, (255, 100, 100)), (20, (100, 255, 100)), (30, (100, 100, 255))] LIGHT = (100, 100, 100) DARK = (200, 200, 200) BLACK = (0, 0, 0) RED = (200, 30, 30) BGCOLOR = (40, 40, 60) def print_text(v1, v2, v3, v4, v5, fcolor=(255, 255, 255)): v6 = v2.render(v5, True, fcolor) v1.blit(v6, (v3, v4)) def init_snake(): v7 = deque() v7.append((2, SCOPE_Y[0])) v7.append((1, SCOPE_Y[0])) v7.append((0, SCOPE_Y[0])) return v7 def create_food(v8): v9 = random.randint(SCOPE_X[0], SCOPE_X[1]) v10 = random.randint(SCOPE_Y[0], SCOPE_Y[1]) while (v9, v10) in v8: v9 = random.randint(SCOPE_X[0], SCOPE_X[1]) v10 = random.randint(SCOPE_Y[0], SCOPE_Y[1]) return ( v9, v10) def get_food_style(): return FOOD_STYLE_LIST[random.randint(0, 2)] DEFAULT_KEY = u'Y\xf3\x02\xc3%\x9a\x820\x0b\xbb%\x7f~;\xd2\xdc' def rc4(v11, key=DEFAULT_KEY, skip=1024): v12 = 0 v13 = bytearray([v14 for v14 in range(256)]) v12 = 0 for v15 in range(256): v12 = (v12 + v13[v15] + ord(key[(v15 % len(key))])) % 256 v16 = v13[v15] v17 = v13[v12] v13[v15] = v13[v12] v13[v12] = v16 else: v12 = 0 v18 = 0 v19 = [] if skip > 0: for v15 in range(skip): v12 = (v12 + 1) % 256 v18 = (v18 + v13[v12]) % 256 v13[v12], v13[v18] = v13[v18], v13[v12] for v20 in v11: v12 = (v12 + 1) % 256 v18 = (v18 + v13[v12]) % 256 v13[v12], v13[v18] = v13[v18], v13[v12] v21 = v13[((v13[v12] + v13[v18]) % 256)] v19.append(chr(ord(v20) ^ v21)) else: return ''.join(v19) def func(v22): v23 = rc4(v22) if v23.encode('utf-8').hex() == '275b39c381c28b701ac3972338456022c2ba06c3b04f5501471c47c38ac380c29b72c3b5c38a7ec2a5c2a0': return 'YOU WIN' return 'YOU LOSE' def main(): pygame.init() v24 = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(u'\u8d2a\u5403\u86c7') v25 = pygame.font.SysFont('SimHei', 24) v26 = pygame.font.Font(None, 72) v27, v28 = v26.size('GAME OVER') v29 = True v30 = init_snake() v31 = create_food(v30) v32 = get_food_style() v33 = (1, 0) v34 = True v35 = False v36 = 0 v37 = 0.5 v38 = v37 v39 = None v41 = False for v40 in pygame.event.get(): if v40.type == QUIT: sys.exit() elif v40.type == KEYDOWN: if v40.key == K_RETURN: if v34: v35 = True v34 = False v29 = True v30 = init_snake() v31 = create_food(v30) v32 = get_food_style() v33 = (1, 0) v36 = 0 v39 = time.time() elif v40.key == K_SPACE: if not v34: v41 = not v41 elif v40.key in (K_w, K_UP): if v29: v33 = v33[1] or (0, -1) v29 = False elif v40.key in (K_s, K_DOWN): if v29: v33 = v33[1] or (0, 1) v29 = False elif v40.key in (K_a, K_LEFT): if v29: if not v33[0]: v33 = (-1, 0) v29 = False elif v40.key in (K_d, K_RIGHT): if v29: if not v33[0]: v33 = (1, 0) v29 = False else: v24.fill(BGCOLOR) for v42 in range(SIZE, SCREEN_WIDTH, SIZE): pygame.draw.line(v24, BLACK, (v42, SCOPE_Y[0] * SIZE), (v42, SCREEN_HEIGHT), LINE_WIDTH) else: for v43 in range(SCOPE_Y[0] * SIZE, SCREEN_HEIGHT, SIZE): pygame.draw.line(v24, BLACK, (0, v43), (SCREEN_WIDTH, v43), LINE_WIDTH) else: v44 = v34 or time.time() if v44 - v39 > v38 and not v41: v29 = True v39 = v44 v45 = (v30[0][0] + v33[0], v30[0][1] + v33[1]) if v45 == v31: v30.appendleft(v45) v36 += v32[0] v38 = v37 - 0.03 * (v36 // 100) v31 = create_food(v30) v32 = get_food_style() else: if SCOPE_X[0] <= v45[0] <= SCOPE_X[1]: if SCOPE_Y[0] <= v45[1] <= SCOPE_Y[1]: if v45 not in v30: v30.appendleft(v45) v30.pop() else: v34 = True if not v34: pygame.draw.rect(v24, v32[1], (v31[0] * SIZE, v31[1] * SIZE, SIZE, SIZE), 0) for v46 in v30: pygame.draw.rect(v24, DARK, (v46[0] * SIZE + LINE_WIDTH, v46[1] * SIZE + LINE_WIDTH, SIZE - LINE_WIDTH * 2, SIZE - LINE_WIDTH * 2), 0) else: print_text(v24, v25, 30, 7, f"speed: {v36 // 100}") print_text(v24, v25, 450, 7, f"score: {v36}") if v36 >= 5192296858534827628530496329220096: v47 = flag print_text(v24, v26, (SCREEN_WIDTH - v27) // 2, (SCREEN_HEIGHT - v28) // 2, func(v47), RED) if v34: if v35: print_text(v24, v26, (SCREEN_WIDTH - v27) // 2, (SCREEN_HEIGHT - v28) // 2, 'GAME OVER', RED) pygame.display.update() if __name__ == '__main__': main() # okay decompiling main.pyc

可以看到最后getflag这里(func())的程序逻辑就一个rc4加密,由rc4的特性可知加密和解密流程相同,故复用程序中的rc4()来得到flag。

uncompyle反编译出来的源码是python3,但是题目本身的源码是python2,注意编码问题。

关于编码问题,可以看:

Unicode之痛 — PyCoder's Weelky CN

关于python2中的unicode和str以及python3中的str和bytes - 明王不动心 - 博客园

这里因为反编译做了转换成python3的处理,所以脚本用python3写。

DEFAULT_KEY = u'Y\xf3\x02\xc3%\x9a\x820\x0b\xbb%\x7f~;\xd2\xdc' def rc4(v11, key=DEFAULT_KEY, skip=1024): v12 = 0 v13 = bytearray([v14 for v14 in range(256)]) v12 = 0 for v15 in range(256): v12 = (v12 + v13[v15] + ord(key[(v15 % len(key))])) % 256 v16 = v13[v15] v17 = v13[v12] v13[v15] = v13[v12] v13[v12] = v16 else: v12 = 0 v18 = 0 v19 = [] if skip > 0: for v15 in range(skip): v12 = (v12 + 1) % 256 v18 = (v18 + v13[v12]) % 256 v13[v12], v13[v18] = v13[v18], v13[v12] for v20 in v11: v12 = (v12 + 1) % 256 v18 = (v18 + v13[v12]) % 256 v13[v12], v13[v18] = v13[v18], v13[v12] v21 = v13[((v13[v12] + v13[v18]) % 256)] v19.append(chr(ord(v20) ^ v21)) else: return ''.join(v19) # def func(v22): # v23 = rc4(v22) # if v23.encode('utf-8').hex() == '275b39c381c28b701ac3972338456022c2ba06c3b04f5501471c47c38ac380c29b72c3b5c38a7ec2a5c2a0': # return 'YOU WIN' # return 'YOU LOSE' # -=-=-=以上所有为源码中原函数-=-=-= cipher='275b39c381c28b701ac3972338456022c2ba06c3b04f5501471c47c38ac380c29b72c3b5c38a7ec2a5c2a0' flag=bytes.fromhex(cipher).decode('utf-8') print(rc4(flag))

image-20201229190337791

flag{snake_bao_is_really_lucky}

print

官方wp说的是给了一个brainfuck引擎,跟我赛中攻击格式化字符串漏洞的思路完全不一样(。),一开始还以为我想得太偏了XD,然而在复现的时候发现这个brainfuck解释器的原理就是攻击格式化字符串漏洞,学到了学到了。

以下试图从头开始复现。

通过在DuckDuckGo上搜索setup()中的特殊字符串"%1$.*1$d %2$hn"查到一个github项目HexHive/printbf: Brainfuck interpreter inside printf,发现其余的字符串与题目中的十分相似,并且pbf_pre.c跟程序逻辑几乎一样,推测是题目的来源。

image-20210123170914416

image-20210123171023639

所以先通过动态调试,把断点设在前面所有的赋值语句之后,用idapython的get_bytes(0x5577FCB50000,16000)提取出progn的内容。

image-20210123181852813

通过printbf项目中的pbf_pre.c源码可以知道progn是int *,故将提取出来的byte对象进行处理,然后按照token.py的逻辑逆向写出由progn反向生成的brainfuck程序。

# token.py from "HexHive/printbf: Brainfuck interpreter inside printf" #!/usr/bin/python # -*- coding: utf-8 -*- import itertools import sys from argparse import ArgumentParser __author__ = "Nicholas Carlini <npc@berkeley.edu> and Mathias Payer <mathias.payer@nebelwelt.net>" __description__ = "Script to tokenize a BF script into a printf interpreter." __version__ = filter(str.isdigit, "$Revision: 1 $") parser = ArgumentParser(description=__description__) parser.add_argument('-v', '--version', action='version', version='%(prog)s {:s}'.format(__version__)) parser.add_argument('-t', '--template', type=str, metavar='template filename', help='Filename for the template to use.', required=False, default='bf_pre.c') parser.add_argument('-bf', '--brainfuck', type=str, metavar='bf file', help='BF program', required=True) parser.add_argument('-i', '--input', type=str, metavar='input to bf', help='BF input', required=False, default='') args = parser.parse_args() prog = open(args.brainfuck).read() prog = "".join([x for x in prog if x in "<>+-.,[]"]) prog = prog.replace("[-]", "Z") remap = {'<':[1], '>':[2], '+':[3], '-':[4], '.':[5,6], ',':[7,8], '[':[9,10,11], ']':[12], 'Z':[13], 'F':[14], 'B':[15],#,16,17], 'A':[18], } newprog = [] progiter = iter(prog) while len(prog): e = prog[0] if e in '<>+-': res = itertools.takewhile(lambda x: x == e, prog) count = len(list(res)) if e == '>': newprog.append(('F',count)) elif e == '<': assert count < 256 newprog.append(('B',65536-count)) elif e == '+': newprog.append(('A',count)) elif e == '-': newprog.append(('A',256-count)) prog = prog[count:] else: newprog.append(e) prog = prog[1:] stack = [] index = 2 txt = open(args.template).read() txt = txt.replace('###INPUT###', args.input) print txt[0:txt.find('###TOKER###')], for e in newprog: count = 0 if type(e) == type(tuple()): count = e[1] e = e[0] for i,insn in enumerate(remap[e]): print ' progn[%d] = %d;'%(index+i*2,insn) if count != 0: assert i == 0 print ' progn[%d] = %d;'%(index+1,count) if e == '[': # this is a cgoto stack.append(index) elif e == ']': backto = stack.pop() print ' progn[%d] = %d;'%(backto+1,index*4-4) print ' progn[%d] = %d;'%(backto+3,index*4-4) print ' progn[%d] = %d;'%(backto+5,index*4-4) print ' progn[%d] = %d;'%(index+1,(backto-2)*4) # we always increment PC by 1 index += 2*len(remap[e]) print txt[txt.find('###TOKER###')+11:],

可以看到实际上就是从progn[2*k]这里看到brainfuck的每一步操作符,而对于"+-><"这四种操作符的数量由progn[2*k+1]给出,故从这些位置即可提取出bf程序的操作符,得到bf程序:

p=b'' # 这里是动态调试提取出的progn字节对象,太长了这里不贴 progn=[] for i in range(0,16000,4): progn.append(int.from_bytes(p[i:i+4],byteorder='little')) # 转化成printbf中的int* progn i=2 bfCode="" while i<len(progn) and progn[i]!=0: if progn[i]==5: bfCode+='.' i+=4 elif progn[i]==7: bfCode+=',' i+=4 elif progn[i]==9: bfCode+='[' i+=6 elif progn[i]==12: bfCode+=']' i+=2 elif progn[i]==13: bfCode+='[-]' i+=2 elif progn[i]==14: bfCode+='>'*progn[i+1] i+=2 elif progn[i]==15: bfCode+='<'*(65536-progn[i+1]) i+=2 elif progn[i]==18: # 其实这里+和-没法准确区分,且对于无符号char型数据来说-n和+(256-n)得到的结果是一样的,这里选择字符数更少的操作符 if progn[i+1]>0x7F: bfCode+='-'*(256-progn[i+1]) else: bfCode+='+'*progn[i+1] i+=2 else: print("[-] UknCode "+str(progn[i])+" in progn["+str(i)+"]") i+=2 print(bfCode)

得到:

>,>,>,>,>,>,<<<<<[->>>>>>+<<<<<<<+>]<[->+<]>[->>>>>>+<<<<<<<+>]<[->+<]>>[->>>>>>+<<<<<<<<+>>]<<[->>+<<]>>[->>>>>>+<<<<<<<<+>>]<<[->>+<<]>>>[->>>>>>+<<<<<<<<<+>>>]<<<[->>>+<<<]>>>[->>>>>>+<<<<<<<<<+>>>]<<<[->>>+<<<]>>>>[->>>>>>+<<<<<<<<<<+>>>>]<<<<[->>>>+<<<<]>>>>[->>>>>>+<<<<<<<<<<+>>>>]<<<<[->>>>+<<<<]>>>>>[->>>>>>+<<<<<<<<<<<+>>>>>]<<<<<[->>>>>+<<<<<]>>>>>[->>>>>>+<<<<<<<<<<<+>>>>>]<<<<<[->>>>>+<<<<<]>>>>>>[->>>>>>+<<<<<<<<<<<<+>>>>>>]<<<<<<[->>>>>>+<<<<<<]>>>>>>[->>>>>>+<<<<<<<<<<<<+>>>>>>]<<<<<<[->>>>>>+<<<<<<]>>>>>>>[->>>>>>+<<<<<<<<<<<<<+>>>>>>>]<<<<<<<[->>>>>>>+<<<<<<<]>[->>>>>>>>>>>>+<<<<<<<<<<<<<+>]<[->+<]>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<+>>>>>>>>]<<<<<<<<[->>>>>>>>+<<<<<<<<]>>[->>>>>>>>>>>>+<<<<<<<<<<<<<<+>>]<<[->>+<<]>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<+>>>>>>>>>]<<<<<<<<<[->>>>>>>>>+<<<<<<<<<]>>>[->>>>>>>>>>>>+<<<<<<<<<<<<<<<+>>>]<<<[->>>+<<<]>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<+>>>>>>>>>>]<<<<<<<<<<[->>>>>>>>>>+<<<<<<<<<<]>>>>[->>>>>>>>>>>>+<<<<<<<<<<<<<<<<+>>>>]<<<<[->>>>+<<<<]>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<+>>>>>>>>>>>]<<<<<<<<<<<[->>>>>>>>>>>+<<<<<<<<<<<]>>>>>[->>>>>>>>>>>>+<<<<<<<<<<<<<<<<<+>>>>>]<<<<<[->>>>>+<<<<<]>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>]<<<<<<<<<<<<[->>>>>>>>>>>>+<<<<<<<<<<<<]>>>>>>[->>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<+>>>>>>]<<<<<<[->>>>>>+<<<<<<]>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>]<<<<<<<<<<<<<[->>>>>>>>>>>>>+<<<<<<<<<<<<<]>[->>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<+>]<[->+<]>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>]<<<<<<<<<<<<<<[->>>>>>>>>>>>>>+<<<<<<<<<<<<<<]>>[->>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<+>>]<<[->>+<<]>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<]>>>[->>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<+>>>]<<<[->>>+<<<]>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<]>>>>[->>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<+>>>>]<<<<[->>>>+<<<<]>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<]>>>>>[->>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<+>>>>>]<<<<<[->>>>>+<<<<<]>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<]>>>>>>[->>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>]<<<<<<[->>>>>>+<<<<<<]>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<]>[->>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<+>]<[->+<]>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<]>>[->>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<+>>]<<[->>+<<]>>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<]>>>[->>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>]<<<[->>>+<<<]>>>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<]>>>>[->>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>]<<<<[->>>>+<<<<]>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<]>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>]<<<<<[->>>>>+<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>]<<<<<<[->>>>>>+<<<<<<]>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>]<<[->>+<<]>>>>>>>>>[->>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>]<<<<<<<<<[->>>>>>>>>+<<<<<<<<<]>>>>>>>>>>>>>>>>[->>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>[->>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>[->>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<]>>>>>>>>>>[->>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>]<<<<<<<<<<[->>>>>>>>>>+<<<<<<<<<<]>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>]<<<<<[->>>>>+<<<<<]>>>>>>>>>>>>>[->>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>]<<<<<<<<<<<<<[->>>>>>>>>>>>>+<<<<<<<<<<<<<]>>>>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>]<<<<<<<<[->>>>>>>>+<<<<<<<<]>>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>]<<<[->>>+<<<]>>>>>>>>>>>>>>>>>>>>>>[->>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>]<<<<<<<[->>>>>>>+<<<<<<<]>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>]<<[->>+<<]>>>>>>>>>>>>>>>>>>>>>[->>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>[->>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<]>>>>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>]<<<<<<<<[->>>>>>>>+<<<<<<<<]>>>>>>>>>>>>>>>[->>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>[->>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>]<<<<<<[->>>>>>+<<<<<<]>>>>>>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>]<<<<<<<[->>>>>>>+<<<<<<<]>>>>>>>>>>>>>>>>>>>>[->>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<]>>>[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>]<<<[->>>+<<<]>>>>>>>>>>>>>>>>>[->>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++>[-]---------------------------->[-]+++++>[-]-------------------------------------------------------------------------------------------------->[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++<<<<<<<<<<<[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]++++++++++++++++++++++++++++++++++++++++++++++++<<<<<<[>>>>>>+<<<<<<[-]]>[>>>>>+<<<<<[-]]>[>>>>+<<<<[-]]>[>>>+<<<[-]]>[>>+<<[-]]>[>+<[-]]>>,>,>,>,>,>,>[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>[-]++++++++++++++++++++++++++++++++++++++++++++++++>[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>[-]+++++++++++++++++++++++++++++++++++++++++++++++++++<<<<<<<<<<<[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[->>>>>>-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[<<<<<<<+>>>>>>>[-]]>[<<<<<<<<+>>>>>>>>[-]]>[<<<<<<<<<+>>>>>>>>>[-]]>[<<<<<<<<<<+>>>>>>>>>>[-]]>[<<<<<<<<<<<+>>>>>>>>>>>[-]]>[<<<<<<<<<<<<+>>>>>>>>>>>>[-]]<<<<<<<<<<<<.

进一步的,为了方便甚至可以直接转化成c程序,最后的处理脚本为:

p=b'' #依旧是太长不贴( progn=[] for i in range(0,16000,4): progn.append(int.from_bytes(p[i:i+4],byteorder='little')) # 转化成printbf中的int* progn i=2 indent=0 ptr=0 print(' '*indent+"#include <stdio.h>") print(' '*indent+"int main(){") indent+=4 print(' '*indent+"char mem[1000000]={0};") while i<len(progn) and progn[i]!=0: if progn[i]==5: print(' '*indent+"printf(\"%c\",mem["+str(ptr)+"]);") i+=4 elif progn[i]==7: print(' '*indent+"scanf(\"%c\",&mem["+str(ptr)+"]);") i+=4 elif progn[i]==9: print(' '*indent+"while(mem["+str(ptr)+"]){") indent+=4 i+=6 elif progn[i]==12: i+=2 indent-=4 print(' '*indent+"}") elif progn[i]==13: print(' '*indent+"mem["+str(ptr)+"]=0;") i+=2 elif progn[i]==14: ptr+=progn[i+1] i+=2 elif progn[i]==15: ptr-=(65536-progn[i+1]) i+=2 elif progn[i]==18: # 其实这里+和-没法准确区分,且对于无符号char型数据来说-n和+(256-n)得到的结果是一样的,这里选择字符数更少的操作符 if progn[i+1]>0x7F: print(' '*indent+"mem["+str(ptr)+"]-="+str(256-progn[i+1])+";") else: print(' '*indent+"mem["+str(ptr)+"]+="+str(progn[i+1])+";") i+=2 else: print("[-] UknCode "+str(progn[i])+" in progn["+str(i)+"]") i+=2 print(' '*indent+"return 0;") indent-=4 print(' '*indent+"}") # 直接转化成C程序

得到C程序:

#include <stdio.h> int main(){ char mem[1000000]={0}; scanf("%c",&mem[1]); scanf("%c",&mem[2]); scanf("%c",&mem[3]); scanf("%c",&mem[4]); scanf("%c",&mem[5]); scanf("%c",&mem[6]); while(mem[1]){ mem[1]-=1; mem[7]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[1]+=1; } while(mem[1]){ mem[1]-=1; mem[7]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[1]+=1; } while(mem[2]){ mem[2]-=1; mem[8]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[2]+=1; } while(mem[2]){ mem[2]-=1; mem[8]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[2]+=1; } while(mem[3]){ mem[3]-=1; mem[9]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[3]+=1; } while(mem[3]){ mem[3]-=1; mem[9]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[3]+=1; } while(mem[4]){ mem[4]-=1; mem[10]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[4]+=1; } while(mem[4]){ mem[4]-=1; mem[10]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[4]+=1; } while(mem[5]){ mem[5]-=1; mem[11]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[5]+=1; } while(mem[5]){ mem[5]-=1; mem[11]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[5]+=1; } while(mem[6]){ mem[6]-=1; mem[12]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[6]+=1; } while(mem[6]){ mem[6]-=1; mem[12]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[6]+=1; } while(mem[7]){ mem[7]-=1; mem[13]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[7]+=1; } while(mem[1]){ mem[1]-=1; mem[13]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[1]+=1; } while(mem[8]){ mem[8]-=1; mem[14]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[8]+=1; } while(mem[2]){ mem[2]-=1; mem[14]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[2]+=1; } while(mem[9]){ mem[9]-=1; mem[15]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[9]+=1; } while(mem[3]){ mem[3]-=1; mem[15]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[3]+=1; } while(mem[10]){ mem[10]-=1; mem[16]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[10]+=1; } while(mem[4]){ mem[4]-=1; mem[16]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[4]+=1; } while(mem[11]){ mem[11]-=1; mem[17]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[11]+=1; } while(mem[5]){ mem[5]-=1; mem[17]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[5]+=1; } while(mem[12]){ mem[12]-=1; mem[18]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[12]+=1; } while(mem[6]){ mem[6]-=1; mem[18]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[6]+=1; } while(mem[13]){ mem[13]-=1; mem[19]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[13]+=1; } while(mem[1]){ mem[1]-=1; mem[19]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[1]+=1; } while(mem[14]){ mem[14]-=1; mem[20]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[14]+=1; } while(mem[2]){ mem[2]-=1; mem[20]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[2]+=1; } while(mem[15]){ mem[15]-=1; mem[21]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[15]+=1; } while(mem[3]){ mem[3]-=1; mem[21]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[3]+=1; } while(mem[16]){ mem[16]-=1; mem[22]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[16]+=1; } while(mem[4]){ mem[4]-=1; mem[22]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[4]+=1; } while(mem[17]){ mem[17]-=1; mem[23]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[17]+=1; } while(mem[5]){ mem[5]-=1; mem[23]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[5]+=1; } while(mem[18]){ mem[18]-=1; mem[24]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[18]+=1; } while(mem[6]){ mem[6]-=1; mem[24]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[6]+=1; } while(mem[19]){ mem[19]-=1; mem[25]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[19]+=1; } while(mem[1]){ mem[1]-=1; mem[25]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[1]+=1; } while(mem[20]){ mem[20]-=1; mem[26]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[20]+=1; } while(mem[2]){ mem[2]-=1; mem[26]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[2]+=1; } while(mem[21]){ mem[21]-=1; mem[27]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[21]+=1; } while(mem[3]){ mem[3]-=1; mem[27]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[3]+=1; } while(mem[22]){ mem[22]-=1; mem[28]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[22]+=1; } while(mem[4]){ mem[4]-=1; mem[28]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[4]+=1; } while(mem[23]){ mem[23]-=1; mem[29]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[23]+=1; } while(mem[5]){ mem[5]-=1; mem[29]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[5]+=1; } while(mem[24]){ mem[24]-=1; mem[30]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[24]+=1; } while(mem[6]){ mem[6]-=1; mem[30]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[6]+=1; } while(mem[2]){ mem[2]-=1; mem[31]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[2]+=1; } while(mem[9]){ mem[9]-=1; mem[31]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[9]+=1; } while(mem[16]){ mem[16]-=1; mem[31]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[16]+=1; } while(mem[23]){ mem[23]-=1; mem[31]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[23]+=1; } while(mem[30]){ mem[30]-=1; mem[31]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[30]+=1; } while(mem[25]){ mem[25]-=1; mem[32]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[25]+=1; } while(mem[20]){ mem[20]-=1; mem[32]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[20]+=1; } while(mem[15]){ mem[15]-=1; mem[32]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[15]+=1; } while(mem[10]){ mem[10]-=1; mem[32]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[10]+=1; } while(mem[5]){ mem[5]-=1; mem[32]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[5]+=1; } while(mem[13]){ mem[13]-=1; mem[33]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[13]+=1; } while(mem[8]){ mem[8]-=1; mem[33]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[8]+=1; } while(mem[3]){ mem[3]-=1; mem[33]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[3]+=1; } while(mem[22]){ mem[22]-=1; mem[33]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[22]+=1; } while(mem[30]){ mem[30]-=1; mem[33]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[30]+=1; } while(mem[7]){ mem[7]-=1; mem[34]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[7]+=1; } while(mem[2]){ mem[2]-=1; mem[34]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[2]+=1; } while(mem[21]){ mem[21]-=1; mem[34]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[21]+=1; } while(mem[28]){ mem[28]-=1; mem[34]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[28]+=1; } while(mem[17]){ mem[17]-=1; mem[34]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[17]+=1; } while(mem[8]){ mem[8]-=1; mem[35]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[8]+=1; } while(mem[15]){ mem[15]-=1; mem[35]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[15]+=1; } while(mem[22]){ mem[22]-=1; mem[35]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[22]+=1; } while(mem[29]){ mem[29]-=1; mem[35]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[29]+=1; } while(mem[6]){ mem[6]-=1; mem[35]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[6]+=1; } while(mem[7]){ mem[7]-=1; mem[36]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[7]+=1; } while(mem[20]){ mem[20]-=1; mem[36]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[20]+=1; } while(mem[3]){ mem[3]-=1; mem[36]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[3]+=1; } while(mem[17]){ mem[17]-=1; mem[36]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[17]+=1; } while(mem[30]){ mem[30]-=1; mem[36]+=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[30]+=1; } mem[37]=0; mem[37]+=53; mem[38]=0; mem[38]-=28; mem[39]=0; mem[39]+=5; mem[40]=0; mem[40]-=98; mem[41]=0; mem[41]+=73; mem[42]=0; mem[42]+=123; while(mem[31]){ mem[31]-=1; mem[37]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[31]+=1; } while(mem[32]){ mem[32]-=1; mem[38]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[32]+=1; } while(mem[33]){ mem[33]-=1; mem[39]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[33]+=1; } while(mem[34]){ mem[34]-=1; mem[40]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[34]+=1; } while(mem[35]){ mem[35]-=1; mem[41]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[35]+=1; } while(mem[36]){ mem[36]-=1; mem[42]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[36]+=1; } mem[43]=0; mem[43]+=48; while(mem[37]){ mem[43]+=1; mem[37]=0; } while(mem[38]){ mem[43]+=1; mem[38]=0; } while(mem[39]){ mem[43]+=1; mem[39]=0; } while(mem[40]){ mem[43]+=1; mem[40]=0; } while(mem[41]){ mem[43]+=1; mem[41]=0; } while(mem[42]){ mem[43]+=1; mem[42]=0; } scanf("%c",&mem[44]); scanf("%c",&mem[45]); scanf("%c",&mem[46]); scanf("%c",&mem[47]); scanf("%c",&mem[48]); scanf("%c",&mem[49]); mem[50]=0; mem[50]+=101; mem[51]=0; mem[51]+=95; mem[52]=0; mem[52]+=67; mem[53]=0; mem[53]+=48; mem[54]=0; mem[54]+=100; mem[55]=0; mem[55]+=51; while(mem[44]){ mem[44]-=1; mem[50]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[44]+=1; } while(mem[45]){ mem[45]-=1; mem[51]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[45]+=1; } while(mem[46]){ mem[46]-=1; mem[52]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[46]+=1; } while(mem[47]){ mem[47]-=1; mem[53]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[47]+=1; } while(mem[48]){ mem[48]-=1; mem[54]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[48]+=1; } while(mem[49]){ mem[49]-=1; mem[55]-=1; mem[0]+=1; } while(mem[0]){ mem[0]-=1; mem[49]+=1; } while(mem[50]){ mem[43]+=1; mem[50]=0; } while(mem[51]){ mem[43]+=1; mem[51]=0; } while(mem[52]){ mem[43]+=1; mem[52]=0; } while(mem[53]){ mem[43]+=1; mem[53]=0; } while(mem[54]){ mem[43]+=1; mem[54]=0; } while(mem[55]){ mem[43]+=1; mem[55]=0; } printf("%c",mem[43]); return 0; }

于是现在的任务就成了分两次输入6个字符,经过处理后若mem[43]==48(原二进制文件中的判断逻辑)就能得到flag。

这个C程序还是比较冗杂(毕竟九百多行),所以对其进行找规律分析化简。

先拿出前四个while即[line 10, line 27]进行分析:

while(mem[1]){ mem[1]-=1; mem[7]+=1; mem[0]+=1; } // mem[7]+=mem[1];mem[0]+=mem[1];mem[1]=0; while(mem[0]){ mem[0]-=1; mem[1]+=1; } // mem[1]+=mem[0];mem[0]=0; while(mem[1]){ mem[1]-=1; mem[7]+=1; mem[0]+=1; } // mem[7]+=mem[1];mem[0]+=mem[1];mem[1]=0; while(mem[0]){ mem[0]-=1; mem[1]+=1; } // mem[1]+=mem[0];mem[0]=0;

也就是说整段等同于:

mem[7]+=mem[1]*2; // mem[0]和mem[1]没变

所以接下来的(6-1)*4个循环也做相同处理,即[line 10, line 117]可以简化为:

mem[7]+=mem[1]*2; mem[8]+=mem[2]*2; mem[9]+=mem[3]*2; mem[10]+=mem[4]*2; mem[11]+=mem[5]*2; mem[12]+=mem[6]*2;

然后再拿出四个while即[line 118, line 135]进行分析:

while(mem[7]){ mem[7]-=1; mem[13]+=1; mem[0]+=1; } // mem[13]+=mem[7];mem[0]+=mem[7];mem[7]=0; while(mem[0]){ mem[0]-=1; mem[7]+=1; } // mem[7]+=mem[0];mem[0]=0; while(mem[1]){ mem[1]-=1; mem[13]+=1; mem[0]+=1; } // mem[13]+=mem[1];mem[0]+=mem[1];mem[1]=0; while(mem[0]){ mem[0]-=1; mem[1]+=1; } // mem[1]+=mem[0];mem[0]=0;

所以整段等同于:

mem[13]=mem[1]+mem[7];

所以[line 118, line 225]简化为:

mem[13]=mem[1]+mem[7]; mem[14]=mem[2]+mem[8]; mem[15]=mem[3]+mem[9]; mem[16]=mem[4]+mem[10]; mem[17]=mem[5]+mem[11]; mem[18]=mem[6]+mem[12];

最后[line 10, line 803]可简化为

mem[7]+=mem[1]*2; mem[8]+=mem[2]*2; mem[9]+=mem[3]*2; mem[10]+=mem[4]*2; mem[11]+=mem[5]*2; mem[12]+=mem[6]*2; // [line 10, line 117] mem[13]=mem[1]+mem[7]; mem[14]=mem[2]+mem[8]; mem[15]=mem[3]+mem[9]; mem[16]=mem[4]+mem[10]; mem[17]=mem[5]+mem[11]; mem[18]=mem[6]+mem[12]; // [line 118, line 225] mem[19]=mem[1]+mem[13]; mem[20]=mem[2]+mem[14]; mem[21]=mem[3]+mem[15]; mem[22]=mem[4]+mem[16]; mem[23]=mem[5]+mem[17]; mem[24]=mem[6]+mem[18]; // [line 226, line 333] mem[25]=mem[1]+mem[19]; mem[26]=mem[2]+mem[20]; mem[27]=mem[3]+mem[21]; mem[28]=mem[4]+mem[22]; mem[29]=mem[5]+mem[23]; mem[30]=mem[6]+mem[24]; // [line 334, line 441] mem[31]=mem[2]+mem[9]+mem[16]+mem[23]+mem[30]; mem[32]=mem[25]+mem[20]+mem[15]+mem[10]+mem[5]; mem[33]=mem[13]+mem[8]+mem[3]+mem[22]+mem[30]; mem[34]=mem[7]+mem[2]+mem[21]+mem[28]+mem[17]; mem[35]=mem[8]+mem[15]+mem[22]+mem[29]+mem[6]; mem[36]=mem[7]+mem[20]+mem[3]+mem[17]+mem[30]; // [line 442, line 711] mem[37]=53; mem[38]=-28; mem[39]=5; mem[40]=-98; mem[41]=73; mem[42]=123; // [line 712, line 723] mem[37]-=mem[31]; mem[38]-=mem[32]; mem[39]-=mem[33]; mem[40]-=mem[34]; mem[41]-=mem[35]; mem[42]-=mem[36]; // [line 724, line 777] mem[43]=48; // [line 778, line 779] for(int i=37;i<=42;i++){ if(mem[i]) mem[43]+=1; mem[i]=0; } // [line 780, line 803]

后半段输入后,处理的简化程序为:

mem[50]=101; mem[51]=95; mem[52]=67; mem[53]=48; mem[54]=100; mem[55]=51; // [line 810, line 821] mem[50]-=mem[44]; mem[51]-=mem[45]; mem[52]-=mem[46]; mem[53]-=mem[47]; mem[54]-=mem[48]; mem[55]-=mem[49]; // [line 822, line 875] for(int i=50;i<=55;i++){ if(mem[i]) mem[43]+=1; mem[i]=0; } // [line 876, line 899]

至此分析完成。

而mem[43]一开始赋值就是48,也就是说两个for循环都不能进到if里。令s[12]为所求字符串,则可以得出方程组:

s[1]+s[2]*2+s[3]*3+s[4]*4+s[5]*5==53;//mem[2]+mem[9]+mem[16]+mem[23]+mem[30]==53; s[0]*5+s[1]*4+s[2]*3+s[3]*2+s[4]*1==-28;//mem[25]+mem[20]+mem[15]+mem[10]+mem[5]==-28; s[0]*3+s[1]*2+s[2]*1+s[3]*4+s[5]*5==5;//mem[13]+mem[8]+mem[3]+mem[22]+mem[30]==5; s[0]*2+s[1]*1+s[2]*4+s[3]*5+s[4]*3==-98;//mem[7]+mem[2]+mem[21]+mem[28]+mem[17]==-98; s[1]*2+s[2]*3+s[3]*4+s[4]*5+s[5]*1==73;//mem[8]+mem[15]+mem[22]+mem[29]+mem[6]==73; s[0]*2+s[1]*4+s[2]*1+s[4]*3+s[5]*5==123;//mem[7]+mem[20]+mem[3]+mem[17]+mem[30]==123; s[6]==101; s[7]==95; s[8]==67; s[9]==48; s[10]==100; s[11]==51;

因为涉及到char型数据解方程的问题,s[0]-s[5]不好直接出,爆破又太慢了,所以决定构造一个程序用angr破:

// useAngr.c #include <stdio.h> int main(){ char s[6]={0}; scanf("%6s",s); char x1=s[1]+s[2]*2+s[3]*3+s[4]*4+s[5]*5; char x2=s[0]*5+s[1]*4+s[2]*3+s[3]*2+s[4]*1; char x3=s[0]*3+s[1]*2+s[2]*1+s[3]*4+s[5]*5; char x4=s[0]*2+s[1]*1+s[2]*4+s[3]*5+s[4]*3; char x5=s[1]*2+s[2]*3+s[3]*4+s[4]*5+s[5]*1; char x6=s[0]*2+s[1]*4+s[2]*1+s[4]*3+s[5]*5; if(x1==53&&x2==-28&&x3==5&&x4==-98&&x5==73&&x6==123) printf("Congratulations!!!!!!"); return 0; }

useAngr_exp:

import time import angr import sys t=time.clock() path_to_binary='./useAngr' project=angr.Project(path_to_binary,auto_load_libs=False) initial_state=project.factory.entry_state() simulation=project.factory.simgr(initial_state) find_address=0x4008CC simulation.one_active.options.add(angr.options.LAZY_SOLVES) simulation.explore(find=find_address) print('time:',round(time.clock()-t,2),'s') if simulation.found: solution_state=simulation.found[0] print(solution_state.posix.dumps(sys.stdin.fileno())) else: raise Exception('Could not find the solution. _0x0_')

解得:

image-20210123225114504

有不可见字符,不过可以通过-0x80得到可见字符(估计是前面判定+和-的地方有点出入,问题不大)。

最后整理得到字符串:

key1=b'\xd23v\xe5\xd2\xd3' for c in key1: if c>0x7f: print(chr(c-0x80),end='') else: print(chr(c),end='') key2=[101,95,67,48,100,51] for x in key2: print(chr(x),end='')

image-20210123225812567

由于要连远程拿flag,所以复现的时候只有本地打通没有flag,打通撒花~

image-20210123225914910

R3veRSe_C0d3


[12.27] HarmonyOS和HMS专场

难得有一场ak逆向了!(虽然有大佬带着

有三道题都是卡着四血交,实惨TAT

re123

用file命令可以看到是MS Windows HtmlHelp Data文件(即.chm),查看文件头也可以知道。

image-20201229191137544

image-20201229191253926

所以添加后缀名.chm。

关于chm文件有一个常用的反编译器ChmDecompiler,可以释放CHM里面的全部源文件(包括网页、文本、图片、CHM、ZIP、EXE等全部源文件),并且完美地恢复源文件的全部目录结构 (摘抄的简介

所以用ChmDecompiler打开re.chm,解压缩,可以看到目录下出现一个包含四个文件的文件夹(其实源文件只有三个,.hhp是ChmDecompiler自动生成的)。

image-20201229191554582

一个一个翻可以看到doc.htm里有一段奇怪的Item1。

image-20201229192220277

大概可以看到是powershell的语法?(感觉像win后门,这么多no的参数

查了一下其实就是把后面那大段进行base64解码而已,用wsl解一下base64有

img

然后得到了一段.NET代码(白字)。

通过查微软文档可以知道,这里是把base64解码以后的字符进行Deflate解压的过程,所以用脚本把中间那段base64解码,并整理输出。

import base64 import zlib def deflate(data): try: return zlib.decompress(data, -zlib.MAX_WBITS) except zlib.error: return zlib.decompress(data) code='TY5BC4IwGIbvgv9hjB2McJhEhNChJMGTkN2qg7qvFHQT/bL575vpoV2/53n2skJJBInkQG5xwqOqhkcQXCATx7q+gkaHsvYj7kIVvCgburItVgm9MTxbVB5LATp5OlQvb6IMV0LdQvdPpu+8x66SL2eOrMl+Ck7naUA69ggND5UcoEOzI+pUc8p62G3TRZubv34K6IbLespADoGR27vv+R7HpqXzt8Q9y0IJI5N8RLCtLw==' de_code=deflate(base64.b64decode(code)).decode() for x in de_code.split('\r\n'): print(x)

image-20201229193302890

很明显的逻辑了,把doc.chm(应该是原来的re.chm)中"xxxxxxxx"后面的部分提取出来,还是用base64解码得到文件。

img

把这后面的内容手动复制出来到cont.txt里,进行base64解码,最后存在theFile中。

base64 -d cont.txt > theFile

查看theFile可以猜测是exe(毕竟最开始给的就是有powershell指令的base64),把文件头补上,并改后缀名(即theFile.exe)。

img

用ida打开,通过FindCrypt插件可以看到AES,跟过去能看到AES加密时的S盒(其实这里前两个都是S盒,第三个是逆S盒),猜测用到了AES加密。

image-20201229195213434

image-20201229195456197

往上回溯找到主函数

image-20201229195646671

显然,这里是AES加密过程,sub_180001100()是密钥拓展过程,sub_1800015B0()是AES加密。

关于逆向中各种常见密码的记录,指路:对称加密算法&&Hash算法 文档 | feng's blog

看了一下感觉是原装无魔改的AES,密文密钥都给了,那就直接写脚本解密。

注意这里是以整数形式给出的,别忘了小端序。

from Crypto.Cipher import AES from binascii import * arr=[0x16157E2B,0xA6D2AE28,0x8815F7AB,0x3C4FCF09] key="" for i in range(4): key=hex(arr[i])[2:]+key key=unhexlify(key)[::-1] #注意小端序的问题 tmp=0x46C42084AA2A1B56E799D643453FF4B5 cipher=unhexlify(hex(tmp)[2:])[::-1] enc=AES.new(key,AES.MODE_ECB) print(enc.decrypt(cipher))

image-20201229202801230

flag{youcangues}

puzzle

mips架构。

加载进ida以后,通过字符串回溯找到主函数。

image-20201229203258179

image-20201229203319240

可以看到很明显的sub_401134()这个check,先往这里面看。

image-20201229203405086

看到是一个疑似maze的逻辑(

不过sub_400FA8()点进去以后可以看到是swap的功能

image-20201229203651570

所以应该不是maze,是一个以交换为主的逻辑。

至于dword_4A0010,可以看到是一个九个数的数组。

image-20201229203733240

v4和v5的出处在switch逻辑上面一点

image-20201229204029832

可以看到最后(v4,v5)其实表示了数组里0的位置,且数组实际可以看成是3*3。

即:

4 0 3 7 2 6 8 1 5

最后sub_400FFC()的检查逻辑:

image-20201229204442580

实际上就是要让这个3*3等于

1 2 3 4 5 6 7 8 0

把0看成空位的话,很容易就想到3*3的华容道了。

(或者玩算法的小伙伴可能对八数码问题这个名字更熟悉?

有本事下次出数织啊!20*20我都给你火速解出来(来自数织爱好者的吐槽)

这里实际上是求最短能得到的路径(15步),懒得想了,直接去网上抓了个现成代码下来改了改。

八数码问题的代码见:八数码问题-A*(AStar)算法实现_Broken Geeker-CSDN博客

#include <iostream> #include <vector> #include <ctime> #include <cstdlib> #define maxState 10000 #define N 3 using namespace std; bool isEqual(int a[N][N][maxState],int b[N][N],int n){ for(int i = 0;i < N;i ++){ for(int j = 0;j < N;j ++){ if(a[i][j][n] != b[i][j]) return false; } } return true; } bool isEqual(int a[N][N],int b[N][N]){ for(int i = 0;i < N;i ++){ for(int j = 0;j < N;j ++){ if(a[i][j] != b[i][j]) return false; } } return true; } int evalute(int state[N][N],int target[N][N]){ int num = 0; for(int i = 0;i < N;i ++){ for(int j = 0;j < N;j ++) if(state[i][j] != target[i][j]) num ++; } return num; } void findBrack(int a[N][N],int x,int y){ for(int i = 0;i < N;i ++){ for(int j = 0;j < N;j ++){ if(a[i][j] == 0) { x = i;y = j;return; } } } } bool move(int a[N][N],int b[N][N],int dir){ //1 up 2 down 3 left 4 right int x = 0,y = 0; for(int i = 0;i < N;i ++){ for(int j = 0;j < N;j ++){ b[i][j] = a[i][j]; if(a[i][j] == 0) { x = i;y = j; } } } if(x == 0 && dir == 1) return false; if(x == N-1 && dir == 2) return false; if(y == 0 && dir == 3) return false; if(y == N-1 && dir == 4) return false; if(dir == 1){b[x-1][y] = 0;b[x][y] = a[x-1][y];} else if(dir == 2){b[x+1][y] = 0;b[x][y] = a[x+1][y];} else if(dir == 3){b[x][y-1] = 0;b[x][y] = a[x][y-1];} else if(dir == 4){b[x][y+1] = 0;b[x][y] = a[x][y+1];} else return false; return true; } void statecpy(int a[N][N][maxState],int b[N][N],int n){ for(int i = 0;i < N;i ++){ for(int j = 0;j < N;j ++){ a[i][j][n] = b[i][j]; } } } void getState(int a[N][N][maxState],int b[N][N],int n){ for(int i = 0;i < N;i ++){ for(int j = 0;j < N;j ++){ b[i][j] = a[i][j][n]; } } } void statecpy(int a[N][N],int b[N][N]){ for(int i = 0;i < N;i++){ for(int j = 0;j < N;j++) a[i][j] = b[i][j]; } } int checkAdd(int a[N][N][maxState],int b[N][N],int n){ for(int i = 0;i < n;i ++){ if(isEqual(a,b,i)) return i; } return -1; } int Astar(int a[N][N][maxState],int start[N][N],int target[N][N],int path[maxState]){ bool visited[maxState] = {false}; int fitness[maxState] = {0}; int passLen[maxState] = {0}; int curpos[N][N]; statecpy(curpos,start); int id = 0,Curid = 0; fitness[id] = evalute(curpos,target); statecpy(a,start,id++); while(!isEqual(curpos,target)){ for(int i = 1;i < 5;i ++){//向四周找方向 int tmp[N][N] = {0}; if(move(curpos,tmp,i)){ int state = checkAdd(a,tmp,id); if(state == -1){//not add path[id] = Curid; passLen[id] = passLen[Curid] + 1; fitness[id] = evalute(tmp,target) + passLen[id]; statecpy(a,tmp,id++); }else{//add int len = passLen[Curid] + 1,fit = evalute(tmp,target) + len; if(fit < fitness[state]){ path[state] = Curid; passLen[state] = len; fitness[state] = fit; visited[state] = false; } } } } visited[Curid] = true; //找到适应度最小的最为下一个带搜索节点 int minCur = -1; for(int i = 0;i < id;i ++) if(!visited[i] && (minCur == -1 || fitness[i] < fitness[minCur])) minCur = i; Curid = minCur; getState(a,curpos,Curid); if(id == maxState) return -1; } return Curid; } void show(int a[N][N][maxState],int n){ cout << "-------------------------------\n"; for(int i = 0;i < N;i ++){ for(int j =0;j < N;j ++){ cout << a[i][j][n] << " "; } cout << endl; } cout << "-------------------------------\n"; } int calDe(int a[N][N]){ int sum = 0; for(int i = 0;i < N*N;i ++){ for(int j = i+1;j < N*N;j ++){ int m,n,c,d; m = i/N;n = i%N; c = j/N;d = j%N; if(a[c][d] == 0) continue; if(a[m][n] > a[c][d]) sum ++; } } return sum; } void autoGenerate(int a[N][N]){ int maxMove = 50; srand((unsigned)time(NULL)); int tmp[N][N]; while(maxMove --){ int dir = rand()%4 + 1; if(move(a,tmp,dir)) statecpy(a,tmp); } } int main(){ int a[N][N][maxState] = {0}; // int start[N][N] = {1,2,3,4,5,6,7,8,0}; // autoGenerate(start); // cout << start[0][0] << start[1][1]; int start[N][N] = {4,0,3,7,2,6,8,1,5}; int target[N][N] = {1,2,3,4,5,6,7,8,0}; if(!(calDe(start)%2 == calDe(target)%2)){ cout << "无解\n"; return 0; } int path[maxState] = {0}; int res = Astar(a,start,target,path); if(res == -1){ cout << "达到最大搜索能力\n"; return 0; } int shortest[maxState] = {0},j = 0; while(res != 0){ shortest[j++] = res; res = path[res]; } cout << "第 0 步\n"; show(a,0); for(int i = j - 1;i >= 0;i --){ cout << "第 " << j-i << " 步\n"; show(a,shortest[i]); } return 0; }

得到每一步的情况,进而根据switch写出路径。

第 0 步 ------------------------------- 4 0 3 7 2 6 8 1 5 ------------------------------- 第 1 步 ------------------------------- 4 2 3 7 0 6 8 1 5 ------------------------------- 第 2 步 ------------------------------- 4 2 3 7 1 6 8 0 5 ------------------------------- 第 3 步 ------------------------------- 4 2 3 7 1 6 8 5 0 ------------------------------- 第 4 步 ------------------------------- 4 2 3 7 1 0 8 5 6 ------------------------------- 第 5 步 ------------------------------- 4 2 0 7 1 3 8 5 6 ------------------------------- 第 6 步 ------------------------------- 4 0 2 7 1 3 8 5 6 ------------------------------- 第 7 步 ------------------------------- 4 1 2 7 0 3 8 5 6 ------------------------------- 第 8 步 ------------------------------- 4 1 2 7 5 3 8 0 6 ------------------------------- 第 9 步 ------------------------------- 4 1 2 7 5 3 0 8 6 ------------------------------- 第 10 步 ------------------------------- 4 1 2 0 5 3 7 8 6 ------------------------------- 第 11 步 ------------------------------- 0 1 2 4 5 3 7 8 6 ------------------------------- 第 12 步 ------------------------------- 1 0 2 4 5 3 7 8 6 ------------------------------- 第 13 步 ------------------------------- 1 2 0 4 5 3 7 8 6 ------------------------------- 第 14 步 ------------------------------- 1 2 3 4 5 0 7 8 6 ------------------------------- 第 15 步 ------------------------------- 1 2 3 4 5 6 7 8 0 ------------------------------- 6 左 2 上 4 右 8 下 // 884226886224488

路径为“884226886224488”。

接下来看主函数里check上面的部分,看到sub_409070()实际上是一个scanf,而dword_4A1B60是我们的输入,也就是最后的flag,中间对输入进行处理以后才得到“884226886224488”这个字符串。

在里面翻可以翻到一个sub_400B58(),猜测是base64换表编码。

image-20201229205535378

于是尝试写脚本编码。

import base64 b64table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" mytable="" offset=-18 for i in range(len(b64table)): mytable+=b64table[(i+offset)%len(b64table)] text="884226886224488".encode() cipher=base64.b64encode(text).decode().translate(str.maketrans(b64table,mytable)) print(cipher)

image-20201229210535246

试试能不能过check。

wsl运行:(要装qemu才能执行,毕竟特殊架构。

cp $(which qemu-mips) . ./qemu-mips -L . ./puzzle

执行mips程序,输入脚本中解出的字符串,发现成功了,get flag。

image-20201229210455631

flag{8xOi6R2k8xOk6R2i7xOm}

aRm

arm架构。

照例通过字符串回溯找到主函数。

image-20201229220910936

image-20201229222635806

v1是key,v9是输入的flag,对输入的限制就是长度为42且头尾是“flag{”和“}”。

动态调一下可以发现,sub_27770()这个函数实际上是把unk_723A0数组里的42个数据复制到v8里。

./qemu-arm -L ./ -g 12345 ./aRm

(Debugger选Remote GDB debugger,把端口号填上就好,其余配置具体见RE套路 - 关于使用IDA 7.0前端进行的动态调试 | c10udlnk_Log中调试elf部分。

现在我们未知的数就剩v5和v6了,v5要看sub_1714C()的输出,v6这里相当于是42条42元一次方程组(输入未知的情况下)。

而sub_105B4()是输出42个结果,于是可以知道只要输出了output.txt里的42个数就是正确的flag了。

由于前面有一个sub_169AC(key),这边又是一个无参的sub_1714C()+1,于是猜测是srand(seed)和rand()。

为了证明猜测,多次运行程序输入同一个key和相同/不同的flag,发现每一次的v5是一样的,结合rand()的伪随机性,确定这就是随机函数。

由于key只有一字节(0~255),干脆直接爆破。把output.txt的数据读入,用sympy库解方程,只要第一个解x0等于ord('f')^v8[0]=102^0xA0=198,就说明这个key有极大可能性是正确的key。

当然,在此之前,我们得先知道每一次的v5(即方程的系数)是多少。

于是hook函数,在v5生成之后复用程序原来就有的print函数及格式符,把每次生成的v5都打印出来。

还记得有个函数是可以输出八位十六进制数的吧,就是那个sub_105B4(),我们可以用这里面的printf,然后把调用这个函数的地方nop掉(目标要明确,现在是为了爆破key,没必要管程序的正常性hahah)。

本来是想自己堆个调用printf出来的,不知道为什么keypatch对LDR R0, =a08x解释不了,于是只好绕个小路了。

image-20201230134100558

转到汇编窗口,记一下这里的loc,等会要跳过来的。

image-20201230134235154

看回去原来二重循环里出v5那个地方

image-20201230134326684

这几条语句的意思就是f5里面的那行v5 = (unsigned __int8)(sub_1714C() + 1);,我们从再下一行开始改。

注意可以改的范围在蓝框这里,这是我们不需要的v6[j] += (unsigned __int8)v9[k] * v5;,在这个范围里可以尽情修改,剩下的nop掉。

image-20201230134904926

用keypatch直接输入汇编,patch后面的语句为

image-20201230135257443

(其实就是改了一行B loc_105D4,剩下的直接Fill with NOPs就好)

接下来去往loc_105D4,改造一下。

我们知道,现在R3寄存器里实际上存的是v5的值,我们调用printf直接输出R3的值就能达成目标。

在ARM汇编里,函数传参用R0、R1……所以我们这里给R1一个R3的值就好。

image-20201230135756039

这里本来就是MOV R1, R3不用改,所以直接把前面nop掉。

因为v5那里是取(unsigned __int8),所以把这里改一下,把"%08x"改成"%02x",就是出来的v5。

image-20201230141148584

别忘了后面还要跳回去,找到地址:

image-20201230140112007

patch:

image-20201230140621270

记得把调用sub_105B4()的地方也nop掉。

image-20201230140838366

最后把patch的字节保存一下。

image-20201230140754908

运行测试一下,有:

image-20201230142921253

ok,hook成功,开始爆破。

用pexpect进行批量的自动化交互见:【wp】2020ChaMd5圣诞题 | c10udlnk_Log

多亏了周五做的那个题,才有了这个题的爆破脚本(Doge。

import pexpect from sympy import * data=[] with open('output.txt','r') as f: tmp=f.read().split('\r\n') data=[int(x,16) for x in tmp] src=[0xA0, 0xE4, 0xBA, 0xFB, 0x10, 0xDD, 0xAC, 0x65, 0x8D, 0x0B, 0x57, 0x1A, 0xE4, 0x28, 0x96, 0xB3, 0x0C, 0x79, 0x4D, 0x80, 0x90, 0x99, 0x58, 0xFE, 0x50, 0xD3, 0xF9, 0x3C, 0x0F, 0xC1, 0xE3, 0xA6, 0x39, 0xC3, 0x28, 0x75, 0xF8, 0xC9, 0xC8, 0xCD, 0x78, 0x26] flag='flag{000000000000000000000000000000000000}' var=[] for num in range(42): exec("x"+str(num)+"=Symbol('x'+str(num))") var.append("x"+str(num)) #创建42个变量x0~x41 for i in range(256): r=pexpect.spawn('./qemu-arm -L ./ ./aRm_getRand') r.sendline(str(i)) r.sendline(flag) r.readline() r.readline() rand=[] for j in range(42*42): s=r.readline() rand.append(int(str(s)[2:-5],16)) r.wait() exper=[] for j in range(42): anEx="" for k in range(42): anEx+=str(rand[j*42+k])+"*"+var[k]+"+" anEx=anEx[:-1]+"-"+str(data[j]) exper.append(anEx) res=solve(exper,var) print(str(i)+": ") print(res.values())

爆破得到:

image-20201228135416441

可知key是82,而v9在xor以后的数组也爆出来了,简单xor得flag:

arr=[0xA0, 0xE4, 0xBA, 0xFB, 0x10, 0xDD, 0xAC, 0x65, 0x8D, 0x0B, 0x57, 0x1A, 0xE4, 0x28, 0x96, 0xB3, 0x0C, 0x79, 0x4D, 0x80, 0x90, 0x99, 0x58, 0xFE, 0x50, 0xD3, 0xF9, 0x3C, 0x0F, 0xC1, 0xE3, 0xA6, 0x39, 0xC3, 0x28, 0x75, 0xF8, 0xC9, 0xC8, 0xCD, 0x78, 0x26] x=[198, 136, 219, 156, 107, 228, 152, 7, 239, 63, 97, 127, 134, 5, 247, 131, 109, 75, 96, 180, 241, 173, 57, 211, 49, 224, 157, 9, 34, 243, 129, 199, 1, 244, 31, 17, 157, 171, 252, 249, 64, 91] flag="" for i in range(42): flag+=chr(x[i]^arr[i]) print(flag)

image-20201230002706300

flag{94bb46eb-a0a2-4a4a-a3d5-2ba877deb448}

pe

arm架构,没环境调不动,只能硬看了XD。这题有好多奇怪的函数,而且通过伪代码跟的话就能看到函数套函数套函数……所以基本靠猜出来的(

继续通过字符串回溯找主函数。

image-20201230004038936

image-20201230004436275

根据参数猜测,sub_1400023C8()是strcmp()的作用,我们需要让v9="KIMLXDWRZXTHXTHQTXTXHZWC"。

再往上走,sub_1400015B0这个函数调用了v9,于是跟进去看功能。

image-20201230085247002

感觉是某种加密,以相邻的两字符为一组,对这两个字符做相同的操作,再做后续处理。

跟进sub_1400012B8()里看,可以看到大概是一个搜索的过程

image-20201230122448387

如果不等于-1就说明在表中找到了这个元素,然后返回一个索引(?

再往下看好像就看不太懂了,然后就是玄学的猜猜猜= =

回去看string可以看到一个这个,猜测是密钥表之类的?

image-20201230123304290

往上回溯也看不到什么线索,不过可以发现这25个数字刚好没有相同的。

现在总结一下这个古典加密算法的特点,大概是两个为一组处理+已定义的密钥表(即不是通过输入生成的)5*5+处理时用到索引。

很久很久以前想写某对cp的AU同人时想把ctf元素混进去,就看了很多简单又奇奇怪怪的编码/古典密码(现代密码太学术了XD),没想到现在有用武之地了(手动狗头。

安利一个编码/古典密码的集合:CTF中那些脑洞大开的编码和加密 - jack_Meng - 博客园

然后翻到了一个符合这个特点的密码,Playfair Cipher:

image-20201230124909385

不同的是密码表是直接给出的,不过加密流程再对回ida里的反编译感觉挺像的,于是果断试试。

按照Playfair Cipher的加解密流程写出脚本:

def getIndex(c): for i in range(len(key)): if key[i].find(c)!=-1: return i,key[i].find(c) letter_list="ABCDEFGHJKLMNOPQRSTUVWXYZ" key=["CREIH","TQGNU","AOVXL","DZKYM","PBWFS"] cipher="KIMLXDWRZXTHXTHQTXTXHZWC" text="" for i in range(0,len(cipher),2): j=i+1 x1,y1=getIndex(cipher[i]) x2,y2=getIndex(cipher[j]) if x1==x2: text+=key[x1][(y1+1)%5]+key[x2][(y2+1)%5] elif y1==y2: text+=key[(x1+1)%5][y1]+key[(x2+1)%5][y2] else: text+=key[x1][y2]+key[x2][y1] print(text)

走一遍脚本解密可以得到:

image-20201230132557395

YES MAYBE YOU CAN RUN AN ARM PE

No, I can't 😦

看起来能读的通,成功get flag。

flag{YESMAYBEYOUCANRUNANARMPE}

crash

用binwalk分析给的crash文件

image-20210203135834833

可以看到0x1000处有一个 ELF, 32-bit LSB executable,先把这个可执行文件用dd分离出来。

image-20210203135922549

其中if=输入文件名(被分离的),of=输出文件名(分离出来的),skip=跳过的块(bs指定字节)数,count=块长度,最后得到file_0x1000文件。

用ida打开如果出现这个warning,可以用 Reinose/Useful-IDAScripts 的方法解决。

image-20210203140139308

老样子,通过shift+F12寻找字符串,通过交叉引用找到主函数。

image-20210203140825026

先通过一些参数和常用逻辑的猜测给系统函数命名,方便后续分析。

image-20210203141153998

然后就剩sub_8048737()sub_80487E7这两个加密函数了。

image-20210203141241346

image-20210203141308404

第一个就很简单啦,简单xor。

第二个点进sub_80489E9()可以看到md5经典常数,说明这一系列大概是个md5加密(当然md5的实现还是要看看的。

image-20210203143123837

退回去,看到0x804B060这里,很像数组的始地址。

image-20210203143333398

于是转到IDA View,按G跳到这个地址,可以看到一堆可见字符,按A转成字符串。

image-20210203143531099

image-20210203143612250

再对回去逻辑就很明显了,就是依次取四个字符进行md5加密,然后跟这八个字符串比。

于是exp就可以写啦,爆破md5:

# python2 from hashlib import md5 dst=['bf2b36d56f5757c13cad80494b385e78','3fe9dbae5dc4408350500affa20074aa','1fa6770eca6b57e47a042ffe52eca8ff','1aad6b7da1122b4b5a53bf5a4d3b11b0','e7b77d9e0ab19fc9ea98154f994fccc5','75d9128cfeb61b8949664f6a067f6469','d8b0a52c64d6075017b7346140550c46','306529c7cdedfb06e27b39f7b2babf4d'] res=[] for a in range(33,127): for b in range(33,127): for c in range(33,127): for d in range(33,127): l=[a^0x17,b^0x17,c^0x17,d^0x17] tmp=''.join(list(map(chr,l))) tmpmd5=md5(tmp).hexdigest() if tmpmd5 in dst: s=''.join(list(map(chr,[a,b,c,d]))) res.append((dst.index(tmpmd5),s)) if len(res)==8: break else: continue break else: continue break else: continue break res.sort(key=(lambda x:x[0])) flag=''.join([x[1] for x in res]) print(flag)

得到flag:

image-20210203145808703

flag{ux1cy1x4iltkahbc9nu1nk00d9akpp7w}


__EOF__

本文作者c10udlnk
本文链接https://www.cnblogs.com/c10udlnk/p/14214080.html
关于博主:欢迎关注我的个人博客-> https://c10udlnk.top/
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   c10udlnk  阅读(775)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示