首页
社区
课程
招聘
[原创]BYTECTF language bindings
2021-10-18 16:37 15151

[原创]BYTECTF language bindings

2021-10-18 16:37
15151

题目给了一个 exe 文件和一个 out 文件,猜测是使用 exe 去加载 out 的脚本。out 似乎被加密过了,看不出来是什么文件格式。

 

ida7.6 打开,exe 是 golang 写的,函数名都被混淆了。

 

 

因为 gopclntab 的 magic 是 0xFFFFFFFA ,所以肯定是 go 1.16 版本以上。这里和 go 1.17 的 bindiff 结果最接近,导入 Matched Functions ,恢复大部分 go 函数,找到 main_main。

 

题目直接运行提示 usage: new_lang binfile ,后面加上给的 out 文件的路径以后正常运行,所以下断 ReadFile。

 

回溯,runtime_asmstdcall 这类 go 内置函数直接略过,第一个混淆名字的函数 dazZVaWekDxmHOBm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void **__usercall dazZVaWekDxmHOBm@<rax>()
{
  __int64 v0; // rax
  __int64 v1; // rcx
  __int64 v2; // r8
  __int64 v3; // r14
  __int64 v4; // rax
  __int64 v5; // rcx
  __int64 v8; // [rsp-8h] [rbp-78h]
  __int64 v9; // [rsp-8h] [rbp-78h]
  __int64 v10; // [rsp+0h] [rbp-70h]
  __int64 v11; // [rsp+0h] [rbp-70h]
  __int64 v12; // [rsp+10h] [rbp-60h]
  __int64 v13; // [rsp+28h] [rbp-48h]
  void *retaddr; // [rsp+70h] [rbp+0h] BYREF
 
  while ( 1 )
  {
    if ( &retaddr <= *(v3 + 16) )
      goto LABEL_12;
    v10 = syscall___ptr_LazyProc__Find(v8);
    if ( !v4 )
      break;
    runtime_gopanic(v9, v10);
LABEL_12:
    v11 = v0;
    v12 = v1;
    v13 = v2;
    runtime_morestack_noctxt();
    v0 = v11;
    v1 = v12;
    v2 = v13;
  }
  if ( syscall_Syscall6() )
    return 0LL;
  if ( !v5 )
    return off_51FD10;
  if ( v5 == 997 )
    return off_51FD20;
  runtime_convT64(v9);
  return &off_580720;

经典 syscall_ptr_LazyProcFind ,还是处于 go 的函数中,继续往上,直到 hDwRGqXZOHitsyulULeUIiCTqaedCidWS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void hDwRGqXZOHitsyulULeUIiCTqaedCidWS()
{
  __int64 v0; // rax
  __int64 v1; // rcx
  __int64 v2; // rdi
  __int64 v3; // r14
  __int64 v4; // rbx
  __int64 v5; // rax
  __int64 i; // rdx
  __int64 v7; // [rsp-40h] [rbp-68h]
  __int64 v8; // [rsp-38h] [rbp-60h]
  __int64 v9; // [rsp-30h] [rbp-58h]
  __int64 v10; // [rsp-28h] [rbp-50h]
  __int64 v11; // [rsp-20h] [rbp-48h]
  __int64 v12; // [rsp-18h] [rbp-40h]
  void *retaddr; // [rsp+28h] [rbp+0h] BYREF
  __int64 v15; // [rsp+30h] [rbp+8h]
  __int64 v18; // [rsp+40h] [rbp+18h]
 
  while ( &retaddr <= *(v3 + 16) )
  {
    v15 = v0;
    v18 = v1;
    runtime_morestack_noctxt();
    v0 = v15;
    v1 = v18;
  }
  v4 = v1;
  v5 = PWTGSQGCyWF(); //v5 为 out 内容
  if ( !v2 )
  {
    for ( i = 0LL; v4 > i; ++i )
      *(v5 + i) ^= 0x55u; // out 每字节异或 0x55
    v7 = runtime_concatstring2();
    LnNZNVGrFxUawfmfFtLuzZZpawrA(v7, v8, v9, v10, v11, v12);
  }
}

解密以后就看出来是 lua ,但是文件的 magic 和版本信息好像不太对

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
__int64 __usercall LnNZNVGrFxUawfmfFtLuzZZpawrA@<rax>()
{
  __int64 v0; // rax
  __int64 v1; // rcx
  _DWORD *v2; // rbx
  __int64 v3; // r8
  _BYTE *v4; // r9
  __int64 v5; // r10
  __int64 v6; // r14
  __int64 v7; // rax
  _QWORD *v8; // rax
  __int64 v9; // rsi
  __int64 v10; // rax
  _QWORD *v11; // rax
  __int64 v12; // rdx
  __int64 v13; // rsi
  __int64 v14; // rdx
  __int64 v15; // rbx
  __int64 *v16; // rax
  _QWORD *v17; // rax
  __int64 *v18; // rsi
  unsigned __int64 v19; // r8
  __int64 v20; // r9
  unsigned __int64 v21; // rcx
  __int64 v22; // r8
  __int64 v24; // [rsp-26h] [rbp-80h]
  __int64 v25; // [rsp-26h] [rbp-80h]
  __int64 v26; // [rsp-26h] [rbp-80h]
  char v27; // [rsp-26h] [rbp-80h]
  __int64 v28; // [rsp-1Eh] [rbp-78h]
  __int64 v29; // [rsp+Ah] [rbp-50h]
  __int64 v30; // [rsp+12h] [rbp-48h]
  _QWORD *v31; // [rsp+1Ah] [rbp-40h]
  _QWORD *v32; // [rsp+22h] [rbp-38h]
  _QWORD v33[4]; // [rsp+2Ah] [rbp-30h] BYREF
  void (**v34)(void); // [rsp+4Ah] [rbp-10h]
  void *retaddr; // [rsp+5Ah] [rbp+0h] BYREF
  __int64 v37; // [rsp+62h] [rbp+8h]
  __int64 v38; // [rsp+62h] [rbp+8h]
  __int64 v41; // [rsp+72h] [rbp+18h]
  __int64 v45; // [rsp+8Ah] [rbp+30h]
  _BYTE *v46; // [rsp+92h] [rbp+38h]
  __int64 v47; // [rsp+9Ah] [rbp+40h]
 
  while ( &retaddr <= *(v6 + 16) )
  {
    ......
  }
  v37 = v0;
  v33[0] = oIrjHUemhkTuYesiVzPoKmpOwAxNduMvhL;
  v33[1] = v0;
  v34 = v33;
  if ( v1 > 4 && *v2 == 0x77654EAA )//修改过的 LUA_SIGNATURE
  {
    if ( v5 != 1 || *v4 != 116 )
    {
      ZjulCvXVFufJHreFwkLSR();//以 luac 模式加载
      goto LABEL_10;
    }
LABEL_43:
    v27 = runtime_gopanic();
    runtime_deferreturn(v27);
    return 3LL;
  }
  if ( v5 == 1 && *v4 == 98 )
  {
    runtime_gopanic();
    goto LABEL_43;
  }
  v24 = runtime_slicebytetostring();
  v28 = jSJunBFoJhTHsNqpeUQxzn(v24);
LABEL_10:
  v30 = v7;
  v25 = runtime_newobject();
......

函数 ZjulCvXVFufJHreFwkLSR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void __golang ZjulCvXVFufJHreFwkLSR()
{
  __int64 v0; // rax
  __int64 v1; // rcx
  __int64 v2; // rbx
  __int64 v3; // r14
  __int64 v4; // [rsp-18h] [rbp-38h]
  __int64 v5; // [rsp-10h] [rbp-30h]
  void *retaddr; // [rsp+20h] [rbp+0h] BYREF
  __int64 v7; // [rsp+28h] [rbp+8h]
  __int64 v8; // [rsp+38h] [rbp+18h]
 
  while ( &retaddr <= *(v3 + 16) )
  {
    ......
  }
  v4 = MDGKlXiFeGzaITATislDNVCASdsieREOQofO();//检查文件头,注意 lua 版本被改成了 17
  if ( !v2 )
    runtime_panicIndex();
  PhtvIcXnnZQDYbqGLutONyDkaSpvgYsoaT(v4, v5);// 读 lua 的 prototype 信息
}

这里确认是 lua,因为 size_t size 是 8,所以要编译 64 位的 luadec ,但是反编译不出来,怀疑替换了 opcode_table。

 

程序里有 luago 的字符串, github 搜了一下,找到了源码,https://github.com/zxh0/lua.go 。 git 上有很多 luago 的版本,可是都没实现 stdlib 的 string.dump 函数,而 exe 可以用这个函数,用这个判断。

 

LnNZNVGrFxUawfmfFtLuzZZpawrA 就是项目中 api_call.go 的 Load 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func dostring(L LuaState, s, name string) int {
    return dochunk(L, lua_load(L, []byte(s), name, "bt"))// lua_load 封装了 Load, 加载 lua 代码后用 dochunk 运行
}
/*
dochunk -> docall -> lua_pcall -> PCall -> Call -> callLuaClosure -> runLuaClosure
*/
func (state *luaState) runLuaClosure() {
    for {
        inst := vm.Instruction(state.Fetch())
        inst.Execute(state)
        if inst.Opcode() == vm.OP_RETURN {
            break
        }
    }
}
 
func (instr Instruction) Execute(vm api.LuaVM) {
    opcodes[instr.Opcode()].action(instr, vm)//注意这里的 opcodes
}

尝试三种方法找到 opcode_table 的位置:

  1. 搜索字符串 MOVE 等,失败(因为出题人把 opcode 的名称都改成了 NONNAME)

  2. 根据函数调用关系找到 Execute,得到 opcodes

  3. 在 LnNZNVGrFxUawfmfFtLuzZZpawrA (Load)函数返回后,对于每一个 call ,都 F8 ,如果跑飞了,那说明 Execute 在这个 call 里,重复这个过程缩小范围(感觉有时间可以写个小工具,还是挺实用的方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
__int64 __usercall swPNnkHIjlVmdLQqsILDPLymAPtAGakBvGoiL@<rax>() // Execute
{
  __int64 v0; // rax
  __int64 v1; // r14
  __int64 v2; // rsi
  __int64 v3; // rdi
  unsigned __int64 v4; // rcx
  __int64 v5; // rdi
  unsigned __int64 v6; // r8
  int v7; // edi
  unsigned __int64 v8; // rdi
  __int64 result; // rax
  void *retaddr; // [rsp+Ch] [rbp+0h] BYREF
  __int64 i; // [rsp+14h] [rbp+8h]
  __int64 v13; // [rsp+14h] [rbp+8h]
 
  while ( &retaddr <= *(v1 + 16) )
  {
    v13 = v0;
    runtime_morestack_noctxt();
    v0 = v13;
  }
  for ( i = v0; ; v0 = i )
  {
    v2 = *(v0 + 32);    //state.Fetch()
    v3 = **(v2 + 40);
    v4 = *(v3 + 40);
    v5 = *(v3 + 32);
    v6 = *(v2 + 80);
    if ( v4 <= v6 )
      runtime_panicIndex();
    v7 = *(v5 + 4 * v6);
    *(v2 + 80) = v6 + 1;
    v8 = v7 & 0x3F//inst := vm.Instruction(state.Fetch())
    if ( qword_51FD98 <= v8 )
      runtime_panicIndex();
    result = (**(off_51FD90 + 4 * v8 + 3))(); // opcodes[instr.Opcode()].action(instr, vm)
    if ( v8 == 42LL )
      break;
  }
  return result;
}

off_51FD90 即为 opcode_table 的指针。

 

 

红框里的为一组 opcode 的结构,大小 0x20,opcode 的名称被出题人都改成一样的了。这里没法从这判断顺序。尝试两种方法:

  1. 编译 lua.go 然后 bindiff,根据每个 opcode 的处理函数来识别(失败,similarity 总是 0.0几,不知道为啥)
  2. https://bbs.pediy.com/thread-250618.htm

特别感谢这位老哥,思路非常牛逼,让相同的代码在题目和我们自己编译的 lua.go 中跑,dump 出两个代码,进行对比,得到 opcode 的顺序。

 

我修改了一下,帮他补上了 lua 5.3 版本新增的 opcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
-- test.lua
function test()
    boy = {money = 200, t = 200}
    local t0 = 0x10
    t0 = t0 // 10 -- idiv
    function boy.goToMarket(self ,someMoney)
        self.money = self.money - someMoney
        self.money = self.t
    end
    boy:goToMarket(100)
    local t1 = 1
    local t2 = t1
    local t3 = t2 and t1 or t1
    t1 = nil
    t1 = true
    t2 = (-(t1)) - t1 * t2 / t2 % t2 ^ t2 + t2
    t3 = {}
    t3 = {"foo","bar"}
    for k , _ in t3 do
    end
    for i=10,1,-1 do
        t2 = t1 ^ t3
        t2 = t2 ^ t1 ^ 0x66
        t2 = t1 & t3
        t2 = ~t2
        t2 = t1 | t3
        t2 = t2 << 2
        t2 = t2 >> 2
    end
    function f0(...)
    end
    function f1(n)
        function f2(...)
            f0(...)
            n = 200
            print(#n .. "foobar")
            if not t2 then
                t1 = t1 > t2
                t1 = t2 <= t1
                t1 = t2 == t1
                t1 = t2 ~=t1
                t1 = not t2
            else
            end
        end
        return f2(300)
    end
    for k=1, f1(3) do
    end
end
 
return test

check_opcode.lua 要小改一下,主要把 5.1 的 opcode 换成 5.3 的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
local bit = require('bit')
local test = require('test')
 
--local fp = io.open("test.luac","rb")
--local ori_data = fp:read("*all")
--fp:close()
local ori_data = {}
-- print('data len '.. #data)
-- print('ori_data len ' .. #ori_data)
 
-- 因为题目不支持 io.read 所以要对 check_opcode.lua 进行修改
-- 把读取注释掉,换成 local ori_data = {} , 然后我们手动读入 opc.lua 并进行替换。
 
local ori_op_name = {
    "OP_MOVE",
    "OP_LOADK",
    "OP_LOADKX",
    "OP_LOADBOOL",
    "OP_LOADNIL",
    "OP_GETUPVAL",
    "OP_GETTABUP",
    "OP_GETTABLE",
    "OP_SETTABUP",
    "OP_SETUPVAL",
    "OP_SETTABLE",
    "OP_NEWTABLE",
    "OP_SELF",
    "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_CONCAT",
    "OP_JMP",
    "OP_EQ",
    "OP_LT",
    "OP_LE",
    "OP_TEST",
    "OP_TESTSET",
    "OP_CALL",
    "OP_TAILCALL",
    "OP_RETURN",
    "OP_FORLOOP",
    "OP_FORPREP",
    "OP_TFORCALL",
    "OP_TFORLOOP",
    "OP_SETLIST",
    "OP_CLOSURE",
    "OP_VARARG",
    "OP_EXTRAARG"
}
local data = string.dump(test)
local new_op = {    ["OP_MOVE"] = -1,
["OP_LOADK"] = -1,
["OP_LOADKX"] = -1,
["OP_LOADBOOL"] = -1,
["OP_LOADNIL"] = -1,
["OP_GETUPVAL"] = -1,
["OP_GETTABUP"] = -1,
["OP_GETTABLE"] = -1,
["OP_SETTABUP"] = -1,
["OP_SETUPVAL"] = -1,
["OP_SETTABLE"] = -1,
["OP_NEWTABLE"] = -1,
["OP_SELF"] = -1,
["OP_ADD"] = -1,
["OP_SUB"] = -1,
["OP_MUL"] = -1,
["OP_MOD"] = -1,
["OP_POW"] = -1,
["OP_DIV"] = -1,
["OP_IDIV"] = -1,
["OP_BAND"] = -1,
["OP_BOR"] = -1,
["OP_BXOR"] = -1,
["OP_SHL"] = -1,
["OP_SHR"] = -1,
["OP_UNM"] = -1,
["OP_BNOT"] = -1,
["OP_NOT"] = -1,
["OP_LEN"] = -1,
["OP_CONCAT"] = -1,
["OP_JMP"] = -1,
["OP_EQ"] = -1,
["OP_LT"] = -1,
["OP_LE"] = -1,
["OP_TEST"] = -1,
["OP_TESTSET"] = -1,
["OP_CALL"] = -1,
["OP_TAILCALL"] = -1,
["OP_RETURN"] = -1,
["OP_FORLOOP"] = -1,
["OP_FORPREP"] = -1,
["OP_TFORCALL"] = -1,
["OP_TFORLOOP"] = -1,
["OP_SETLIST"] = -1,
["OP_CLOSURE"] = -1,
["OP_VARARG"] = -1,
["OP_EXTRAARG"] = -1}
for i = 6, #data do
    local by_ori = ori_data[i] --string.byte(ori_data,i)
    -- 改后这里的 ori_data 是数组了
    local by_new = string.byte(data,i)
    if by_ori ~= by_new then
        local idx = bit:_and(0x3F,by_ori)
        if idx < 47 then -- 限制一个范围,如果超出了 opcode 的数量就说明这个变化有可能不是 opcode 被换导致的
            local op_name = ori_op_name[idx + 1]
            local op_idx = bit:_and(0x3F,by_new)
            new_op[op_name] = op_idx
        end
    end
end
 
print("old \t new \t name")
for idx, op_name in pairs(ori_op_name) do
    local tmp = ''
    tmp = new_op[op_name]
    print((idx - 1) .. "\t" .. tmp .. "\t" .. op_name )
end

也要注意这句,因为我们的文件头和版本和原来肯定是不一样的,不要把他们也算在 opcode 的变化里了

1
for i = 6, #data do

写了个小脚本用来试 opcode(毕竟一次很难补全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 代码很乱
import os
def get_fucked(file_name):
    f = open('./'+file_name,'r')
    cont = f.read(-1)
    out = b''
    for i in range(len(cont)):
        out += (ord(cont[i])^0x55).to_bytes(1,byteorder='little')
    open('./new/'+file_name,'wb').write(out) # 题目异或了 0x55,我们也要异或,这里的 new 文件夹下放题目的 new_lang.exe
 
os.system('lua.exe get_code.lua > opc.lua')
'''
lua.exe 是我自己编译的 lua.go
get_code.lua:
 
local test = require('test')
local data = string.dump(test)
print(data)
 
写入到 opc.lua 中
'''
opc = open('./opc.lua','rb').read(-1)
cont_to_write = 'local ori_data = {' + str(list(opc))[:-1][1:] + '}'
hack = open('./check_opcode.lua','r').read(-1)
open('./check_opcode2.lua','w').write(hack.replace('local ori_data = {}',cont_to_write)) # 将 opc.lua 的内容写入 check_opcode2.lua 的 ori_data 变量
get_fucked('check_opcode2.lua')
get_fucked('test.lua')
os.system('cd .\\new && new_lang.exe check_opcode2.lua > res.txt') # 把输出写入 res.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
res.txt
 
old      new      name
0    30    OP_MOVE
1    45    OP_LOADK
2    -1    OP_LOADKX
3    16    OP_LOADBOOL
4    18    OP_LOADNIL
5    35    OP_GETUPVAL
6    13    OP_GETTABUP
7    33    OP_GETTABLE
8    28    OP_SETTABUP
9    11    OP_SETUPVAL
10    8    OP_SETTABLE
11    6    OP_NEWTABLE
12    40    OP_SELF
13    9    OP_ADD
14    15    OP_SUB
15    27    OP_MUL
16    37    OP_MOD
17    36    OP_POW
18    38    OP_DIV
19    32    OP_IDIV
20    46    OP_BAND
21    26    OP_BOR
22    -1    OP_BXOR
23    20    OP_SHL
24    7    OP_SHR
25    0    OP_UNM
26    39    OP_BNOT
27    25    OP_NOT
28    14    OP_LEN
29    34    OP_CONCAT
30    31    OP_JMP
31    29    OP_EQ
32    23    OP_LT
33    41    OP_LE
34    21    OP_TEST
35    2    OP_TESTSET
36    24    OP_CALL
37    10    OP_TAILCALL
38    42    OP_RETURN
39    43    OP_FORLOOP
40    19    OP_FORPREP
41    17    OP_TFORCALL
42    5    OP_TFORLOOP
43    1    OP_SETLIST
44    3    OP_CLOSURE
45    44    OP_VARARG
46    -1    OP_EXTRAARG

这里有几个 -1 (opcode 没有使用或者和原来一样),我们手动处理一下,首先 OP_LOADKX 和 OP_EXTRAARG 直接跟原来的值一样即可,因为根本不会用到,中间的 BXOR 是因为跟原来一样。

 

然后修改 luadec 中的 lopcodes.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef enum {
    OP_UNM,
    OP_SETLIST,
    OP_TESTSET,
    OP_CLOSURE,
    OP_LOADKX,
    OP_TFORLOOP,
    OP_NEWTABLE,
    OP_SHR,
    OP_SETTABLE,
    OP_ADD,
    OP_TAILCALL,
    OP_SETUPVAL,
    OP_EXTRAARG,
    OP_GETTABUP,
    OP_LEN,
    OP_SUB,
    OP_LOADBOOL,
    OP_TFORCALL,
    OP_LOADNIL,
    OP_FORPREP,
    OP_SHL,
    OP_TEST,
    OP_BXOR,
    OP_LT,
    OP_CALL,
    OP_NOT,
    OP_BOR,
    OP_MUL,
    OP_SETTABUP,
    OP_EQ,
    OP_MOVE, OP_JMP, OP_IDIV, OP_GETTABLE, OP_CONCAT, OP_GETUPVAL, OP_POW, OP_MOD, OP_DIV, OP_BNOT, OP_SELF, OP_LE, OP_RETURN, OP_FORLOOP, OP_VARARG, OP_LOADK, OP_BAND
} OpCode;

编译 luadec ,把 new_lang_script.out 的文件头改一下 LuaS,反编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
print("input flag:")
flag = (io.read)()
if #flag ~= 29 then
  print("flag is wrong")
  return
end
lst = {100, 120, 133}
dict = {[9] = 101, [10] = 122}
ad = function(a, b)
  -- function num : 0_0
  return a + b
end
 
mul = function(a, b)
  -- function num : 0_1
  return a * b
end
 
check2 = function(f)
  -- function num : 0_2 , upvalues : _ENV
  if (string.byte)(f, 18) + 11 ~= 106 then
    return false
  elseif (string.byte)(f, 19) ~= lst[1] + 21 then
    return false
  elseif (string.byte)(f, 20) ~= dict[9] + 8 then
    return false
  elseif (string.byte)(f, 21) ~= ad(100, 22) then
    return false
  elseif (string.byte)(f, 22) ~= 55 then
    return false
  elseif (string.byte)(f, 23) ~= mul(51, 2) then
    return false
  elseif (string.byte)(f, 24) - 1 ~= 108 then
    return false
  elseif (string.byte)(f, 25) ~= 48 then
    return false
  elseif (string.byte)(f, 26) ~= 100 then
    return false
  elseif (string.byte)(f, 27) ~= 102 then
    return false
  elseif (string.byte)(f, 28) ~= 120 then
    return false
  end
  do return true end
  -- DECOMPILER ERROR: 22 unprocessed JMP targets
end
 
check3 = function(f)
  -- function num : 0_3 , upvalues : _ENV
  if (string.byte)(f, 1) ~= 66 then
    return false
  elseif (string.byte)(f, 2) ~= 121 then
    return false
  elseif (string.byte)(f, 3) ~= 116 then
    return false
  elseif (string.byte)(f, 4) ~= 101 then
    return false
  elseif (string.byte)(f, 5) ~= 67 then
    return false
  elseif (string.byte)(f, 6) ~= 84 then
    return false
  elseif (string.byte)(f, 7) ~= 70 then
    return false
  elseif (string.byte)(f, 8) ~= 123 then
    return false
  elseif (string.byte)(f, 29) ~= 125 then
    return false
  end
  do return true end
  -- DECOMPILER ERROR: 18 unprocessed JMP targets
end
 
local flag1 = _(flag)
local flag2 = check2(flag)
local flag3 = check3(flag)
if not not flag1 or flag2 or flag3 then
  print("flag is right")
elseif true then
  print("flag is wrong")
end
-- DECOMPILER ERROR: 6 unprocessed JMP targets

逻辑很清楚,但是第 9 位到第 17 位这 9 位 flag 的验证缺失,是在 local flag1 = _(flag) 验证的,应该是个 native 函数。

 

找到 Execute,对运行指令的位置下断点,一直 F9,直到 flag 读入,对第 9 位下断点,断两次以后来到关键函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
__int64 __usercall AXjIVygOebTSlrNJffL@<rax>()  //fuck_check
{
  __int64 v0; // rax
  __int64 v1; // r14
  unsigned __int64 v2; // rbx
  __int64 i; // rcx
  _BYTE v5[10]; // [rsp+8h] [rbp-32h] BYREF
  void *retaddr; // [rsp+3Ah] [rbp+0h] BYREF
  __int64 v8; // [rsp+42h] [rbp+8h]
  __int64 v9; // [rsp+42h] [rbp+8h]
 
  while ( &retaddr <= *(v1 + 16) )
  {
    v9 = v0;
    runtime_morestack_noctxt();
    v0 = v9;
  }
  v8 = v0;
  qmemcpy(v5, "9negozc9aj", sizeof(v5));
  v2 = (*(v0 + 1000))();
  nnsoTqLPqCsbjvyfujzSUtXzr();
  for ( i = 8LL; i < 17; ++i )
  {
    if ( v2 <= i )
      runtime_panicIndex();
  }
  (*(v8 + 624))();
  return 1LL;
}

就是个异或,然后把 flag 拼起来就行了。

 

总结:学到了很多,下次出题把 lua 明文执行的接口删掉


阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

最后于 2022-1-20 16:01 被v0id_编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (7)
雪    币: 8731
活跃值: (5493)
能力值: ( LV13,RANK:296 )
在线值:
发帖
回帖
粉丝
sunfishi 4 2021-10-18 17:14
2
0
mark! 学习了
雪    币: 8297
活跃值: (4831)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
v0id_ 2021-10-18 17:34
3
0
不小心发错版了,版主能帮我移动到软件逆向版吗
雪    币: 1688
活跃值: (2619)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
Forgo7ten 2021-10-18 19:18
4
0
Void爷,膜
雪    币: 1107
活跃值: (486)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Golbeze 2021-10-21 17:24
5
0
tql
雪    币: 9587
活跃值: (1480)
能力值: ( LV10,RANK:164 )
在线值:
发帖
回帖
粉丝
CrazymanArmy 2 2021-11-17 09:37
6
0
Void爷,膜
雪    币: 92
活跃值: (209)
能力值: ( LV6,RANK:95 )
在线值:
发帖
回帖
粉丝
谢逅 2 2022-3-8 18:53
7
0
老哥缺工作吗?
雪    币: 6462
活跃值: (10654)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
Nameless_a 5 2022-4-27 15:47
8
0

Void爷,tql

最后于 2022-4-27 15:48 被Nameless_a编辑 ,原因:
游客
登录 | 注册 方可回帖
返回