[WUSTCTF2020]level3
进IDA,发现应该是base64
去看看base64_encode
函数看看,发现没有异常,观察base64_table
发现数据段中的也没有异常
那就看看还有哪里改了这个base64_table
,因为直接解base64出来有乱码(
发现一个可疑的O_OLookAtYou
函数,进去一看果然修改了base64_table
还原即可得到结果
standard_base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
custom_base64_table = list(standard_base64_table)
for i in range(0, 10):
temp = custom_base64_table[i]
custom_base64_table[i] = custom_base64_table[19 - i]
custom_base64_table[19 - i] = temp
custom_base64_table = ''.join(custom_base64_table)
decode_map = str.maketrans(custom_base64_table, standard_base64_table)
encoded_str = "d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD=="
standard_encoded_str = encoded_str.translate(decode_map)
import base64
print(base64.b64decode(standard_encoded_str))
Youngter-drive
到手发现有UPX壳,脱壳后进IDA
进入第一个函数观察
发现是读取用户输入,接着回到主函数,发现主函数通过两个CreateThread
创建了两个子线程
其中第一个线程是执行StartAddress
函数,执行sub_41112C
,然后等待另一个线程
而另一个线程sub_411B10
则是dword_418008 - 1
最后当dword_418008
为-1
时,执行sub_411190
,比较flag是否正确
所以整体思路应该是,对off_418004
还原,因为dword_418008
初始值为29,先启动的是线程StartAddress
,所以推测应该是对所有奇数位动手,因此写出还原exp.py(注意,观察sub_411190
只验证了0-28位,所以最后我们还原出来还要自己补一位)
off_418004 = "TOiZiZtOrYaToUwPnToBsOaOapsyS"
off_418000 = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"
for i in range(29):
if i % 2:
temp = off_418000.index(off_418004[i])
if 'A' <= off_418004[i] <= 'Z':
print(chr(temp + 96), end='')
else:
print(chr(temp + 38), end='')
else:
print(off_418004[i], end='')
但是flag只有29位,所以最后一位要猜。。。
所以完整flag是flag{ThisisthreadofwindowshahaIsESE}
(为什么猜大写E
,第一尝试了几个,但是Buuoj提交不对,第二其它师傅的WP写的是E)
[FlareOn4]IgniteMe
进IDA分析大致流程后,找到核心的比较函数
比较的对象分别是byte_403000
和byte_403180
,其中byte_403000
是目标提取出来,然后分析byte_403180
如何构成
其中v4=sub_401000();
所以刚开始v4=4
。回到sub_401050
,发现应该是对输入,从最后向前异或,然后进行比较,所以写出脚本还原
byte_403000 = [0xd, 0x26, 0x49, 0x45, 0x2a, 0x17, 0x78, 0x44, 0x2b, 0x6c, 0x5d, 0x5e, 0x45, 0x12, 0x2f, 0x17, 0x2b, 0x44, 0x6f, 0x6e, 0x56, 0x9, 0x5f, 0x45, 0x47, 0x73, 0x26, 0xa, 0xd, 0x13, 0x17, 0x48, 0x42, 0x1, 0x40, 0x4d, 0xc, 0x2, 0x69]
byte_403000[-1] ^= 4
for i in range(len(byte_403000) - 2, -1, -1):
byte_403000[i] ^= byte_403000[i + 1]
print(''.join([chr(i) for i in byte_403000]))
# flag{R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com}
相册
进jadx-gui,找到他自己写的部分,发现没有东西(x
所以选择直接搜索与email
有关的内容
找到了A2中声明的方法,然后根据方法,去查找用例
发现收件信息来自C2
,进C2
查看
发现user要去native方法里找,把libcore.so拖进IDA,找到相关内容
解base64,得到flag
[WUSTCTF2020]Cr0ssfun
进IDA,看到输入v4
然后check(v4)
,如果返回为真,就表示flag正确
进check
分析
a1 = {
10: "p",
13: "@",
3: "f",
26: "r",
20: "e",
7: "0",
16: "_",
11: "p",
23: "e",
30: "u",
0: "w",
6: "2",
22: "s",
31: "n",
12: "_",
15: "d",
8: "{",
18: "3",
28: "_",
21: "r",
2: "t",
9: "c",
32: "}",
19: "v",
5: "0",
14: "n",
4: "2",
17: "r",
29: "f",
24: "_",
1: "c",
25: "@",
27: "e",
}
print("".join([a1[i] for i in range(33)]))
# flag{cpp_@nd_r3verse_@re_fun}
[UTCTF2020]basic-re
没啥好说的,IDA见
特殊的 BASE64
标题暗示太明显了,换了table的base64,直接去string里找,然后还原
custom_base64_table = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+"
standard_base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
decoded_map = str.maketrans(custom_base64_table, standard_base64_table)
encoded_string = "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI=="
decoded_string = encoded_string.translate(decoded_map)
import base64
print(base64.b64decode(decoded_string).decode())
# flag{Special_Base64_By_Lich}
[GWCTF 2019]xxor
无壳,进IDA查看主函数
先看比较条件sub_400770
__int64 __fastcall sub_400770(_DWORD *a1)
{
if ( a1[2] - a1[3] == 0x84A236FFLL
&& a1[3] + a1[4] == 0xFA6CB703LL
&& a1[2] - a1[4] == 0x42D731A8LL
&& *a1 == 0xDF48EF7E
&& a1[5] == 0x84F30420
&& a1[1] == 0x20CAACF4 )
{
puts("good!");
return 1LL;
}
else
{
puts("Wrong!");
return 0LL;
}
}
// 解出来(没必要用什么Z3,手动计算器都能解)
// a0 = 0xDF48EF7E
// a1 = 0x20CAACF4
// a2 = 0xE0F30FD5
// a3 = 0x5C50D8D6
// a4 = 0x9E1BDE2D
// a5 = 0x84F30420
然后观察循环,发现每组只有一个数进行了处理,进入sub_400686
函数分析
天真了,以为只对一组数处理,结果还是每组都变了,根据函数还原,得到flag
- 需要模拟unsigned int的范围
输出需要自己拼接
unk_601060 = [2, 2, 3, 4]
a1 = [0xDF48EF7E, 0x20CAACF4, 0xE0F30FD5, 0x5C50D8D6, 0x9E1BDE2D, 0x84F30420]
def simulate_unsigned_int(value):
return value & 0xFFFFFFFF
for i in range(3):
v5 = 1166789954 * 64
v3 = simulate_unsigned_int(a1[i * 2])
v4 = simulate_unsigned_int(a1[i * 2 + 1])
for _ in range(64):
v4 = simulate_unsigned_int(v4 - ((v3 + v5 + 20) ^ ((v3 << 6) + unk_601060[2]) ^ ((v3 >> 9) + unk_601060[3]) ^ 0x10))
v3 = simulate_unsigned_int(v3 - ((v4 + v5 + 11) ^ ((v4 << 6) + unk_601060[0]) ^ ((v4 >> 9) + unk_601060[1]) ^ 0x20))
v5 -= 1166789954
a1[i * 2] = v3
a1[i * 2 + 1] = v4
for i in range(6):
print(chr((a1[i] >> 16) & 0xFF), end='')
print(chr((a1[i] >> 8) & 0xFF), end='')
print(chr(a1[i] & 0xFF), end='')
[FlareOn6]Overlong
没有加壳,直接进IDA,发现逻辑很简单外层有sub_401160
获取要展示的内容
int __stdcall start(int a1, int a2, int a3, int a4)
{
char Text[128]; // [esp+0h] [ebp-84h] BYREF
unsigned int v6; // [esp+80h] [ebp-4h]
v6 = sub_401160(Text, asc_402008, 28u);
Text[v6] = 0;
MessageBoxA(0, Text, Caption, 0);
return 0;
}
进入sub_401160
,发现是通过sub_401000
函数获取下一次的位置
unsigned int __cdecl sub_401160(char *a1, char *a2, unsigned int a3)
{
unsigned int i; // [esp+4h] [ebp-4h]
for ( i = 0; i < a3; ++i )
{
a2 += sub_401000((unsigned __int8 *)a1, a2);
if ( !*a1++ )
break;
}
return i;
}
进入sub_401000
看到完整逻辑
int __cdecl sub_401000(unsigned __int8 *a1, char *a2)
{
int v3; // [esp+0h] [ebp-8h]
unsigned __int8 v4; // [esp+4h] [ebp-4h]
if ( (int)(unsigned __int8)*a2 >> 3 == 30 )
{
v4 = a2[3] & 0x3F | ((a2[2] & 0x3F) << 6);
v3 = 4;
}
else if ( (int)(unsigned __int8)*a2 >> 4 == 14 )
{
v4 = a2[2] & 0x3F | ((a2[1] & 0x3F) << 6);
v3 = 3;
}
else if ( (int)(unsigned __int8)*a2 >> 5 == 6 )
{
v4 = a2[1] & 0x3F | ((*a2 & 0x1F) << 6);
v3 = 2;
}
else
{
v4 = *a2;
v3 = 1;
}
*a1 = v4;
return v3;
}
直接运行程序,没有flag,根据提示,推测应该是v6 = sub_401160(Text, asc_402008, 28u);
处28值太少了,结合数据段asc_402008
的长度推测,应该至少到175,所以写出脚本,得到flag
# [idc.get_wide_byte(0x402008 + i) for i in range(0xb7-0x08)]
asc_402008 = [0xe0, 0x81, 0x89, 0xc0, 0xa0, 0xc1, 0xae, 0xe0, 0x81, 0xa5, 0xc1, 0xb6, 0xf0, 0x80, 0x81, 0xa5, 0xe0, 0x81, 0xb2, 0xf0, 0x80, 0x80, 0xa0, 0xe0, 0x81, 0xa2, 0x72, 0x6f, 0xc1, 0xab, 0x65, 0xe0, 0x80, 0xa0, 0xe0, 0x81, 0xb4, 0xe0, 0x81, 0xa8, 0xc1, 0xa5, 0x20, 0xc1, 0xa5, 0xe0, 0x81, 0xae, 0x63, 0xc1, 0xaf, 0xe0, 0x81, 0xa4, 0xf0, 0x80, 0x81, 0xa9, 0x6e, 0xc1, 0xa7, 0xc0, 0xba, 0x20, 0x49, 0xf0, 0x80, 0x81, 0x9f, 0xc1, 0xa1, 0xc1, 0x9f, 0xc1, 0x8d, 0xe0, 0x81, 0x9f, 0xc1, 0xb4, 0xf0, 0x80, 0x81, 0x9f, 0xf0, 0x80, 0x81, 0xa8, 0xc1, 0x9f, 0xf0, 0x80, 0x81, 0xa5, 0xe0, 0x81, 0x9f, 0xc1, 0xa5, 0xe0, 0x81, 0x9f, 0xf0, 0x80, 0x81, 0xae, 0xc1, 0x9f, 0xf0, 0x80, 0x81, 0x83, 0xc1, 0x9f, 0xe0, 0x81, 0xaf, 0xe0, 0x81, 0x9f, 0xc1, 0x84, 0x5f, 0xe0, 0x81, 0xa9, 0xf0, 0x80, 0x81, 0x9f, 0x6e, 0xe0, 0x81, 0x9f, 0xe0, 0x81, 0xa7, 0xe0, 0x81, 0x80, 0xf0, 0x80, 0x81, 0xa6, 0xf0, 0x80, 0x81, 0xac, 0xe0, 0x81, 0xa1, 0xc1, 0xb2, 0xc1, 0xa5, 0xf0, 0x80, 0x80, 0xad, 0xf0, 0x80, 0x81, 0xaf, 0x6e, 0xc0, 0xae, 0xf0, 0x80, 0x81, 0xa3, 0x6f, 0xf0, 0x80, 0x81, 0xad]
ans = []
def sub_401000(arg2):
v3 = 0
v4 = 0
if (asc_402008[arg2] >> 3 == 30):
v4 = asc_402008[arg2 + 3] & 0x3f | ((asc_402008[arg2 + 2] & 0x3f) << 6)
v3 = 4
elif (asc_402008[arg2] >> 4 == 14):
v4 = asc_402008[arg2 + 2] & 0x3f | ((asc_402008[arg2 + 1] & 0x3f) << 6)
v3 = 3
elif (asc_402008[arg2] >> 5 == 6):
v4 = asc_402008[arg2 + 1] & 0x3f | ((asc_402008[arg2] & 0x3f) << 6)
v3 = 2
else:
v4 = asc_402008[arg2]
v3 = 1
ans.append(v4)
return v3
temp = 0
for i in range(175):
temp += sub_401000(temp)
if temp >= 175:
break
print("".join([chr(i) for i in ans]))
# flag{I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com}
[FlareOn3]Challenge1
没有壳,直接进IDA
主函数逻辑很简单,经过函数sub_401260
后与上面固定的Str2
比较,那就进sub_401260
又是base64加密,应该是换了base64_table的,找到byte_413000
,然后还原,得到flag
# "".join([chr(idc.get_wide_byte(0x413000 + i)) for i in range(0x3f + 1)])
Str2 = "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q"
custom_base64_table = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"
standard_base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
decode_map = str.maketrans(custom_base64_table, standard_base64_table)
import base64
print(base64.b64decode(Str2.translate(decode_map)).decode())
# flag{sh00ting_phish_in_a_barrel@flare-on.com}
[ACTF新生赛2020]Oruga
没壳,直接进IDA
主函数逻辑清晰,读取输入,判断是否为actf{
开头,然后进入sub_78A
判断
好吧居然又是走迷宫,WMJE
对应上下左右,但是和其他迷宫的不同,他是向一个方向走到阻碍才停止,因此部分边界空白区域会直接出去,限制住了多解的情况。
word_201020 = [0x0, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x23, 0x23, 0x23, 0x23, 0x0, 0x0, 0x0, 0x23, 0x23, 0x0, 0x0, 0x0, 0x4f, 0x4f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f, 0x4f, 0x0, 0x50, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x0, 0x4f, 0x4f, 0x0, 0x4f, 0x4f, 0x0, 0x50, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x0, 0x4f, 0x4f, 0x0, 0x4f, 0x4f, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x4c, 0x0, 0x4f, 0x4f, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f, 0x4f, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4d, 0x4d, 0x4d, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4d, 0x4d, 0x4d, 0x0, 0x0, 0x0, 0x0, 0x45, 0x45, 0x0, 0x0, 0x0, 0x30, 0x0, 0x4d, 0x0, 0x4d, 0x0, 0x4d, 0x0, 0x0, 0x0, 0x0, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x45, 0x45, 0x54, 0x54, 0x54, 0x49, 0x0, 0x4d, 0x0, 0x4d, 0x0, 0x4d, 0x0, 0x0, 0x0, 0x0, 0x45, 0x0, 0x0, 0x54, 0x0, 0x49, 0x0, 0x4d, 0x0, 0x4d, 0x0, 0x4d, 0x0, 0x0, 0x0, 0x0, 0x45, 0x0, 0x0, 0x54, 0x0, 0x49, 0x0, 0x4d, 0x0, 0x4d, 0x0, 0x4d, 0x21, 0x0, 0x0, 0x0, 0x45, 0x45]
for i in range(16):
for j in range(16):
print(chr(word_201020[i * 16 + j]) if word_201020[i * 16 + j] else ".", end="")
print()
# a1 = "actf{MEWEWEMEMJMJ}"
a1 = "actf{MEWEMEWJMEWJM}"
def test(a1):
v2 = 0
v4 = 0
v3 = 5
while word_201020[v2] != 0x21:
v2 -= v4
if (a1[v3] != 'W') or v4 == -16:
if (a1[v3] != 'E') or v4 == 1:
if (a1[v3] != 'M') or v4 == 16:
if (a1[v3] != 'J') or v4 == -1:
return -1
v4 = -1
else:
v4 = 16
else:
v4 = 1
else:
v4 = -16
v3 += 1
while not word_201020[v2]:
if (v4 == -1) and (v2 & 0xf == 0):
return -2
if (v4 == 1) and (v2 % 16 == 15):
return -3
if (v4 == 16) and ((v2 - 240) & 0xffffffff <= 0xf):
return -4
if (v4 == -16) and ((v2 + 15) & 0xffffffff <= 0x1e):
return -5
v2 += v4
return a1[v3] == '}'
print(test(a1))
....#.......####
...##...OO......
........OO.PP...
...L.OO.OO.PP...
...L.OO.OO.P....
..LL.OO....P....
.....OO....P....
#...............
............#...
......MMM...#...
.......MMM....EE
...0.M.M.M....E.
..............EE
TTTI.M.M.M....E.
.T.I.M.M.M....E.
.T.I.M.M.M!...EE
BUUOJ_FLAG: flag{MEWEMEWJMEWJM}
[ACTF新生赛2020]Universe_final_answer
无壳,直接进IDA,直接去sub_860
分析
what can i say? Z3 in!
from z3 import *
solver = Solver()
v1 = BitVec("v1", 32)
v2 = BitVec("v0", 32)
v3 = BitVec("v2", 32)
v4 = BitVec("v3", 32)
v5 = BitVec("v4", 32)
v6 = BitVec("v6", 32)
v7 = BitVec("v5", 32)
v8 = BitVec("v7", 32)
v9 = BitVec("v8", 32)
v11 = BitVec("v9", 32)
solver.add(v1 > 0)
solver.add(v2 > 0)
solver.add(v3 > 0)
solver.add(v4 > 0)
solver.add(v5 > 0)
solver.add(v6 > 0)
solver.add(v7 > 0)
solver.add(v8 > 0)
solver.add(v9 > 0)
solver.add(v11 > 0)
solver.add(-85 * v9 + 58 * v8 + 97 * v6 + v7 + -45 * v5 + 84 * v4 + 95 * v2 - 20 * v1 + 12 * v3 == 12613)
solver.add(30 * v11 + -70 * v9 + -122 * v6 + -81 * v7 + -66 * v5 + -115 * v4 + -41 * v3 + -86 * v1 - 15 * v2 - 30 * v8 == -54400)
solver.add(-103 * v11 + 120 * v8 + 108 * v7 + 48 * v4 + -89 * v3 + 78 * v1 - 41 * v2 + 31 * v5 - (v6 << 6) - 120 * v9 == -10283)
solver.add(71 * v6 + (v7 << 7) + 99 * v5 + -111 * v3 + 85 * v1 + 79 * v2 - 30 * v4 - 119 * v8 + 48 * v9 - 16 * v11 == 22855)
solver.add(5 * v11 + 23 * v9 + 122 * v8 + -19 * v6 + 99 * v7 + -117 * v5 + -69 * v3 + 22 * v1 - 98 * v2 + 10 * v4 == -2944)
solver.add(-54 * v11 + -23 * v8 + -82 * v3 + -85 * v2 + 124 * v1 - 11 * v4 - 8 * v5 - 60 * v7 + 95 * v6 + 100 * v9 == -2222)
solver.add(-83 * v11 + -111 * v7 + -57 * v2 + 41 * v1 + 73 * v3 - 18 * v4 + 26 * v5 + 16 * v6 + 77 * v8 - 63 * v9 == -13258)
solver.add(81 * v11 + -48 * v9 + 66 * v8 + -104 * v6 + -121 * v7 + 95 * v5 + 85 * v4 + 60 * v3 + -85 * v2 + 80 * v1 == -1559)
solver.add(101 * v11 + -85 * v9 + 7 * v6 + 117 * v7 + -83 * v5 + -101 * v4 + 90 * v3 + -28 * v1 + 18 * v2 - v8 == 6308)
solver.add(99 * v11 + -28 * v9 + 5 * v8 + 93 * v6 + -18 * v7 + -127 * v5 + 6 * v4 + -9 * v3 + -93 * v1 + 58 * v2 == -1697)
ans = {}
if solver.check() == z3.sat:
model = solver.model()
for i in list(model):
ans[int(str(i)[1:])] = chr(model[i].as_long())
print("".join([ans[i] if i in ans else "" for i in range(10)]))
不是哥们,v的顺序和数组是乱的,然后出来的结果还不是flag,还得分析sub_C50
?不!直接执行,出最终结果
[Zer0pts2020]easy strcmp
无壳,直接进IDA,主函数非常干净,线索不多,于是看其他的函数
得找a1和a2在哪里被处理了,发现sub_6EA
中有蹊跷
# Python>[idc.get_wide_byte(0x201068 + i) for i in range(0x7F - 0x68 + 1)]
flag_encoded = [
0x42,
0x9,
0x4A,
0x49,
0x35,
0x43,
0xA,
0x41,
0xF0,
0x19,
0xE6,
0xB,
0xF5,
0xF2,
0xE,
0xB,
0x2B,
0x28,
0x35,
0x4A,
0x6,
0x3A,
0xA,
0x4F,
]
encoded_flag = list("********CENSORED********")
ans = []
for i in range(3):
part_of_flag = flag_encoded[i * 8 : (i + 1) * 8]
part_of_flag_in_int = int(
(
"0x" + "".join([str(hex(i))[2:].zfill(2) for i in part_of_flag[::-1]])
).encode(),
16,
)
part_of_encoded_flag = encoded_flag[i * 8 : (i + 1) * 8]
part_of_encoded_flag_in_int = int(
(
"0x"
+ "".join(
[str(hex(ord(i)))[2:].zfill(2) for i in part_of_encoded_flag[::-1]]
)
).encode(),
16,
)
original = part_of_flag_in_int + part_of_encoded_flag_in_int
for j in range(14, -1, -2):
ans.append(chr(int(hex(original)[2:][j : j + 2], 16)))
print("".join(ans))
# flag{l3ts_m4k3_4_DETOUR_t0d4y}
[BJDCTF2020]BJD hamburger competition
添柴般题目,不会,记录一下
0x01 准备工具
逆向最简单的Unity3D类安卓游戏建议使用安装好 JAVA 环境的Windows系统(涉及到dll文件的修改,所以Windows平台更加适合)。并且下载好专用于.net逆向反编译的dnspy
安卓apk逆向三件套
一般 APK 逆向,常使用到 apktool、dex2jar、jd-gui。在逆向 Unity3D 安卓游戏时,仅仅只需要使用到 apktool
Apktool: 用于解压/重新打包安卓APK。
dex2jar: 将解压出来的dex文件变成jar,方便使用jd-gui查看
jd-gui: 查看dex文件逻辑
dll文件逆向三件套
一般的 Unity3D 安卓游戏的主逻辑都在dll文件中,所以我们还需要 dll文件逆向/重新打包 的工具。
ILSpy: 用于查看dll程序逻辑
ILDASM: 用于反编译dll文件,生成il文件(存放了dll反编译后的指令)和res文件(反编译后的资源文件),可以安装Windows SDK或者从网上下载。
ilasm: .net4.0自带了,位置在 C:\Windows\Microsofr.NET\Framework\v4.0.30319\ilasm.exe
0x02 Unity开发的前世今生
Unity3D这款游戏引擎想必大家都不陌生,独立游戏制作者们很多人都在用它,甚至一些大公司也用在很商业的游戏制作上。
Unity3D最大的一个特点是一次制作,多平台部署,而 这一核心功能是靠Mono实现的。可以说一直以来Mono是Unity3D核心中的核心,是Unity3D跨平台的根本。这种形式一直持续到2014年年中,Unity3D官方博客上发了一篇“The future of scripting in unity”的文章,引出了IL2CPP的概念,这种相比Mono来说安全性更强的方式。
Mono与IL
Mono:一个由 Xamarin公司主持的自由开放源代码项目,目标是创建一系列符合ECMA标准(Ecma- 334和Ecma-335)的.NET工具包括C#编译器和通用语言架构。与微软的.NET Framework(共通语言运行平台)不同Mono项目不仅可以运行于Windows系统上,还可以运行于 Linux,FreeBSD,Unix,OS X和Solaris,甚至一些游戏平台。Mono使得C#这门语言有了很好的跨平台能力。
IL:全称是 Intermediate Language。翻译过来就是中间语言。它是一种属于 通用语言架构和.NET框架的低阶(lowest-level)的人类可读的编程语言。简单来说,IL类似于一个面向对象的汇编语言。
0x03 unity游戏逆向基本思路
unity主要可以看成两类,dll游戏和libil2cpp游戏,dll游戏比较简单,其核心代码都在 game/assets/bin/data/Managed/Assembly-CSarp.dll这个 dll 文件中,并且由于c#类似Js的语言特性,几乎就是可以明文随便篡改。为了提高安全性,用来转换dll to so 的libil2cpp应运而生,但实际上逆向的时候使用ida分析libil2cpp的时候也差不多,有点汇编基础不难看懂,题型要么是结合frida去动态断点一些位置,要么是使用dwarf去动态调试一些位置。
一般dll类型的unity游戏逆向,唯一核心就是逆向/修改某个 dll 文件就可以了。而一般IL2CPP的Unity3D游戏的逆向,大多只需要根据global-metadata.dat和libil2cpp.so来进行就可以了。目标异常明确,这也是 Unity3D 和 其它安卓逆向不同的地方。
这道题的核心在BJD hamburger competition_Data\Managed\Assembly-CSharp.dll
(为什么在这里?看上面折叠里的内容)
扔进dnSpyEx 分析,在ButtonSpawnFruit
里找到了验证逻辑
string text = Init.secret.ToString();
if (ButtonSpawnFruit.Sha1(text) == "DD01903921EA24941C26A48F2CEC24E0BB0E8CC7")
{
this.result = "BJDCTF{" + ButtonSpawnFruit.Md5(text) + "}";
Debug.Log(this.result);
}
关注这一段逻辑即可,flag是这一个Sha1的值的原始内容取Md5,用cmd5,找到text应该是1001
但是直接交不对,还是得去看Sha1和Md5的计算
发现计算没有问题,但是Md5值返回了前20位,最后使用全部大写提交flag通过
flag{B8C37E33DEFDE51CF91E}
[WUSTCTF2020]level4
无壳,进IDA
逻辑很简单,分别进type1
和type2
查看
分别是中序遍历和后序遍历,根据主函数看到还有type3
没有写完,推测应该是前序遍历,那就是用中序遍历结果和后序遍历结果,还原出树,然后输出前序遍历结果是flag
def build_preorder(inorder, postorder):
if not inorder or not postorder:
return ""
root = postorder[-1]
root_index = inorder.index(root)
left_inorder = inorder[:root_index]
right_inorder = inorder[root_index + 1:]
left_postorder = postorder[:root_index]
right_postorder = postorder[root_index:-1]
left_preorder = build_preorder(left_inorder, left_postorder)
right_preorder = build_preorder(right_inorder, right_postorder)
return root + left_preorder + right_preorder
inorder = "2f0t02T{hcsiI_SwA__r7Ee}"
postorder = "20f0Th{2tsIS_icArE}e7__w"
preorder = build_preorder(inorder, postorder)
print("Pre-order Traversal:", preorder)
# buuoj: flag{This_IS_A_7reE}
[羊城杯 2020]easyre
无壳,进IDA
分析主函数,长度应该是38,然后经过三次加密与Str2比较,先进第一个函数encode_one
查看
啊,经典base64,看一眼alphabet
,似乎没有改变,那就可以直接解密,再来看看encode_two
这个逻辑就简单多了,主要是一个位置交换
a1[26]-a1[38]
->a3[0] -a3[12]
a1[0] -a1[12]
->a3[13]-a3[25]
a1[39]-a1[51]
->a3[26]-a3[38]
a1[13]-a1[25]
->a3[39]-a3[51]
接着进encode_three
分析函数,发现他进行了以下处理
- 对于所有字母和数字,在各自范围内,向右循环移动3个
- 对于字符,不变
所以写出还原脚本,得到flag
Str2 = "EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG"
TEMP1 = ""
TEMP2 = ""
# Decrypt_Three
custom_table = "DEFGHIJKLMNOPQRSTUVWXYZABCdefghijklmnopqrstuvwxyzabc3456789012"
standard_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
decoded_table = str.maketrans(custom_table, standard_table)
TEMP1 = Str2.translate(decoded_table)
# Decrypt_Two
TEMP2 = TEMP1[13:26] + TEMP1[39:] + TEMP1[:13] + TEMP1[26:39]
# Decrypt_One
import base64
print(base64.b64decode(TEMP2).decode())
# buuoj: flag{672cc4778a38e80cb362987341133ea2}