前言
和 Nebula 的队员们一起参加了这次的 L3HCTF ,最后排名第7,很不错的成绩。记录一下我做的 re 。
比赛官网:https://l3hctf.xctf.org.cn
比赛时间:2021-11-13 09:00:00 到 2021-11-15 09:00:00
reverse
IDAAAAAA
没给程序,给了一个ida的i64文件,直接拖入ida。
正常流程分析下来,是输入三个整数,判断5个等式,但是无法同时满足,应该是程序将判断流程隐藏了,但是程序中找不到修改代码段的行为。
看反汇编窗口,发现main
函数有一个断点,打开断点列表(ida开始卡了),发现condition里有东西,复制condition到文本编辑器(文本编辑器开始卡了),是一个很大的idapython脚本。
ida下断点在0x40201F
,当执行到这条cmp时就触发断点,执行上面的脚本,在另一个位置下断点,获得断点信息,并修改断点添加condition为idapython脚本,之后修改[rsp]
和rip
的值使程序转到设置的断点,触发断点执行脚本,并根据输入的input[i]
确定需要解密的index
和key
,使用sub_401DB5
(维吉尼亚)解密,并且修改[rsp]
设置返回地址,并在返回地址设置断点,解密完成后返回时触发断点,继续执行脚本,解密出来的数据将会作为下一个断点的脚本,而这个脚本又会重复上面的过程。
这样可知,原程序并不能判断flag,真正控制执行流程的是idapython脚本。
尝试了很久,除了第一个断点的脚本,几乎每个脚本解密出来都是这样的形式:
NyPGpw = idaapi.get_byte(5127584 + N4QKUt)
NyPGpw -= ord('a')
if NyPGpw == 0:
afvkwL = 667
hsYnNw = b'vjHiPd4bBuf'
elif NyPGpw == 1:
afvkwL = 667
hsYnNw = b'vjHiPd4bBuf'
elif NyPGpw == 2:
afvkwL = 667
hsYnNw = b'vjHiPd4bBuf'
else:
afvkwL = -1
if afvkwL < 0:
# ...
else:
# …
# ...
每个if/elif
内部赋了两个值,第一个数值是需要解密的下一个脚本的序号index
,第二个bytes是解密的密钥key
;下面的if
判断,如果序号为负,就直接结束;else
块会解密出一个脚本,继续重复上述流程。
初始的断点脚本中间也有这样的if/elif
结构,将if块的数据提取出来得:
[287, b'lqAT7pNI3BX']
[96, b'z3Uhis74aPq']
[8, b'9tjseMGBHR5']
[777, b'FhnvgMQjexH']
[496, b'SKnZ51f9WsE']
[822, b'gDJy104BSHW']
[914, b'PbRV4rSM7fd']
[550, b'WHPnoMTsbx3']
[273, b'mLx5hvlqufG']
[259, b'QvKgNmUFTnW']
[334, b'TCrHaitRfY1']
[966, b'm26IAvjq1zC']
[331, b'dQb2ufTZwLX']
[680, b'Y6Sr7znOeHL']
[374, b'hLFj1wl5A0U']
[717, b'H6W03R7TLFe']
[965, b'fphoJwDKsTv']
[952, b'CMF1Vk7NH4O']
[222, b'43PSbAlgLqj']
观察发现,每个解密的key长度都为11,而且解密出的第一行除了起始的变量名(变量名长度也都是6),后面是完全相同的,所以可以直接异或得到key:
keys = []
dec = 'NyPGpw = idaapi.get_byte(5127584 + N4QKUt)'[11: ]
for i in encs: # BIG bytes array, encrypted scripts, length 1000
enc = i.decode()[11: ]
t = ''
for a, b in zip(enc, dec):
t += chr(ord(a) ^ ord(b))
if not t[11: ].startswith(t[: 11]):
keys.append(None)
else:
keys.append(t[:11])
得到keys
后,发现keys.count(None) == 1
,再结合ida中最后flag的格式:
输入有最短,那么这应该是一个图问题,找最短路径,每个解密出来的脚本的if/elif
块即为判断是否存在这条边,存在则进入下一节点。只有一个节点没有这个块,应该是图的终点。
于是编写脚本:
#!/usr/bin/python3
import networkx as nx
from hashlib import md5
encs = [
# BIG bytes array, encrypted scripts, length 1000
]
def veg_xor_crypt(src, key):
dec = b''
for i in range(len(src)):
dec += chr(src[i] ^ key[i % len(key)]).encode()
return dec.decode()
def parse_graph(script):
i_name = 'N4QKUt'
var_name = script[: script.index(' ')]
lines = script.splitlines()
if lines[0] == var_name + ' = idaapi.get_byte(5127584 + ' + i_name + ')':
if lines[1] == var_name + ' -= ord(\'a\')':
if lines[2] == 'if ' + var_name + ' == 0:':
# enc = [[int(lines[3][lines[3].index('=') + 2: ]), lines[4][lines[4].index('=') + 2:][2: -1].encode()]]
enc = [int(lines[3][lines[3].index('=') + 2: ])]
i = 5
while lines[i].startswith('elif ' + var_name + ' == '):
assert(int(lines[i][lines[i].index('==') + 3: -1]) == (i - 2) // 3)
enc.append(int(lines[i + 1][lines[i + 1].index('=') + 2: ]))
i += 3
assert(lines[i] == 'else:')
return enc
print("parse error: ")
print(script)
return None
keys = []
edges = []
dec = 'NyPGpw = idaapi.get_byte(5127584 + N4QKUt)'[11: ]
for i in encs:
enc = i.decode()[11: ]
t = ''
for a, b in zip(enc, dec):
t += chr(ord(a) ^ ord(b))
if not t[11: ].startswith(t[: 11]):
keys.append(None)
edges.append([])
else:
keys.append(t[:11])
edges.append(parse_graph(veg_xor_crypt(i, keys[-1].encode())))
assert(keys[8] == '9tjseMGBHR5')
assert(keys.count(None) == 1) # destination
dst_node = keys.index(None)
src_node = -1
src_edges = [287, 96, 8, 777, 496, 822, 914, 550, 273, 259, 334, 966, 331, 680, 374, 717, 965, 952, 222]
G = nx.DiGraph()
G.add_node(-1)
for i in range(len(encs)):
G.add_node(i)
for i in src_edges:
G.add_edge(-1, i)
for i in range(len(edges)):
for j in edges[i]:
G.add_edge(i, j)
path = nx.shortest_path(G, source = src_node, target = dst_node)
print(path)
s = chr(ord('a') + src_edges.index(path[1]))
for i in range(2, len(path)):
s += chr(ord('a') + edges[path[i - 1]].index(path[i]))
print(s)
print('L3HCTF{%s}' % md5(s.encode()).hexdigest())
执行结果:
[-1, 331, 578, 255, 875, 765, 687, 209, 119, 963, 939, 443, 250, 366, 65, 504, 920, 849, 720, 893, 728, 580, 114, 665, 72, 51, 241, 519, 473, 970, 984, 557, 90, 793, 487, 67, 428, 236, 263, 24, 39, 104, 505, 491, 95, 223, 486, 798, 873, 872, 64, 229, 37, 274, 329, 601, 372, 750, 446, 3, 332, 698, 277, 740, 816, 845, 570, 828, 21, 36, 839, 770, 343, 451, 151, 994, 937, 760, 644, 9, 614, 302, 454, 153, 840, 76, 424, 352, 950, 238, 613, 497, 898, 858, 415, 205, 393, 927, 522, 705, 426]
mcaebacedaabfacacabgagbbaaeacabcbacebagaaabcdbgbdbcbdacgabfbbebababbbbbcaabdababafaccacdagdaababaaaa
L3HCTF{6584ed9fd9497981117f22a6c572caee}
double-joy
主函数里初始化了两个vm
,添加到一个deque
中,然后执行第一个,当返回值为1时将刚执行的vm
再加到deque
末尾,返回值为0时不再添加,并重复上面的过程。所以是两个vm
交替执行。
sub_1D90
即为vm
执行程序,将switch
结构恢复出来,写idc脚本反汇编:
static disasm(addr) {
auto ins;
auto pc = 0;
auto tmp0, tmp1, tmp2, tmp3;
Message("Disasm 0x%x:\n", addr);
while (1) {
/*
origin stack after operation
top:
num1 ===> top:
num2 num1 op num2
... ...
/**/
ins = Byte(addr + pc);
if (ins == 0) {
Message("0x%04X: add\n", pc);
pc++;
} else if (ins == 1) {
Message("0x%04X: sub\n", pc);
pc++;
} else if (ins == 2) {
Message("0x%04X: mul\n", pc);
pc++;
} else if (ins == 3) {
Message("0x%04X: div\n", pc);
pc++;
} else if (ins == 4) {
Message("0x%04X: mod\n", pc);
pc++;
} else if (ins == 5) {
Message("0x%04X: and\n", pc);
pc++;
} else if (ins == 6) {
Message("0x%04X: or\n", pc);
pc++;
} else if (ins == 7) {
Message("0x%04X: xor\n", pc);
pc++;
} else if (ins == 8) {
Message("0x%04X: store\n", pc);
pc++;
} else if (ins == 9) {
Message("0x%04X: load\n", pc);
pc++;
} else if (ins == 10) {
Message("0x%04X: neq\n", pc);
pc++;
} else if (ins == 11) {
Message("0x%04X: lt\n", pc);
pc++;
} else if (ins == 12) {
Message("0x%04X: exch\n", pc);
pc++;
} else if (ins == 13) {
Message("0x%04X: pop\n", pc);
pc++;
} else if (ins == 14) {
Message("0x%04X: push 0x%x\n", pc, Dword(addr + pc + 1));
pc = pc + 5;
} else if (ins == 15) {
Message("0x%04X: jmp 0x%04X\n", pc, (pc + 5 + Dword(addr + pc + 1)) & 0xffffffff);
pc = pc + 5;
} else if (ins == 16) {
Message("0x%04X: jnz 0x%04X\n", pc, (pc + 5 + Dword(addr + pc + 1)) & 0xffffffff);
pc = pc + 5;
} else if (ins == 17) {
Message("0x%04X: int %d\n", pc, Dword(addr + pc + 1));
pc = pc + 5;
} else if (ins == 18) {
Message("0x%04X: ret %d\n", pc, Dword(addr + pc + 1));
pc = pc + 5;
if (Dword(addr + pc + 1) == 0) return ;
}
}
}
static main() {
disasm(0x5280);
disasm(0x5020);
Message("All done!\n");
}
然后读汇编,恢复出C程序:
#define CONTINUE 1
#define FINISH 0
int func1(int* buffer) {
// unsigned int buffer[10]; // stack[0] ~ stack[9]
int delta; // stack[0xa]
int sum; // stack[0xb]
int v0; // stack[0xc]
int v1; // stack[0xd]
int k[4]; // stack[0xe] ~ stack[0x11]
// stack[0x12] = 0; // ??
int i; // stack[0x13]
int j; // stack[0x14]
// unsigned int stack[21];
delta = 0x75bcd15;
sum = 0x3ade68b1;
for (i = 0; i < 10; i++) {
buffer[i] ^= ((i + 1) * 0x01010101);
}
k[0x0] = 0x494c;
k[0x1] = 0x6f76;
k[0x2] = 0x6520;
k[0x3] = 0x4355;
for (i = 0; i < 10; i += 2) {
for (j = 0; j < 20; j++) {
v0 = buffer[i];
v1 = buffer[i + 1];
v0 += (((v1 * 16) ^ (v1 / 32) + v1) ^ (sum + k[sum & 3]));
sum += delta;
v1 += (((v0 * 16) ^ (v0 / 32)) + v0) ^ (sum + k[(sum / 0x800) & 3]);
buffer[i] = v0;
buffer[i + 1] = v1;
return CONTINUE;
}
}
return FINISH;
}
int func2(int* buffer) {
// int buffer[10]; // stack[0] ~ stack[9]
int delta; // stack[0xa]
int sum; // stack[0xb]
int v0; // stack[0xc]
int v1; // stack[0xd]
int k[4]; // stack[0xe] ~ stack[0x11]
// stack[0x12] = 0; // ??
int i; // stack[0x13]
int j; // stack[0x14]
delta = 0x154cbf7;
sum = 0x5eeddead;
for (i = 0; i < 10; i++) {
buffer[i] ^= ((i + 1) * 0x01010101);
}
for (i = 0; i < 10; i += 2) {
for (j = 0; j < 20; j++) {
v0 = buffer[i];
v1 = buffer[i + 1];
sum += delta;
v0 += (v1 * 16 + k[0]) ^ (v1 + sum) ^ (v1 / 32 + k[1]);
v1 += (v0 * 16 + k[2]) ^ (v0 + sum) ^ (v0 / 32 + k[3]);
buffer[i] = v0;
buffer[i + 1] = v1;
return CONTINUE;
}
}
return FINISH;
}
发现是xtea和tea,编写逆向程序:
#include <stdio.h>
int main() {
int buffer[11] = {
0xAEE0FAE8, 0xFC3E4101, 0x167CAD92, 0x51EA6CBE,
0x242A0100, 0x01511A1B, 0x514D6694, 0x2F5FBFEB,
0x46D36398, 0x79EEE3F0, 0
};
int delta_1 = 0x75bcd15, delta_2 = 0x154cbf7;
int sum_1 = 0x3ade68b1, sum_2 = 0x5eeddead;
int v0;
int v1;
int k_1[4] = {0x494c, 0x6f76, 0x6520, 0x4355};
int k_2[4] = {0x5354, 0x4f4d, 0x2074, 0x6561};
int i;
int j;
// init
for (i = 0; i < 10; i += 2) {
for (j = 0; j < 20; j++) {
sum_1 += delta_1;
sum_2 += delta_2;
}
}
// func1 and func2
for (i = 8; i >= 2; i -= 2) {
for (j = 0; j < 20; j++) {
v0 = buffer[i];
v1 = buffer[i + 1];
v1 -= (v0 * 16 + k_2[2]) ^ (v0 + sum_2) ^ (v0 / 32 + k_2[3]);
v0 -= (v1 * 16 + k_2[0]) ^ (v1 + sum_2) ^ (v1 / 32 + k_2[1]);
sum_2 -= delta_2;
buffer[i] = v0;
buffer[i + 1] = v1;
v0 = buffer[i];
v1 = buffer[i + 1];
v1 -= (((v0 * 16) ^ (v0 / 32)) + v0) ^ (sum_1 + k_1[(sum_1 / 0x800) & 3]);
sum_1 -= delta_1;
v0 -= (((v1 * 16) ^ (v1 / 32)) + v1) ^ (sum_1 + k_1[sum_1 & 3]);
buffer[i] = v0;
buffer[i + 1] = v1;
}
}
i = 0;
// func1 and func2
for (j = 1; j < 20; j++) {
v0 = buffer[i];
v1 = buffer[i + 1];
v1 -= (v0 * 16 + k_2[2]) ^ (v0 + sum_2) ^ (v0 / 32 + k_2[3]);
v0 -= (v1 * 16 + k_2[0]) ^ (v1 + sum_2) ^ (v1 / 32 + k_2[1]);
sum_2 -= delta_2;
buffer[i] = v0;
buffer[i + 1] = v1;
v0 = buffer[i];
v1 = buffer[i + 1];
v1 -= (((v0 * 16) ^ (v0 / 32)) + v0) ^ (sum_1 + k_1[(sum_1 / 0x800) & 3]);
sum_1 -= delta_1;
v0 -= (((v1 * 16) ^ (v1 / 32)) + v1) ^ (sum_1 + k_1[sum_1 & 3]);
buffer[i] = v0;
buffer[i + 1] = v1;
}
// func2
v0 = buffer[0];
v1 = buffer[1];
v1 -= (v0 * 16 + k_2[2]) ^ (v0 + sum_2) ^ (v0 / 32 + k_2[3]);
v0 -= (v1 * 16 + k_2[0]) ^ (v1 + sum_2) ^ (v1 / 32 + k_2[1]);
sum_2 -= delta_2;
buffer[0] = v0;
buffer[1] = v1;
for (i = 0; i < 10; i++) {
buffer[i] ^= ((i + 1) * 0x01010101);
}
// func1
v0 = buffer[0];
v1 = buffer[1];
v1 -= (((v0 * 16) ^ (v0 / 32)) + v0) ^ (sum_1 + k_1[(sum_1 / 0x800) & 3]);
sum_1 -= delta_1;
v0 -= (((v1 * 16) ^ (v1 / 32)) + v1) ^ (sum_1 + k_1[sum_1 & 3]);
buffer[0] = v0;
buffer[1] = v1;
for (i = 0; i < 10; i++) {
buffer[i] ^= ((i + 1) * 0x01010101);
}
puts((char*) buffer);
}
得到flag:L3HCTF{D0uBle_vM_W1th_dOubIe_TEA}
Load
拖入ida,sub_401000
计算了一些值,与输入无关,然后输入被映射到名为l3hsec
的 MapViewOfFile
上,进入sub_401290
,做了一些操作后判断一个值是否为0x4550,转为char[]为PE\x00\x00
,正是exe文件的一个头,而sub_4017F0
中将byte_404020
数组解密,总共长度为10752,很大,很可疑,于是x86dbg
动态调试,下断点在上面的比较位置,内存转到byte_404020
,正是熟悉的MZ...PE...
头,于是dump出来,在010中将MZ前面的所有字节去掉,拖入ida,判断逻辑有了。
先通过MapViewOfFile
重新得到输入的flag,将flag{…}中间的字节提取,每两个16进制转为一个新的字节值(长度26 / 2 = 13),保存到一个长为9和另一个长为4的数组中,然后进入函数sub_401370
变换,没完全分析这个函数,只认真看了调用的sub_4012A0
函数,在草稿纸上画一下维度为2时的结果就可以知道这是det
,求矩阵行列式的函数,sub_401370
没仔细看了,跟delta
相关的操作应该就是求逆了。
用软件求出目标矩阵的逆矩阵,再写脚本获得flag:
#!/usr/bin/python
part1 = [-8, 18, -9, 6, -13, 6, -1, 2, -1]
part2 = [13, -3, -30, 7]
flag = 'flag{'
for i in part1:
flag += '%02x' % (i & 0xff)
for i in part2:
flag += '%02x' % (i & 0xff)
print(flag + '}')
luuuuua
真机下安装apk并运行,点按钮后会闪退。
题目说是lua
脚本,在assets
中查找,两个文件,一个logo.jpg
,另一个test.lua
, test.lua
中的判断只有base64,解码后提交不正确,显然test.lua
跟本题无关。
logo.jpg
拖入010,尾部有一些多余的数据,这可能是加密的脚本。jadx
中搜索logo.jpg
,除了onCreate
中设置ImageView
外,lua的解析部分也有一处:
调用了getExternalFilesDir
函数,但是运行程序后到/sdcard/Android/data/com.l3hsec.luuuuua/files
目录下查看并没有文件,猜测这就是运行时闪退的原因,尝试将文件复制到此目录,再运行,正常了。
jadx
跟入LdoFile
,调用了native
函数_LdoFile
,ida中打开armeabi-v7a
下的 libluajava.so
,找到jni_LdoFile
,进入sub_9BA4
,看到如下代码:
010中可以看到,jpg的有效大小刚好是0x3AFA1,那么后面的部分就是加密的lua脚本。
继续分析,sub_9D80
读入了一个字节,判断该字节的值;sub_9DFC
中执行解密:
解密方式就是异或0x3C
,尝试发现第一个字节不用异或,只有后面的字节需要,这样得到文件头部:\x1bLuaS
,luac
脚本。尝试luadec
反编译,失败,应该是改变了字节码的对应关系。
为了恢复出改变后的字节码与原字节码的对应关系,将一个编译好的lua
可执行文件拖入ida(带符号信息,下载编译lua参考 https://www.lua.org/download.html#building ,改成5.3版本即可),找到luaV_execute
函数;同时将apk中x86-64
的libluajava.so
拖入ida
,通过字符串'for' limit must be a number
的交叉引用定位到luaV_execute
函数,再在两个函数的switch块找对应的opcode。
这里找对应关系有一些小技巧,比如我先找出两个switch
中的没有if
判断的case
块,各有5个,再比较这几个的代码,对应关系就容易找到;还有相同字符串常量出现的case
块有对应关系;还有根据带符号的程序恢复出另一个程序的一些函数名,再在luaV_execute
函数中找调用了相同函数的case
块等等。
找了一些对应关系后就可以发现规律,再在解密出来的luac
脚本中找到几个函数的位置,使用010 script修复opcode:
char table[47];
int i;
for (i = 0; i < 16; i++) table[i] = i + 13;
table[16] = 0;
for (i = 17; i < 34; i++) table[i] = i + 12;
table[34] = 0x3f;
for (i = 35; i < 47; i++) table[i] = i - 34;
int filesize = FileSize();
int poses[20] = {
0x0037, 0x02C1, 0x0392, 0x04E4,
0x0928, 0x0A7C, 0x10C0, 0x19EF,
0x1B15, 0x1CF8, 0x1DF8, 0x1F3E,
0x2200, 0x2419, 0x288A
};
int pos = 0x37;
int size;
int j;
char ins;
char type;
for (i = 0; i < 15; i++) {
pos = poses[i];
size = ReadInt(pos);
pos += 4;
Printf("Start patching codes at 0x%x, size: %d, stack size: %d.\n", pos, size, ReadByte(pos - 5));
for (j = 0; j < size; j++) {
ins = ReadByte(pos + 4 * j);
WriteByte(pos + 4 * j, table[ins & 0x3f] | (ins & 0xC0));
}
}
修复后就能使用luadec
反编译了:
~/luadec/luadec/luadec ./test.luac > test.lua
得到lua脚本:
-- Decompiled using luadec 2.2 rev: 895d923 for Lua 5.3 from https://github.com/viruscamp/luadec
-- Command line: ./sth.luac
-- 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
check_login = function(username, password)
-- function num : 0_11 , upvalues : base64, enc
local encoded = (base64.encode)(username)
if encoded ~= "TDNIX1NlYw==" then
return false
end
username = username .. "!@#$%^&*("
local x = (base64.encode)(enc(username, "1qazxsw2", password))
if x == "LKq2dSc30DKJo99bsFgTkQM9dor1gLl2rejdnkw2MBpOud+38vFkCCF13qY=" then
return true
end
return false
end
end
end
end
读代码发现enc函数只是固定值异或明文,所以将密文加密一次即可解密。将enc前的local去掉,最后添加一行:
print(enc("L3H_Sec!@#$%^&*(", "1qazxsw2", ",\xaa\xb6u'7\xd02\x89\xa3\xdf[\xb0X\x13\x91\x03=v\x8a\xf5\x80\xb9v\xad\xe8\xdd\x9eL60\x1aN\xb9\xdf\xb7\xf2\xf1d\x08!u\xde\xa6"))
运行后输出乱码,读代码时就觉得有个地方很奇怪,是keysetup
中的这行:
kt[1] = sunpack(">I4I4I4I4", key)
应该是luadec
反编译出错了,将其改为:
kt[1], kt[2], kt[3], kt[4] = sunpack(">I4I4I4I4", key)
运行脚本,得到flag:L3HCTF{20807a82-fcd7-4947-841e-db4dfe95be3e}
总结
题目总体较难,中途不止一次被卡住,还要多学习。