L3HCTF luuuuua

L3HCTF luuuuua 的题解,非预期警告

java 层感觉看不出什么,直接从 native 层开始

libluajava

​ 一个实现安卓调用 lua 的库,首先寻找加载 lua 字节码的函数 lua_loadfile(反编译后没有这个函数名,根据其他参考项目识别出)

参考项目:
https://github.com/jasonsantos/luajava
https://github.com/mkottman/AndroLua
https://github.com/LuaDist/luajava

(最后一个是题目中用到的库,但是赛时编译不出来就没用到)

​ 通过动态调试发现程序加载了 assets 目录下的 logo.jpg,并对其进行解密

​ 从下图的 fseek 可以得到开始加载的文件偏移

​ 之后就是解出 luac 文件

字节码替换

逆向虚拟机(分析不出来)

​ 直接解出来的 luac 文件无法反编译,猜测又是进行了字节码的替换。从库里面能发现可能替换的顺序

​ 按这样子替换还是无法正确反编译,猜测程序内部虚拟机还改了顺序,但是这个虚拟机有点复杂(主要是源码没编译出来),当时分析了半天也没分析出正确的替换顺序

根据特征手动替换op(非预期)

​ 比较 assets 里的 test.lua 与 解密出来的 luac 中的字符串,发现两者都使用了一个 base64 模块,那么就可以根据 test.lua 编译成的字节码进行替换

​ 光按能对照出来的替换有些函数会反编译不完全,所以还要根据反编译时的错误进行猜测(可以对着像是 tea 类加密的函数猜)

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
MOVE ADD
LOADK SUB
LOADKX MUL
LOADBOOL MOD
LOADNIL POW
GETUPVAL FORLOOP
GETTABUP FORPREP
GETTABLE BAND
SETTABUP SETTABUP
SETUPVAL BXOR
SETTABLE SHL
NEWTABLE SHR
SELF EXTRAARG
ADD MOVE
SUB LOADK
MUL LEN
MOD MOVE
POW CONCAT
DIV JMP
IDIV EQ
BAND LT
BOR LE
BXOR TEST
SHL TESTSET
SHR CALL
UNM TAILCALL
BNOT RETURN
NOT FORLOOP
LEN FORPREP
CONCAT TFORCALL
JMP TFORLOOP
EQ SETLIST
LT CLOSURE
LE BOR
TEST BXOR
TESTSET LOADK
CALL SHR
TAILCALL LOADBOOL
RETURN LOADNIL
FORLOOP GETUPVAL
FORPREP GETTABUP
TFORCALL GETTABLE
TFORLOOP JMP
SETLIST EQ
CLOSURE SETTABLE
VARARG NEWTABLE
EXTRAARG SELF

粗略换一下
用 luadec 里的 luaopswap 替换 op

​ 最终得到以下的反编译结果

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
-- params : ...
-- function num : 0 , upvalues : _ENV
local base64 = {}
if _G.bit32 then
local extract = (_G.bit32).extract
end
if not extract then
if _G.bit then
local shl, shr, band = (_G.bit).lshift, (_G.bit).rshift, (_G.bit).band
do
extract = function(v, from, width)
-- function num : 0_0 , upvalues : band, shr, shl
return band(shr(v, from), shl(1, width) - 1)
end

end
else
do
if _G._VERSION == "Lua 5.1" then
extract = function(v, from, width)
-- function num : 0_1
local w = 0
local flag = 2 ^ from
for i = 0, width - 1 do
local flag2 = flag + flag
if flag <= v % flag2 then
w = w + 2 ^ i
end
flag = flag2
end
return w
end

else
extract = (load("return function( v, from, width )\n\t\t\treturn ( v >> from ) & ((1 << width) - 1)\n\t\tend"))()
end
base64.makeencoder = function(s62, s63, spad)
-- function num : 0_2 , upvalues : _ENV
local encoder = {}
for b64code,char in pairs({"B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", s62 or "+", s63 or "/", spad or "="; [0] = "A"}) do
encoder[b64code] = char:byte()
end
return encoder
end

base64.makedecoder = function(s62, s63, spad)
-- function num : 0_3 , upvalues : _ENV, base64
local decoder = {}
for b64code,charcode in pairs((base64.makeencoder)(s62, s63, spad)) do
decoder[charcode] = b64code
end
return decoder
end

local DEFAULT_ENCODER = (base64.makeencoder)()
local DEFAULT_DECODER = (base64.makedecoder)()
local char, concat = string.char, table.concat
base64.encode = function(str, encoder, usecaching)
-- function num : 0_4 , upvalues : DEFAULT_ENCODER, char, extract, concat
if not encoder then
encoder = DEFAULT_ENCODER
end
local t, k, n = {}, 1, #str
local lastn = n % 3
local cache = {}
for i = 1, n - lastn, 3 do
local a, b, c = str:byte(i, i + 2)
local v = a * 65536 + b * 256 + c
local s = nil
if usecaching then
s = cache[v]
if not s then
s = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[extract(v, 0, 6)])
cache[v] = s
end
else
s = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[extract(v, 0, 6)])
end
t[k] = s
k = k + 1
end
if lastn == 2 then
local a, b = str:byte(n - 1, n)
local v = a * 65536 + b * 256
t[k] = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[64])
else
do
do
if lastn == 1 then
local v = str:byte(n) * 65536
t[k] = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[64], encoder[64])
end
return concat(t)
end
end
end
end

base64.decode = function(b64, decoder, usecaching)
-- function num : 0_5 , upvalues : DEFAULT_DECODER, _ENV, char, extract, concat
if not decoder then
decoder = DEFAULT_DECODER
end
local pattern = "[^%w%+%/%=]"
do
if decoder then
local s62, s63 = nil, nil
for charcode,b64code in pairs(decoder) do
if b64code == 62 then
s62 = charcode
else
if b64code == 63 then
s63 = charcode
end
end
end
pattern = ("[^%%w%%%s%%%s%%=]"):format(char(s62), char(s63))
end
b64 = b64:gsub(pattern, "")
if usecaching then
local cache = {}
end
local t, k = {}, 1
local n = #b64
local padding = (b64:sub(-2) == "==" and 2) or (b64:sub(-1) == "=" and 1) or 0
for i = 1, padding > 0 and n - 4 or n, 4 do
local a, b, c, d = b64:byte(i, i + 3)
local s = nil
if usecaching then
local v0 = a * 16777216 + b * 65536 + c * 256 + d
s = cache[v0]
if not s then
local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64 + decoder[d]
s = char(extract(v, 16, 8), extract(v, 8, 8), extract(v, 0, 8))
cache[v0] = s
end
else
do
do
do
local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64 + decoder[d]
s = char(extract(v, 16, 8), extract(v, 8, 8), extract(v, 0, 8))
t[k] = s
k = k + 1
-- DECOMPILER ERROR at PC143: LeaveBlock: unexpected jumping out DO_STMT

-- DECOMPILER ERROR at PC143: LeaveBlock: unexpected jumping out DO_STMT

-- DECOMPILER ERROR at PC143: LeaveBlock: unexpected jumping out IF_ELSE_STMT

-- DECOMPILER ERROR at PC143: LeaveBlock: unexpected jumping out IF_STMT

end
end
end
end
end
if padding == 1 then
local a, b, c = b64:byte(n - 3, n - 1)
local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64
t[k] = char(extract(v, 16, 8), extract(v, 8, 8))
else
do
if padding == 2 then
local a, b = b64:byte(n - 3, n - 2)
local v = decoder[a] * 262144 + decoder[b] * 4096
t[k] = char(extract(v, 16, 8))
end
do
return concat(t)
end
end
end
end
end

local strf = string.format
local byte, char = string.byte, string.char
local spack, sunpack = string.pack, string.unpack
local app, concat = table.insert, table.concat
local stohex = function(s, ln, sep)
-- function num : 0_6 , upvalues : strf, byte, concat
if #s == 0 then
return ""
end
if not ln then
return s:gsub(".", function(c)
-- function num : 0_6_0 , upvalues : strf, byte
return strf("%02x", byte(c))
end
)
end
if not sep then
sep = ""
end
local t = {}
for i = 1, #s - 1 do
t[#t + 1] = strf("%02x%s", s:byte(i), i % ln == 0 and "\n" or sep)
end
t[#t + 1] = strf("%02x", s:byte(#s))
return concat(t)
end

local hextos = function(hs, unsafe)
-- function num : 0_7 , upvalues : _ENV, char
local tonumber = tonumber
if not unsafe then
hs = (string.gsub)(hs, "%s+", "")
if (string.find)(hs, "[^0-9A-Za-z]") or #hs % 2 ~= 0 then
error("invalid hex string")
end
end
return hs:gsub("(%x%x)", function(c)
-- function num : 0_7_0 , upvalues : char, tonumber
return char(tonumber(c, 16))
end
)
end

local stx = stohex
local xts = hextos
local ROUNDS = 64
local keysetup = function(key)
-- function num : 0_8 , upvalues : _ENV, sunpack, ROUNDS
assert(#key == 16)
local kt = {0, 0, 0, 0}
kt[1] = sunpack(">I4I4I4I4", key)
local skt0 = {}
local skt1 = {}
local sum, delta = 0, 2654435769
for i = 1, ROUNDS do
skt0[i] = sum + kt[(sum & 3) + 1]
sum = sum + delta & 4294967295
skt1[i] = (sum) + kt[((sum) >> 11 & 3) + 1]
end
do return {skt0 = skt0, skt1 = skt1} end
-- DECOMPILER ERROR: 1 unprocessed JMP targets
end

local encrypt_u64 = function(st, bu)
-- function num : 0_9 , upvalues : ROUNDS
local skt0, skt1 = st.skt0, st.skt1
local v0, v1 = bu >> 32, bu & 4294967295
local sum, delta = 0, 2654435769
for i = 1, ROUNDS do
v0 = v0 + ((v1 << 4 ~ v1 >> 5) + v1 ~ skt0[i]) & 4294967295
v1 = v1 + (((v0) << 4 ~ (v0) >> 5) + (v0) ~ skt1[i]) & 4294967295
end
bu = (v0) << 32 | v1
return bu
end

local enc = function(key, iv, itxt)
-- function num : 0_10 , upvalues : _ENV, sunpack, keysetup, encrypt_u64, spack, app, concat
assert(#key == 16, "bad key length")
assert(#iv == 8, "bad IV length")
if #itxt == 0 then
return ""
end
local ivu = sunpack("<I8", iv)
local ot = {}
local rbn = #itxt
local ksu, ibu, ob = nil, nil, nil
local st = keysetup(key)
for i = 1, #itxt, 8 do
ksu = encrypt_u64(st, ivu ~ i)
if rbn < 8 then
local buffer = (string.sub)(itxt, i) .. (string.rep)("\000", 8 - rbn)
ibu = sunpack("<I8", buffer)
ob = (string.sub)(spack("<I8", ibu ~ ksu), 1, rbn)
else
ibu = sunpack("<I8", itxt, i)
ob = spack("<I8", ibu ~ ksu)
rbn = rbn - 8
end
app(ot, ob)
end
do return concat(ot) end
-- DECOMPILER ERROR: 5 unprocessed JMP targets
end

-- WARNING: undefined locals caused missing assignments!
-- DECOMPILER ERROR: 1 unprocessed JMP targets
end
end
end

​ 可以看出实际上是对输入进行一个异或,抄一下反编译出的函数解密就行

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
local base64 = {}

local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode
if not extract then
if _G.bit then -- LuaJIT
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
extract = function( v, from, width )
return band( shr( v, from ), shl( 1, width ) - 1 )
end
elseif _G._VERSION == "Lua 5.1" then
extract = function( v, from, width )
local w = 0
local flag = 2^from
for i = 0, width-1 do
local flag2 = flag + flag
if v % flag2 >= flag then
w = w + 2^i
end
flag = flag2
end
return w
end
else -- Lua 5.3+
extract = load[[return function( v, from, width )
return ( v >> from ) & ((1 << width) - 1)
end]]()
end
end


function base64.makeencoder( s62, s63, spad )
local encoder = {}
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
encoder[b64code] = char:byte()
end
return encoder
end

function base64.makedecoder( s62, s63, spad )
local decoder = {}
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
decoder[charcode] = b64code
end
return decoder
end

local DEFAULT_ENCODER = base64.makeencoder()
local DEFAULT_DECODER = base64.makedecoder()

local char, concat = string.char, table.concat

function base64.encode( str, encoder, usecaching )
encoder = encoder or DEFAULT_ENCODER
local t, k, n = {}, 1, #str
local lastn = n % 3
local cache = {}
for i = 1, n-lastn, 3 do
local a, b, c = str:byte( i, i+2 )
local v = a*0x10000 + b*0x100 + c
local s
if usecaching then
s = cache[v]
if not s then
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
cache[v] = s
end
else
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
end
t[k] = s
k = k + 1
end
if lastn == 2 then
local a, b = str:byte( n-1, n )
local v = a*0x10000 + b*0x100
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
elseif lastn == 1 then
local v = str:byte( n )*0x10000
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
end
return concat( t )
end

function base64.decode( b64, decoder, usecaching )
decoder = decoder or DEFAULT_DECODER
local pattern = '[^%w%+%/%=]'
if decoder then
local s62, s63
for charcode, b64code in pairs( decoder ) do
if b64code == 62 then s62 = charcode
elseif b64code == 63 then s63 = charcode
end
end
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
end
b64 = b64:gsub( pattern, '' )
local cache = usecaching and {}
local t, k = {}, 1
local n = #b64
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
for i = 1, padding > 0 and n-4 or n, 4 do
local a, b, c, d = b64:byte( i, i+3 )
local s
if usecaching then
local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d
s = cache[v0]
if not s then
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
cache[v0] = s
end
else
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
end
t[k] = s
k = k + 1
end
if padding == 1 then
local a, b, c = b64:byte( n-3, n-1 )
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
t[k] = char( extract(v,16,8), extract(v,8,8))
elseif padding == 2 then
local a, b = b64:byte( n-3, n-2 )
local v = decoder[a]*0x40000 + decoder[b]*0x1000
t[k] = char( extract(v,16,8))
end
return concat( t )
end


function encrypt_u64(st, bu)
local skt0, skt1 = st.skt0, st.skt1
local v0, v1 = bu >> 32, bu & 4294967295
local sum, delta = 0, 2654435769
for i = 1, 64 do
v0 = v0 + (((v1) << 4 ~ (v1) >> 5) + (v1) ~ skt0[i]) & 4294967295
v1 = v1 + (((v0) << 4 ~ (v0) >> 5) + (v0) ~ skt1[i]) & 4294967295
end
bu = (v0) << 32 | v1
return bu
end

function keysetup(key)
assert(#key == 16)
local kt = {0, 0, 0, 0}
kt[1],kt[2],kt[3],kt[4] = string.unpack(">I4I4I4I4", key)
local skt0 = {}
local skt1 = {}
local sum, delta = 0, 2654435769
for i = 1, 64 do
skt0[i] = sum + kt[(sum & 3) + 1]
sum = sum + delta & 4294967295
skt1[i] = (sum) + kt[((sum) >> 11 & 3) + 1]
end
do return {skt0 = skt0, skt1 = skt1} end
end

function dec(key, iv, itxt)
assert(#key == 16, "bad key length")
assert(#iv == 8, "bad IV length")
if #itxt == 0 then
return ""
end
local ivu = string.unpack("<I8", iv)
local ot = {}
local rbn = #itxt
print(rbn)
local ksu, ibu, ob = nil, nil, nil
local st = keysetup(key)
for i = 1, #itxt, 8 do
ksu = encrypt_u64(st, ivu ~ i)
if rbn < 8 then
local buffer = (string.sub)(itxt, i) .. (string.rep)("\000", 8 - rbn)
ibu = string.unpack("<I8", buffer)
ob = (string.sub)(string.pack("<I8", ibu ~ ksu), 1, rbn)
print(ob)
else
ibu = string.unpack("<I8", itxt, i)
ob = string.pack("<I8", ibu ~ ksu)
print(ob)
rbn = rbn - 8
end
table.insert(ot, ob)
end
do return table.concat(ot,ob) end
end

dec("L3H_Sec!@#$%^&*(","1qazxsw2",base64.decode("LKq2dSc30DKJo99bsFgTkQM9dor1gLl2rejdnkw2MBpOud+38vFkCCF13qY="))

由于本人还没有深入了解过 lua 虚拟机的实现以及安卓开发,所以文章中有哪些描述不正确的地方希望大家见谅

参考资料

作者

0wl

发布于

2021-11-16

更新于

2021-11-17

许可协议

评论