WMCTF2023

 8.20的比赛,拖了快两个月,我是懒狗,呜呜呜。

0x00 ezAndroid

1
耐心搜索

 Android killer与PKID显示未加壳。jeb打开:

image-20231011140306345

 跟踪this.CheckUsername与this.check2函数。发现是naive层的函数,如下所示:

image-20231011140418292

 (1)找到java函数在native层对应的函数符号,并以此找到函数地址。(包名:com.wmctf.ezandroid

  (a)objection注入失败(frida反调试),但是幸运的是,关键函数CheckUsername与check2都使用动态注册,根据frida_hook_libart,有:

1
frida -U -f com.wmctf.ezandroid -l hook_RegisterNatives.js --no-pause

  (2)可以得到CheckUsername和check2在libezandroid.so中的名字:0x35a40x3f0c。如下所示:

image-20231011143020836

sub_35a4-CheckUsername

Step1

 分析libezandroid.so,对于sub_35a4(CheckUsername),其中首先调用了sub_3EC0(a1, name, 0LL),其中a1为JNIEnv,name为用户名。跟进后,发现是JNIEnv+0x548函数。

image-20231011144740090

 因此,打算hook sub_3EC0函数,并打印其输入与返回值。但是首先,先绕过反调试(因为frida注入时总会退出)。

Step2

  反调试绕过,见链接。首先查看open了哪些文件:

1
2
3
4
5
6
7
8
9
10
11
12
// look_open.js
// find open function in all modules
var pth = Module.findExportByName(null, "open")
Interceptor.attach(pth, {
onEnter:function(args){
this.filename = args[0]
console.log(this.filename.readCString())
},
onLeave:function(ret){
return ret
}
})

 发现断在了:

image-20231011160627564

 /proc/self/maps用于访问当前进程的内存映射信息。查看此映射信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// look_maps.js
function look_maps(){
Java.perform(function(){
var source_path = "file://" + "/proc/self/maps"
var Paths = Java.use("java.nio.file.Paths");
var URI = Java.use("java.net.URI");
var path = Paths.get(URI.create(source_path));
var Files = Java.use("java.nio.file.Files");
var fileBytes = Files.readAllBytes(path);
var JString = Java.use("java.lang.String");
var ret = JString.$new(fileBytes);
console.log(ret)
})
}
setImmediate(look_maps)

 当挂上frida之后,如下所示:

image-20231011164144232

 要绕过此检测,就要备份一个正常启动的maps文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// bypass.js
function bypass() {
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
// 事先准备好
var fakePath = "/data/local/tmp/ezandroid.maps";
// 替换 open 函数
Interceptor.replace(openPtr, new NativeCallback(function (pathnameptr, flag) {
var pathname = Memory.readUtf8String(pathnameptr);
var realFd = open(pathnameptr, flag);
// pathname 中含有 "maps",此时返回 fakePath 的 fd
if (pathname.indexOf("maps") >= 0) {
var filename = Memory.allocUtf8String(fakePath);
return open(filename, flag);
}
var fd = open(pathnameptr, flag);
return fd;
}, 'int', ['pointer', 'int']));
}
setImmediate(bypass)

Step3

 快乐的hook sub_3EC0():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// hook_sub_3EC0.js
function bypass() {
...
}
setImmediate(bypass)
function hook_sub_3ec0(){
var offset = 0x3ec0
var base = Module.findBaseAddress('libezandroid.so')
var func_addr = base.add(offset)
Interceptor.attach(ptr(func_addr), {
onEnter:function(args){
console.log(args[1].readCString())
},
onLeave:function(ret){
console.log("ret:", ret.readCString())
}
})
}

 发现返回值ret为用户名,但是args[1]并不是想象中的,为用户名,猜测应该是用户名的jstring对象。

Step4

  紧接着分析,发现sub_35a4函数貌似经过ollvm的混淆,有很多while循环,如下所示:

image-20231011194917434

 使用项目,将deflat.py代码改为:

image-20231011214247374

 即可扫描到sub_35a4函数,运行指令:

1
python3 deflat.py -f libezandroid.so --addr 0x4035A4

 但是效果不尽人意。

Step5

  经过艰苦卓绝的手动测试,终于得到如下结论:

  • 一共三轮大循环,前两轮大循环无用。
  • 用户名长度为10,如下所示,其中v47为用户名长度:

image-20231011221556799

  • memcmp(v30, &byte_A138, 0xAu)应总返回0,即比较成功。

 接下来,重点关注以下部分(仅执行一次):

image-20231011221904999

 钩取sub_60A0函数,得到返回值为12345678

 钩取sub_6C2C函数,其参数分别为:用户名,返回字符串(要返回),用户名长度,字符串”12345678”,某个值(要返回)。

 钩取memcmp函数,但是系统可能一直在调用memcmp,且Interceptor.attach开销很大,导致钩取时直接卡住。

Step6

 分析sub_6C2C函数,首先使用deflat olllvm反混淆,但失败。之后,将程序划分为多个Block,分别为:

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
// block 1
v21 = 0;
i = 0x6A42046E;
// block 2
if(v21 + 1 >= 0)
v10 = v21 + 1;
else
v10 = v21 + 256;
v21 = v21 + 1 - (v10 & 0xFFFFFF00);
v11 = v15 + (unsigned __int8)v22[v21 + 256];
v12 = v11 + 255;
if (v11 >= 0)
v12 = v15 + (unsigned __int8)v22[v21 + 256];
v15 = v11 - (v12 & 0xFFFFFF00);
result = swap(&v22[v21 + 256], &v22[v15 + 256]);
*(_BYTE *)(a2 + j) = *(_BYTE *)(v20 + j) ^ v22[(unsigned __int8)(v22[v21 + 256] + v22[v15 + 256]) + 256] ^ j;
i = -0x235CE214u;
// block 3
v5 = 0x3D13C5C;
if(v21 >= 256)
v5 = -0x59910D2Cu;
i = v5;
// block 4
++j;
i = -0x16F5EC03u;
// block 5
if ( j >= a3 )
v9 = 0x531796B4;
else
v9 = -0x338869E1u;
i = v9;
// block 6
++v21;
i = -0x3F77EF18u;
// block 7
v15 = 0;
v21 = 0;
j = 0;
i = -0x16F5EC03u;
// block 8
v6 = 0x63790CC6;
if(v21 >= 256)
v6 = 0x5BE5E1B6;
i = v6;
// block 9
v7 = v15 + (unsigned __int8)v22[v21 + 256] + (unsigned __int8)v22[v21];
v8 = v7 + 255;
if ( v7 >= 0 )
v8 = v15 + (unsigned __int8)v22[v21 + 256] + (unsigned __int8)v22[v21];
v15 = v7 - (v8 & 0xFFFFFF00);
result = swap(&v22[v21 + 256], &v22[v15 + 256]);
i = 0x25E9A458;
// block 10
v22[v21 + 256] = v21;
v22[v21] = *(_BYTE *)(a4 + v21 % a5);
i = -0x1F4D6769u;
// block 11
++v21;

 block的运行逻辑为:先256次 block3->block10->block6,block3,block1,再256次 block8->block9->block11,block8,block7,最后10次block5->block2->block4。经过分析,属于RC4算法,密钥为12345678。RC4魔改了一下,最后异或j。

Step7

 这一步主要是获取memcmp的第2个参数byte_A138。前面钩取memcmp的方法行不通,那么找到byte_A138在哪儿赋值的。找到相关函数,如下所示:

image-20231012122728031

 得到byte_A138 = [0xe9,0x97,0x64,0xe6,0x7e,0xeb,0xbd,0xc1,0x0,0x1e]。解密脚本出不了,猜测是不是byte_A138的计算有问题,frida钩取一下。打印内存的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
function print_mem(){
Java.perform(function(){
var addr = Module.findBaseAddress("libezandroid.so")
var byte_A138 = addr.add(0xa138)
console.log(hexdump(byte_A138, {
offset:0,
length: 64,
header: true,
ansi: true,
}))
})
}

 发现byte_A138应该为:[0xe9,0x97,0x64,0xe6,0x7e,0xeb,0xbd,0xc1,0xab,0x43]

 针对用户名的解密脚本:

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
from Crypto.Cipher import ARC4

class rc4util():
def __init__(self, key):
if isinstance(key, str):
self.__keyGen = key.encode()
elif isinstance(key, bytes):
self.__keyGen = key
def __encrypt(self, data) -> bytes:
rc4 = ARC4.new(self.__keyGen)
res = rc4.encrypt(data)
return res
def __decrypt(self, data) -> bytes:
rc4 = ARC4.new(self.__keyGen)
res = rc4.decrypt(data)
return res
def Encrypt(self, d) -> bytes:
res = self.__encrypt(d)
return res
def Decrypt(self, d) -> bytes:
res = self.__decrypt(d)
return res

key = "12345678"
def Entry(d):
rc4 = rc4util(key)
ret = rc4.Encrypt(d)
return ret
def Decry(d):
rc4 = rc4util(key)
ret = rc4.Decrypt(d)
return ret

if __name__ == "__main__":
ciphertext = bytes([0xe9,0x97,0x64,0xe6,0x7e,0xeb,0xbd,0xc1,0xab,0x43])
res = Entry(ciphertext)
for i, j in enumerate(res):
print(chr(i^j), end = "")
# Re_1s_eaSy

sub_3f0c-check2

 再来看验证密码的函数,与上个函数的分析类似,密码长度为16。重点关注sub_AFC与unk_A158。

image-20231012144127979

Step1

 钩取sub_AFC,发现参数为:密码、0x10、Re_1s_eaSy123456。分析sub_AFC,划分block(化简版):

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
// block1
v19 = strlen(v27);
v29 = v30 - 354358356;
result = sub_11A0(&v29, v20);
v18 = 0;
// block2
v29 = v30 + 51219284;
v8 = &v29;
v7 = v33;
sub_1CD8(&v29, v33);
v29 = v30 - 1988849943;
sub_2000(v8, v7);
v29 = v30 + 1568064809;
sub_1990(v8, v7, 10LL);
v29 = v30 + 901913056;
result = sub_2ADC(v8, v7, v22 + v18);
// block3
v29 = v30 + 51219284;
v10 = &v29;
v9 = v33;
sub_1CD8(&v29, v33);
v29 = v30 - 1988849943;
sub_2000(v10, v9);
v29 = v30 - 879117111;
sub_23FC(v10, v9);
v29 = v30 + 1568064809;
result = sub_1990(v10, v9, v17);
// block4
++v17;
// block5
// block6
// block7
v18 += 16;
// block8
v29 = v30 - 739967527;
v12 = &v29;
v11 = v33;
sub_165C(&v29, v22 + v18, v33);
v29 = v30 + 1568064809;
result = sub_1990(v12, v11, 0LL);
v17 = 1;

 计算过程:block1、block6、block8、9次block5->block3->block4、block5、block2、block7、block6。化简后:block1、block8、9次block3->block4、block2、block7。

 分析block1中的sub_11A0,参数分别为:-2263271711、Re_1s_eaSy123456,猜测是初始化参数,在此跳过。

 再分析block8中的sub_165C,是将用户输入的密码传递到另一个变量中,例如存到变量里是abcdefghijklmnoz,那么变量则为aeimbfjncgkodhlz

1
2
3
4
5
6
d = {"0":[], "1":[], "2":[], "3":[]}
inp = "..."
for i in range(16):
ind = i % 4
d[str(ind)].append(inp[i])
inp = d["0"] + d["1"] + d["2"] + d["3"]

 block8中的sub_1990,逻辑为(轮密钥加):

1
2
3
4
5
6
byte_A178 = [...]
for i in range(16):
if i % 4 == 0:
k = 3 - i / 4
j = k + (i % 4) * 4
ret[i] ^= byte_A178[j + arg3 * 16]

 block3中的sub_1CD8,是字节替换。block3中的sub_2000,是行移位。sub_23FC是列混淆。sub_2ADC则是重新得到密文。综上,属于AES加密流程

 既然,AES是对称加密,那么我们把要比较的密文扔进去,再过一遍,不就得到明文了吗?(我真傻逼

 hook得到要比较的密文:

1
2bc8208b5c0da79b2a513ad27171ca50

 主动调用sub_afc函数(不要忘记前面的bypass哦):

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
function bypass() {
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
// 事先准备好
var fakePath = "/data/local/tmp/ezandroid.maps";
// 替换 open 函数
Interceptor.replace(openPtr, new NativeCallback(function (pathnameptr, flag) {
var pathname = Memory.readUtf8String(pathnameptr);
var realFd = open(pathnameptr, flag);
// pathname 中含有 "maps",此时返回 fakePath 的 fd
if (pathname.indexOf("maps") >= 0) {
var filename = Memory.allocUtf8String(fakePath);
return open(filename, flag);
}
var fd = open(pathnameptr, flag);
return fd;
}, 'int', ['pointer', 'int']));
}
setImmediate(bypass)

function call_sub_afc(){
var offset = 0x0AFC
var base = Module.findBaseAddress('libezandroid.so')
var addr = base.add(offset)
var func = new NativeFunction(addr, 'pointer', ['pointer', 'int', 'pointer'])
// 密文
var arg0 = Memory.alloc(16);
Memory.writeByteArray(arg0, [0x2b, 0xc8, 0x20, 0x8b, 0x5c, 0x0d, 0xa7, 0x9b, 0x2a, 0x51, 0x3a, 0xd2, 0x71, 0x71, 0xca, 0x50]);
// 密钥
var arg2 = Memory.alloc(16);
Memory.writeByteArray(arg2, [0x52, 0x65, 0x5f, 0x31, 0x73, 0x5f, 0x65, 0x61, 0x53, 0x79, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36]);
Java.perform(function(){
var env = Java.vm.getEnv()
// 主动调用 sub_afc
func(ptr(arg0), 0x10, ptr(arg2))
})
}
function hook_sub_afc(){
var offset = 0x0AFC
var base = Module.findBaseAddress('libezandroid.so')
var func_addr = base.add(offset)
var arg0 = null
Interceptor.attach(ptr(func_addr), {
onEnter:function(args){
arg0 = args[0]
console.log("enter")
Java.perform(function(){
console.log(hexdump(ptr(arg0), {
offset: 0,
length: 64,
header: true,
ansi: true,
}))
})
},
onLeave:function(ret){
console.log("return")
Java.perform(function(){
console.log(hexdump(ptr(arg0), {
offset: 0,
length: 64,
header: true,
ansi: true,
}))
})
}
})
}

Step2

 与正常的AES加密结果不同,猜测更换了S盒。表面上没改,实际用的时候改了,真狗啊。下面是抓取S盒的脚本:

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
function bypass() {
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
// 事先准备好
var fakePath = "/data/local/tmp/ezandroid.maps";
// 替换 open 函数
Interceptor.replace(openPtr, new NativeCallback(function (pathnameptr, flag) {
var pathname = Memory.readUtf8String(pathnameptr);
var realFd = open(pathnameptr, flag);
// pathname 中含有 "maps",此时返回 fakePath 的 fd
if (pathname.indexOf("maps") >= 0) {
var filename = Memory.allocUtf8String(fakePath);
return open(filename, flag);
}
var fd = open(pathnameptr, flag);
return fd;
}, 'int', ['pointer', 'int']));
}
setImmediate(bypass)

function hook_sub_2fb4(){
var offset = 0x2fb4
var base = Module.findBaseAddress('libezandroid.so')
var func_addr = base.add(offset)
var data = base.add(0xa000)
Interceptor.attach(ptr(func_addr), {
onEnter:function(args){
},
onLeave:function(ret){
Java.perform(function(){
console.log(hexdump(ptr(data), {
offset: 0,
length: 256,
header: true,
ansi: true,
}))
})
}
})
}

 抓到的S盒为:

1
[0x29, 0x40, 0x57, 0x6e, 0x85, 0x9c, 0xb3, 0xca, 0xe1, 0xf8, 0x0f, 0x26, 0x3d, 0x54, 0x6b, 0x82,0x99, 0xb0, 0xc7, 0xde, 0xf5, 0x0c, 0x23, 0x3a, 0x51, 0x68, 0x7f, 0x96, 0xad, 0xc4, 0xdb, 0xf2,0x09, 0x20, 0x37, 0x4e, 0x65, 0x7c, 0x93, 0xaa, 0xc1, 0xd8, 0xef, 0x06, 0x1d, 0x34, 0x4b, 0x62,0x79, 0x90, 0xa7, 0xbe, 0xd5, 0xec, 0x03, 0x1a, 0x31, 0x48, 0x5f, 0x76, 0x8d, 0xa4, 0xbb, 0xd2,0xe9, 0x00, 0x17, 0x2e, 0x45, 0x5c, 0x73, 0x8a, 0xa1, 0xb8, 0xcf, 0xe6, 0xfd, 0x14, 0x2b, 0x42,0x59, 0x70, 0x87, 0x9e, 0xb5, 0xcc, 0xe3, 0xfa, 0x11, 0x28, 0x3f, 0x56, 0x6d, 0x84, 0x9b, 0xb2,0xc9, 0xe0, 0xf7, 0x0e, 0x25, 0x3c, 0x53, 0x6a, 0x81, 0x98, 0xaf, 0xc6, 0xdd, 0xf4, 0x0b, 0x22,0x39, 0x50, 0x67, 0x7e, 0x95, 0xac, 0xc3, 0xda, 0xf1, 0x08, 0x1f, 0x36, 0x4d, 0x64, 0x7b, 0x92,0xa9, 0xc0, 0xd7, 0xee, 0x05, 0x1c, 0x33, 0x4a, 0x61, 0x78, 0x8f, 0xa6, 0xbd, 0xd4, 0xeb, 0x02,0x19, 0x30, 0x47, 0x5e, 0x75, 0x8c, 0xa3, 0xba, 0xd1, 0xe8, 0xff, 0x16, 0x2d, 0x44, 0x5b, 0x72,0x89, 0xa0, 0xb7, 0xce, 0xe5, 0xfc, 0x13, 0x2a, 0x41, 0x58, 0x6f, 0x86, 0x9d, 0xb4, 0xcb, 0xe2,0xf9, 0x10, 0x27, 0x3e, 0x55, 0x6c, 0x83, 0x9a, 0xb1, 0xc8, 0xdf, 0xf6, 0x0d, 0x24, 0x3b, 0x52,0x69, 0x80, 0x97, 0xae, 0xc5, 0xdc, 0xf3, 0x0a, 0x21, 0x38, 0x4f, 0x66, 0x7d, 0x94, 0xab, 0xc2,0xd9, 0xf0, 0x07, 0x1e, 0x35, 0x4c, 0x63, 0x7a, 0x91, 0xa8, 0xbf, 0xd6, 0xed, 0x04, 0x1b, 0x32,0x49, 0x60, 0x77, 0x8e, 0xa5, 0xbc, 0xd3, 0xea, 0x01, 0x18, 0x2f, 0x46, 0x5d, 0x74, 0x8b, 0xa2,0xb9, 0xd0, 0xe7, 0xfe, 0x15, 0x2c, 0x43, 0x5a, 0x71, 0x88, 0x9f, 0xb6, 0xcd, 0xe4, 0xfb, 0x12]

 S盒求逆,得到:

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

 根据项目,快乐得到:_eZ_Rc4_@nd_AES!。最后flag:Re_1s_eaSy_eZ_Rc4_@nd_AES!

0x01 ez_v1deo

1
视频好像被L1near弄坏掉了

 7s的avi视频,92.8MB,感觉离谱,肯定藏了什么东西。根据AVI格式看了看,感觉没啥问题,单纯就是觉得strl块(存放avi数据流)太大了,92MB。但是看了一下,链接说avi文件体积本来就很大,所以应该也没啥问题,还推荐了MSU VideoStego工具来分析avi文件。

 详细的avi格式:链接

 MSU VideoStego工具解压缩需要密码,试了几个说不行。最后使用binwalk提取出了sit类型的文件。但是感觉方向错了,看wp。

wp

(1)首先使用ffmpeg将avi转为帧格式。

1
ffmpeg -i flag.avi -r 20 ./images/image-%3d.png

(2)使用lsb提取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from PIL import Image

for n in range(1, 144):
img = Image.open("C:\\Users\\23957\\Desktop\\images\\image-{:0>3d}.png".format(n))
img = img.convert("RGB")
width, height = img.size
for i in range(0, width):
for j in range(0, height):
tmp = img.getpixel((i,j))
# 最后一位为 0,则为 0
if tmp[1] & 0x1 == 0:
img.putpixel((i,j), 0)
# 最后一位为 1,则为 255
else:
img.putpixel((i,j), 255)
img.save("C:\\Users\\23957\\Desktop\\images\\output\\image-{:0>3d}.png".format(n))

 最终得到flag:WMCTF{5b658ab9-946c-3869-fc21-6ad99b3bc714}。虽然和逆向没太大关系,但是学到了。


 发现我好像把题目记录错了,日了狗了,以下是更改版本。

0x02 gohunt

1
wmctf{...}

 一个flag.jpg,一个gohunt。jpg扫出来数据为:

1
YMQHsYFQu7kkTqu3Xmt1ruYUDLU8uaMoPpsfjqYF4TQMMKtw5KF7cpWrkWpk3

 跑一跑Gohunt,猜测逻辑是有一个original.jpg,输入flag,之后输出了flag.jpg。

Gohunt分析

 发现其中导入了很多github相关函数,那么,直接binaryAI处理一波(没啥卵用),Go_parser报错,显示找不到Moduledata,看来是作者自己删了。

 重点是分析main_main函数。对于__encoding_base64_Encoding__DecodeString函数,得到其转换码表:

1
2
3
4
5
6
7
8
decodeMap = [b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'.', b'\xff', b'\xff', b'\xff', b'\xff', b'\x0e', b'\x17', b'\x1b', b'0', b')', b'\x0f', b'$', b'\x13', b'=', b'-', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'!', b'*', b'\x18', b'5', b'2', b'\x1a', b':', b'?', b'\x04', b'%', b'8', b'6', b'\x1e', b' ', b'\x00', b'4', b',', b'\x02', b'\x0b', b'\x1c', b'\n', b'&', b'3', b';', b'\x06', b'9', b'\t', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'+', b'(', b'1', b'\x14', b'\x0c', b'\x1d', b'\x11', b'7', b'\x08', b"'", b'\x01', b'\x15', b'\x07', b'\x10', b'\r', b'\x19', b'"', b'#', b'<', b'>', b'\x12', b'\x16', b'\x1f', b'\x03', b'\x05', b'/', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'=', b'\x00', b'\x00', b'\x00']
base64Key = {}
for ind, d in enumerate(decodeMap):
if d != b'\xff' and ind <= 255:
base64Key[int.from_bytes(d, byteorder='little', signed=True)] = chr(ind)
for i in range(64):
print(base64Key[i], end = "")
# NkQxHyXmiZTReo05ngu7dlv1BpE2SfLwM@qr6IUjb4AaP9+z3cDVOCKhJYFWs8tG

 时间过了好久好久,已经到 2024-02-29,我又开启了 CTF 的刷题旅程。

Step1:发现函数 __github_com_yeqown_go_qrcode_v2_QRCode__Save(*&old[56LL], __PAIR128__(v417.value, v185.len)),动态调试查看最终保存的数据是哪个参数。最终发现 v.len 保存要输出的数据。

image-20240229152023582

Step2:对 v.len 的写操作进行分析,分析不出来啥。已知题目中使用的项目为 link1,因此打算先做一个 demo,然后对 demo 进行分析。通过对 demo 和 gohunt 的文件进行相似性查看,找到最初的点(也就是把加密后的 flag 使用 QRcode 这个项目进行处理的最开始的点)。

 在此补充一个知识点,这是 go 协程的逆向对比。

image-20240302104912346

image-20240302105005779

Step3: 最终,可以分析出程序的大致逻辑如下。

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
package main

import (
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
)

func main() {
// begin
Print("Please enter a flag:")
data = input()
encrypted_data = encrypt(data)
// end
qrc, err := qrcode.New(encrypted_data)
if err != nil {
panic(err)
}

w0, err := standard.New("./repository_qrcode.png",
standard.WithHalftone("./test.jpeg"),
standard.WithQRWidth(21),
)
handleErr(err)
err = qrc.Save(w0)
handleErr(err)
}

func handleErr(err error) {
if err != nil {
panic(err)
}
}

Step4: 分析 Step3 中 encrypt 函数的逻辑。并可以写脚本,得到 flag:

1
2
3
4
5
6
7
8
1. 使用 xxtea 算法加密输入,v = 输入,k = FMT2ZCEHS6pcfD2R,输出为 out_1。
23 -> 0x977E50ED 0xE4EAF734
2. 有字符串 s = NPWrpd1CEJH2QcJ3,输出 out_2 = s[i % 0x10] ^ out_1[i]。
23 -> 0xE52900A3 0xA7DB9344 -> (这里需要一次反转) 0xA30029E54493DBA7
3. 循环。ss = "nY7TwcE41bzWvMQZXa8fyeprJoBdmhsu9DqVgxRPtFLKN65UH2CikG3SAj",商 = 被除数 % 除数,余数 = 被除数 % 除数,除数一直为 0x3A,之后被除数 = 商。result.append(ss[余数])。
23 -> KqzAHV4n8Zd
4. 反转,得到结果。
23 -> dZ8n4VHAzqK
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
import binascii

if __name__ == "__main__":
inp = "YMQHsYFQu7kkTqu3Xmt1ruYUDLU8uaMoPpsfjqYF4TQMMKtw5KF7cpWrkWpk3"

byte_arr = "nY7TwcE41bzWvMQZXa8fyeprJoBdmhsu9DqVgxRPtFLKN65UH2CikG3SAj"
tmp = 0
for c in inp:
tmp *= 0x3A
ind = byte_arr.index(c)
tmp += ind

byte_string = binascii.unhexlify(hex(tmp)[2:])
int_list = list(byte_string)

byte_arr_2 = "NPWrpd1CEJH2QcJ3"
out_1 = []
i = 0
for l in int_list:
val = l ^ ord(byte_arr_2[i%0x10])
out_1.append(val)
i += 1

j = 0
p = ""
result = []
for i in out_1:
if (j % 4 == 0):
result.append(p)
p = ""
p = hex(i)[2:].zfill(2) + p
j += 1
result.append(p)

result = result[1:]
print("{", end = "")
for r in result:
print('0x'+r, end = ", ")
print("}")

# {0x98b97795, 0x0c96d2e8, 0x80c6ec1b, 0x584a00f6, 0xb18aa61e, 0xb378425b, 0x7249b4fe, 0xc53ac4d1, 0xff9ae037, 0x0ccc6250, 0x1c6e0290, }
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
#include <stdio.h>  
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
uint32_t tmp = n;
unsigned p, rounds, e;
if (n > 1)
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
// z = tmp;
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1)
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}


int main()
{
uint32_t v[11] = {0x98b97795, 0x0c96d2e8, 0x80c6ec1b, 0x584a00f6, 0xb18aa61e, 0xb378425b, 0x7249b4fe, 0xc53ac4d1, 0xff9ae037, 0x0ccc6250, 0x1c6e0290};
uint32_t const k[4] = {0x32544D46,0x4845435A,0x63703653,0x52324466};
int n = 11;
btea(v, -n, k);
for (int j = 0; j < n; j += 1) {
unsigned long number = v[j];
unsigned char byte1 = (number >> 24) & 0xFF;
unsigned char byte2 = (number >> 16) & 0xFF;
unsigned char byte3 = (number >> 8) & 0xFF;
unsigned char byte4 = number & 0xFF;
printf("%c%c%c%c", byte4, byte3, byte2, byte1);
}
return 0;
}

// wmctf{YHNEBJx1WG0cKtZk8e2PNbxJa45WQF09}

0x03 ios

1
wmctf{...}

 发现是 IOS 的 IPA 文件,改为 zip 文件解压缩后,发现 IDA 的调试文件:

image-20240303131652109

 打开后发现是典型的 OLLVM 混淆:

image-20240303140623423

Step1: 使用 Binary Ninja 的去混淆脚本 llvm-deobfuscator 报错,分析发现是此脚本将程序入口定位到了 0x10000a9a0,且此函数并没有其他函数的入口。但是可以看到红框部分应该是主程序的入口,其地址是由参数控制的。

image-20240303200448117

 根据 link,可以知道:IOS 程序会先执行 main 函数(也就是上图中的 MEMORY[0x10000A9A0]),main 函数内部会调用 UIApplicationMain 函数。如下所示:

1
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))

 在 UIApplicationMain 中,会创建 UIApplication 对象与 UIApplication 的 delegate 对象(又叫做 AppDelegate,它会开启一个消息循环,并监听对应的系统事件)。当应用程序启动完成后,UIApplicationMain 会调用 application:didFinishLaunchingWithOptions 方法,在这个方法中,会创建窗口、设置视图控制器、配置数据模型。它标志着程序启动完成。

Step2: 查看 application:didFinishLaunchingWithOptions,并未发现什么有用的逻辑。

image-20240303213402576

Step3: 询问 GPT 后发现,IOS 程序的主逻辑大概有以下几个地方:

1
2
3
4
5
(1)View Controllers,主要就是 window.rootViewController。
(2)Storyboards 和 XIBs。
(3)AppDelegate 的其他方法,在 iOS 13 及以上为 SceneDelegate。
(4)定时器、网络请求等。
(5)第三方框架,例如 Firebase、Realm。

 找到一个 ViewController handleButtonClick 函数,但是还是 OLLVM,所以打算手动修复一下 llvm-deobfuscator 的脚本,猜测:

(1)脚本不 work 的原因可能是函数一开始是有参数的。但是经过调研,了解插件的运行原理之后,发现是自己插件使用的有问题。

(2)继续调研后发现,即使正常调用脚本,也会出现错误,我真是服了,继续调研。

(3)我用的 binary ninja 3.5 的版本,很多 API 更新了。经过调研,应该就是这个问题。

Step4: 本来想将 x64 的脚本改成 ARM 的,但是发现很多地方都不适配,所以想自己写一个 binary ninja 的脚本。首先,找到此题目中 OLLVM 的规律。

image-20240308195917702

规律 1:看黄框,每一个函数的开头都定义了一些变量,这些变量供下面的 cmp 使用。

规律 2:看红框,之后紧接着好多 cmp 指令,用于 dispatch。

规律 3:看蓝框,紧接着有好多实际的逻辑块,最后是 return 块。

 那么,我们可以设计如下去 OLLVM 的算法:

1
2
3
4
1. 根据红框,确定要关注的变量,例如这里的 w11。
2. 计算黄框中 w11 的值,作为起始点。
3. 根据红框中其他的 cmp 变量,使用黄框计算出这些变量的值,并创建映射表,即 w11 = ?,应跳转到哪些实际块(蓝框与 return)。
4. 构建块之间的顺序。

 但是我看其他函数好多还不是这个逻辑,头疼,打算用符号执行了。具体可以见 link2

Step5: 符号执行可以使用 angr 或者 unicorn 工具。使用 angr 去 OLLVM 时,见 useful-scripts。Angr 去除 OLLVM 时,是直接以块为起点,模拟执行来找到下一个块的,如果在这个块之前还产生了某个事件,这个事件对这个块的执行流程产生了影响的,那么本次模拟处理的结果就是不准确的。相比而言,unicorn 在去除花指令或去除 OLLVM 方面更有优势,这是因为:unicorn 相当于一个虚拟 cpu,所以处理的细微程度高于 angr 的代码块级,修复出来的代码精度更高。

 与 angr 类似,unicorn 写去 OLLVM 脚本的思路也是通过代码的CFG图,分析出代码块之间的关系,然后模拟执行每个代码块。但是 fallwind 的脚本是针对 ELF 文件的,我也懒得该脚本了。直接看 wp。wm 给出的 wp 是使用 iPhone 进行的 frida-hook,与我的解题思路不同。

Step6:wp 的复现,wp1wp2。在 wp1 中,我一直不能使自己的 ipad 越狱,会出现以下报错,且我是 intel 的处理器(网上说只有 AMD 的处理器才会报这种错误)。

image-20240309182930221

 可以采取上题 ezandroid 中划分 block 的方法,对 handleButtonClick 函数进行分析。最终用 unicorn 脚本:

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
import idaapi
import idautils
from collections import defaultdict

from unicorn import *
from unicorn.arm64_const import *
from capstone import *

def get_function_blocks(func_ea):
func = idaapi.get_func(func_ea)
if not func:
return None

flowchart = idaapi.FlowChart(func)
blocks = [b for b in flowchart]
assert blocks is not None
return blocks

def get_instructions_in_block(start_addr, end_addr):
instructions = []
for head in idautils.Heads(start_addr, end_addr):
instructions.append((head, idc.GetDisasm(head)))
return instructions

def divide_block_to_different_type(blocks):

def find_entry_blocks(blocks):
""" 入度为 0 """
entry_blocks = []
for block in blocks:
if list(block.preds()) == []:
entry_blocks.append(block)
assert len(entry_blocks) == 1
return entry_blocks

def find_exit_blocks(blocks):
""" 出度为 0 """
exit_blocks = []
for block in blocks:
if list(block.succs()) == []:
exit_blocks.append(block)
assert len(exit_blocks) == 1
return exit_blocks

def find_dispatch_blocks(blocks, n):
""" 入度为 n,出度为 2 """
dispatch_blocks = []
for block in blocks:
if len(list(block.preds())) >= n and len(list(block.succs())) == 2:
dispatch_blocks.append(block)
assert len(dispatch_blocks) >= 1
return dispatch_blocks

def find_if_blocks(blocks):
""" 入度为 1,出度为 2 """
if_blocks = []
for block in blocks:
if len(list(block.preds())) == 1 and len(list(block.succs())) == 2:
if_blocks.append(block)
assert len(if_blocks) >= 1
return if_blocks

def find_real_blocks(blocks, n_instruction):
"""
入度为 1,出度为 1。
"""
real_blocks = []
for block in blocks:
if len(list(block.preds())) == 1 and len(list(block.succs())) == 1:

# 测试其后继是否为 dispatch 块
succ = list(block.succs())[0]
assumption_1 = True
if len(list(succ.preds())) >= 4 and len(list(succ.succs())) == 2:
pass
else:
print("test", hex(block.start_ea))
assumption_1 = False
# assert assumption_1 == True

real_blocks.append(block)
assert len(real_blocks) >= 1
return real_blocks

ret = {}
# ----------------------------------------------------------------------------------------------
# 不同类型的块
info = defaultdict(list)
for b in blocks:
preds = len(list(b.preds()))
succs = len(list(b.succs()))
key = str(preds) + "-" + str(succs)
info[key].append(b)

print("[*] Block types:")
for k in info.keys():
[preds, succs] = k.split("-")
print("[+] preds:{:<2} | succs:{:<2} | num:{:<2}".format(preds, succs, len(info[k])))
# ----------------------------------------------------------------------------------------------
# 函数的入口块和出口块
entry_block = find_entry_blocks(blocks)
exit_block = find_exit_blocks(blocks)

print('[*] Entry blocks:')
print('[+] Start: %s, End: %s' % (hex(entry_block[0].start_ea), hex(entry_block[0].end_ea)))
print('[*] Exit blocks:')
print('[+] Start: %s, End: %s' % (hex(exit_block[0].start_ea), hex(exit_block[0].end_ea)))
# ----------------------------------------------------------------------------------------------
# 函数的 dispatch 块
n = 4
dispatch_blocks = find_dispatch_blocks(blocks, n)

print("[*] Dispatch blocks:")
for b in dispatch_blocks:
print('[+] Start: %s, End: %s' % (hex(b.start_ea), hex(b.end_ea)))
# ----------------------------------------------------------------------------------------------
# 函数的 if 块
if_blocks = find_if_blocks(blocks)

print("[*] If blocks:")
for b in if_blocks:
print('[+] Start: %s, End: %s' % (hex(b.start_ea), hex(b.end_ea)))
# ----------------------------------------------------------------------------------------------
# 函数的真实逻辑块与小块(指令 <= 3)
n_instruction = 3
real_blocks = find_real_blocks(blocks, n_instruction)

print("[*] Real blocks:")
for b in real_blocks:
print('[+] Start: %s, End: %s' % (hex(b.start_ea), hex(b.end_ea)))
# ----------------------------------------------------------------------------------------------
ret['entry_block'] = entry_block
ret['exit_block'] = exit_block
ret['dispatch_blocks'] = dispatch_blocks
ret['if_blocks'] = if_blocks
ret['real_blocks'] = real_blocks
return ret

def read_data(start, end):
return ida_bytes.get_bytes(start, end - start)

def write_data(start, data):
ida_bytes.patch_bytes(start, data)

def unicorn_exec(blocks):
hook_seq = []
main_blocks = blocks['entry_block'] + blocks['exit_block'] + blocks['real_blocks']
main_blocks_addr = set([b.start_ea for b in main_blocks])
start_addr = blocks['entry_block'][0].start_ea
end_addr = blocks['exit_block'][0].start_ea

def hook_code(uc, address, size, user_data):
instruction = uc.mem_read(address, size)
disasm = list(cs.disasm(instruction, address))
assert len(disasm) == 1
disasm = disasm[0]

# 查看是否为函数调用,若为函数调用则跳过
if disasm.mnemonic == "bl":
uc.reg_write(UC_ARM64_REG_PC, address + size)
return

def hook_block(uc, address, size, user_data):
if address in main_blocks_addr:
hook_seq.append((address, size))
if end_addr == address:
uc.emu_stop()
return

# Step1: 模拟准备工作
uc = Uc(UC_ARCH_ARM64, UC_MODE_LITTLE_ENDIAN)
cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM)

mem_size = 1024 * 1024 * 1024 # 1GB
addr = idaapi.get_imagebase()
uc.mem_map(addr, mem_size)

stack_start = 0x80000000
stack_size = 0x10000 * 8 # 512 KB
stack_sp = 0x80000000 + 0x10000 * 6
uc.mem_map(stack_start, stack_size)
uc.reg_write(UC_ARM64_REG_SP, stack_sp)

func = ida_funcs.get_func(start_addr)
func_code = read_data(func.start_ea, func.end_ea)
uc.mem_write(func.start_ea, func_code)

uc.hook_add(UC_HOOK_CODE, hook_code)
uc.hook_add(UC_HOOK_BLOCK, hook_block)

# Step2: 开始模拟
uc.emu_start(start_addr, -1)

# Step3: 展示结果
for ind, addr in enumerate(hook_seq):
print("------------------------- Block {:2d} | Addr {} -------------------------".format(ind, hex(addr[0])))
ins = get_instructions_in_block(addr[0], addr[0] + addr[1])
for i in ins:
print(i)

func_ea = 0x100006D50
blocks = get_function_blocks(func_ea)
different_blocks = divide_block_to_different_type(blocks)

unicorn_exec(different_blocks)

 可以将 handleButtonClick 展平为:

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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
------------------------- Block  0 | Addr 0x100006d50 -------------------------
(4294995280, 'STP X28, X27, [SP,#-0x10+var_50]!')
(4294995284, 'STP X26, X25, [SP,#0x50+var_40]')
(4294995288, 'STP X24, X23, [SP,#0x50+var_30]')
(4294995292, 'STP X22, X21, [SP,#0x50+var_20]')
(4294995296, 'STP X20, X19, [SP,#0x50+var_10]')
(4294995300, 'STP X29, X30, [SP,#0x50+var_s0]')
(4294995304, 'ADD X29, SP, #0x50')
(4294995308, 'SUB SP, SP, #0x100')
(4294995312, 'SUB X8, X29, #-var_38')
(4294995316, 'STUR X2, [X8,#-0x100]')
(4294995320, 'SUB X8, X29, #-var_40')
(4294995324, 'STUR X0, [X8,#-0x100]')
...
(4294995372, 'LDR X9, =sel_show; "show"')
(4294995376, 'NOP')
(4294995380, 'LDR X8, =sel_length; "length"')
(4294995384, 'STUR X8, [X29,#var_98]')
(4294995388, 'NOP')
(4294995392, 'LDR X8, =sel_substringWithRange_; "substringWithRange:"')
(4294995396, 'STP X8, X9, [X29,#var_C0]')
(4294995400, 'NOP')
(4294995404, 'LDR X9, =sel_UTF8String; "UTF8String"')
(4294995408, 'NOP')
(4294995412, 'LDR X8, =sel_hasPrefix_; "hasPrefix:"')
(4294995416, 'STP X8, X9, [X29,#var_D0]')
(4294995420, 'NOP')
(4294995424, 'LDR X9, =sel_theTextField; "theTextField"')
(4294995428, 'NOP')
(4294995432, 'LDR X8, =sel_text; "text"')
(4294995436, 'STP X8, X9, [X29,#var_E0]')
(4294995440, 'NOP')
(4294995444, 'LDR X9, =sel_showJailbreakAlert; "showJailbreakAlert"')
(4294995448, 'NOP')
(4294995452, 'LDR X8, =sel_initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles_; "initWithTitle:message:delegate:cancelBu"...')
(4294995456, 'STP X8, X9, [X29,#var_F0]')
(4294995460, 'NOP')
(4294995464, 'LDR Q0, xmmword_10000DD80')
(4294995468, 'STUR Q0, [X29,#var_100]')
(4294995472, 'NOP')
(4294995476, 'LDR Q0, xmmword_10000DD90')
(4294995480, 'SUB X8, X29, #-var_10')
(4294995484, 'STUR Q0, [X8,#-0x100]')
(4294995488, 'NOP')
(4294995492, 'LDR Q0, xmmword_10000DDA0')
(4294995496, 'SUB X8, X29, #-var_20')
(4294995500, 'STUR Q0, [X8,#-0x100]')
(4294995504, 'NOP')
(4294995508, 'LDR Q0, xmmword_10000DDB0')
(4294995512, 'SUB X8, X29, #-var_30')
(4294995516, 'STUR Q0, [X8,#-0x100]')
(4294995520, 'NOP')
(4294995524, 'LDR X8, =sel_hasSuffix_; "hasSuffix:"')
(4294995528, 'SUB X9, X29, #-var_48')
(4294995532, 'STUR X8, [X9,#-0x100]')
...
------------------------- Block 1 | Addr 0x10000767c -------------------------
(4294997628, 'LDUR W8, [X29,#var_A0]')
(4294997632, 'TST W8, #1')
...
------------------------- Block 2 | Addr 0x100007e1c -------------------------
(4294999580, 'ADR X8, unk_100011CC0')
(4294999584, 'NOP')
(4294999588, 'LDAR W8, [X8]')
(4294999592, 'CMP W8, #0')
(4294999596, 'CSET W8, EQ')
(4294999600, 'STUR W8, [X29,#var_88]')
...
------------------------- Block 3 | Addr 0x10000715c -------------------------
...
------------------------- Block 4 | Addr 0x10000712c -------------------------
...
------------------------- Block 5 | Addr 0x100007b24 -------------------------
(4294998820, 'ADR X9, byte_100011950')
(4294998824, 'NOP')
(4294998828, 'LDRB W8, [X9]')
(4294998832, 'MOV W10, #0x90')
(4294998836, 'EOR W8, W8, W10')
(4294998840, 'ADR X10, asc_100011953; "�\\x0F\\t"')
(4294998844, 'NOP')
(4294998848, 'STRB W8, [X10]; "�\\x0F\\t"')
(4294998852, 'LDRB W8, [X9,#(byte_100011951 - 0x100011950)]')
(4294998856, "MOV W11, #0x50 ; 'P'")
(4294998860, 'EOR W8, W8, W11')
(4294998864, 'STRB W8, [X10,#(asc_100011953+1 - 0x100011953)]; "\\x0F\\t"')
(4294998868, 'LDRB W8, [X9,#(byte_100011952 - 0x100011950)]')
(4294998872, "MOV W9, #0x42 ; 'B'")
(4294998876, 'EOR W8, W8, W9')
(4294998880, 'STRB W8, [X10,#(asc_100011953+2 - 0x100011953)]; "\\t"')
(4294998884, 'ADR X9, byte_100011956')
(4294998888, 'NOP')
(4294998892, 'LDRB W8, [X9]')
(4294998896, 'MOV W10, #0xE8')
(4294998900, 'EOR W8, W8, W10')
(4294998904, 'ADR X10, asc_10001195D; "\\r���\\x01�)"')
(4294998908, 'NOP')
(4294998912, 'STRB W8, [X10]; "\\r���\\x01�)"')
(4294998916, 'LDRB W8, [X9,#(byte_100011957 - 0x100011956)]')
(4294998920, 'EOR W8, W8, #4')
(4294998924, 'STRB W8, [X10,#(asc_10001195D+1 - 0x10001195D)]; "���\\x01�)"')
(4294998928, 'LDRB W8, [X9,#(byte_100011958 - 0x100011956)]')
(4294998932, 'MOV W11, #0xD8')
(4294998936, 'EOR W8, W8, W11')
(4294998940, 'STRB W8, [X10,#(asc_10001195D+2 - 0x10001195D)]; "��\\x01�)"')
(4294998944, 'LDRB W8, [X9,#(byte_100011959 - 0x100011956)]')
(4294998948, 'EOR W8, W8, #0xC')
(4294998952, 'STRB W8, [X10,#(asc_10001195D+3 - 0x10001195D)]; "�\\x01�)"')
(4294998956, 'LDRB W8, [X9,#(byte_10001195A - 0x100011956)]')
(4294998960, 'MOV W11, #0x13')
(4294998964, 'EOR W8, W8, W11')
(4294998968, 'STRB W8, [X10,#(asc_10001195D+4 - 0x10001195D)]; "\\x01�)"')
(4294998972, 'LDRB W8, [X9,#(byte_10001195B - 0x100011956)]')
(4294998976, 'MOV W11, #0xFA')
(4294998980, 'EOR W8, W8, W11')
(4294998984, 'STRB W8, [X10,#(asc_10001195D+5 - 0x10001195D)]; "�)"')
(4294998988, 'LDRB W8, [X9,#(byte_10001195C - 0x100011956)]')
(4294998992, "MOV W9, #0x51 ; 'Q'")
(4294998996, 'EOR W8, W8, W9')
(4294999000, 'STRB W8, [X10,#(asc_10001195D+6 - 0x10001195D)]; ")"')
(4294999004, 'ADR X9, byte_100011964')
(4294999008, 'NOP')
(4294999012, 'LDRB W8, [X9]')
(4294999016, "MOV W10, #0x4A ; 'J'")
(4294999020, 'EOR W8, W8, W10')
(4294999024, 'ADR X10, asc_100011966; "�"')
(4294999028, 'NOP')
(4294999032, 'STRB W8, [X10]; "�"')
(4294999036, 'LDRB W8, [X9,#(byte_100011965 - 0x100011964)]')
------------------------- Block 6 | Addr 0x10000789c -------------------------
(4294998172, 'ADR X8, unk_100011CC0')
(4294998176, 'NOP')
(4294998180, 'MOV W9, #1')
(4294998184, 'STLR W9, [X8]')
(4294998188, 'MOV X8, SP')
(4294998192, 'SUB X9, X8, #0x10')
(4294998196, 'STUR X9, [X29,#var_68]')
(4294998200, 'MOV SP, X9')
(4294998204, 'MOV X23, SP')
(4294998208, 'SUB X9, X23, #0x10')
(4294998212, 'STUR X9, [X29,#var_A8]')
(4294998216, 'MOV SP, X9')
(4294998220, 'MOV X21, SP')
(4294998224, 'SUB X9, X21, #0x10')
(4294998228, 'STUR X9, [X29,#var_78]')
(4294998232, 'MOV SP, X9')
(4294998236, 'SUB X22, SP, #0x10')
(4294998240, 'MOV SP, X22')
(4294998244, 'SUB X9, SP, #0x10')
(4294998248, 'STUR X9, [X29,#var_B0]')
(4294998252, 'MOV SP, X9')
(4294998256, 'SUB X9, SP, #0x10')
(4294998260, 'STUR X9, [X29,#var_80]')
(4294998264, 'MOV SP, X9')
(4294998268, 'SUB X9, SP, #0x10')
(4294998272, 'STUR X9, [X29,#var_70]')
(4294998276, 'MOV SP, X9')
(4294998280, 'SUB X9, X29, #-var_40')
(4294998284, 'LDUR X9, [X9,#-0x100]')
(4294998288, 'STUR X9, [X8,#-0x10]')
(4294998292, 'SUB X8, X29, #-var_38')
(4294998296, 'LDUR X0, [X8,#-0x100]; id')
(4294998300, 'BL _objc_retain')
------------------------- Block 7 | Addr 0x100007778 -------------------------
(4294997880, 'LDUR X8, [X29,#var_68]')
(4294997884, 'LDR X0, [X8]; id')
(4294997888, 'LDUR X1, [X29,#var_D8]; SEL')
(4294997892, 'BL _objc_msgSend')
------------------------- Block 8 | Addr 0x10000767c -------------------------
(4294997628, 'LDUR W8, [X29,#var_A0]')
(4294997632, 'TST W8, #1')
(4294997636, 'MOV W8, #0x1663B27E')
(4294997644, 'CSEL W8, W8, W23, NE')
(4294997648, 'MOV W10, #0xEA914DFB')
(4294997656, 'CMP W9, W10')
(4294997660, 'CSEL W8, W8, W9, EQ')
(4294997664, 'CSEL W10, WZR, W24, EQ')
(4294997668, 'MOV W11, #0xEA5AAFFE')
(4294997676, 'CMP W9, W11')
(4294997680, 'MOV W9, #0x6C694815')
(4294997688, 'CSEL W8, W9, W8, EQ')
(4294997692, 'CSEL W10, W24, W10, EQ')
(4294997696, 'B loc_100006E70')
------------------------- Block 9 | Addr 0x100007288 -------------------------
(4294996616, 'MOV W8, #0xD259F2E6')
(4294996624, 'CMP W9, W8')
(4294996628, 'CSEL W8, W21, W9, EQ')
(4294996632, 'MOV W10, #0xCF9F21BD')
(4294996640, 'CMP W9, W10')
(4294996644, 'MOV W10, #0x667AEC8C')
(4294996652, 'CSEL W8, W10, W8, EQ')
(4294996656, 'CMP W9, W23')
(4294996660, 'MOV W9, #0xC8F813D0')
(4294996668, 'CSEL W8, W9, W8, EQ')
(4294996672, 'LDUR W9, [X29,#var_5C]')
(4294996676, 'CSEL W9, W24, W9, EQ')
(4294996680, 'STUR W9, [X29,#var_5C]')
(4294996684, 'MOV X10, X24')
(4294996688, 'B loc_100006E70')
------------------------- Block 10 | Addr 0x1000077e8 -------------------------
(4294997992, 'LDUR X8, [X29,#var_70]')
(4294997996, 'LDRB W8, [X8]')
(4294998000, 'STUR W8, [X29,#var_84]')
(4294998004, 'MOV W8, #0x84B17691')
(4294998012, 'MOV X10, X24')
------------------------- Block 11 | Addr 0x1000071c8 -------------------------
(4294996424, 'LDUR W8, [X29,#var_84]')
(4294996428, 'TST W8, #1')
(4294996432, 'MOV W11, #0x3BE56CAF')
(4294996440, 'MOV W8, #0xCAC4CC96')
(4294996448, 'CSEL W8, W8, W11, NE')
(4294996452, 'LDUR W10, [X29,#var_5C]')
(4294996456, 'TST W10, #1')
(4294996460, 'MOV W10, #0xAC51EE34')
(4294996468, 'MOV W12, #0x359CA206')
(4294996476, 'CSEL W10, W12, W10, NE')
(4294996480, 'MOV W12, #0x8F7507B2')
(4294996488, 'CMP W9, W12')
(4294996492, 'CSEL W10, W10, W9, EQ')
(4294996496, 'MOV W12, #0x85E430EB')
(4294996504, 'CMP W9, W12')
(4294996508, 'CSEL W10, W11, W10, EQ')
(4294996512, 'MOV W11, #0x84B17691')
(4294996520, 'CMP W9, W11')
(4294996524, 'CSEL W8, W8, W10, EQ')
(4294996528, 'MOV X10, X24')
(4294996532, 'B loc_100006E70')
------------------------- Block 12 | Addr 0x100006eb8 -------------------------
(4294995640, 'LDUR W8, [X29,#var_9C]')
(4294995644, 'TST W8, #1')
(4294995648, 'MOV W8, #0xD71F0709')
(4294995656, 'CSEL W8, W8, W23, NE')
(4294995660, 'CMP W9, W21')
(4294995664, 'MOV W10, #0x8F7507B2')
(4294995672, 'CSEL W10, W10, W9, EQ')
(4294995676, 'MOV W11, #0xF8F0E135')
(4294995684, 'CMP W9, W11')
(4294995688, 'CSEL W8, W8, W10, EQ')
(4294995692, 'CSEL W10, WZR, W24, EQ')
(4294995696, 'B loc_100006E70')
------------------------- Block 13 | Addr 0x1000071c8 -------------------------
(4294996424, 'LDUR W8, [X29,#var_84]')
(4294996428, 'TST W8, #1')
(4294996432, 'MOV W11, #0x3BE56CAF')
(4294996440, 'MOV W8, #0xCAC4CC96')
(4294996448, 'CSEL W8, W8, W11, NE')
(4294996452, 'LDUR W10, [X29,#var_5C]')
(4294996456, 'TST W10, #1')
(4294996460, 'MOV W10, #0xAC51EE34')
(4294996468, 'MOV W12, #0x359CA206')
(4294996476, 'CSEL W10, W12, W10, NE')
(4294996480, 'MOV W12, #0x8F7507B2')
(4294996488, 'CMP W9, W12')
(4294996492, 'CSEL W10, W10, W9, EQ')
(4294996496, 'MOV W12, #0x85E430EB')
(4294996504, 'CMP W9, W12')
(4294996508, 'CSEL W10, W11, W10, EQ')
(4294996512, 'MOV W11, #0x84B17691')
(4294996520, 'CMP W9, W11')
(4294996524, 'CSEL W8, W8, W10, EQ')
(4294996528, 'MOV X10, X24')
(4294996532, 'B loc_100006E70')
------------------------- Block 14 | Addr 0x100007840 -------------------------
(4294998080, 'NOP')
(4294998084, 'LDR X0, =_OBJC_CLASS_$_UIAlertView; Class')
(4294998088, 'BL _objc_alloc')
------------------------- Block 15 | Addr 0x10000715c -------------------------
(4294996316, 'LDUR W8, [X29,#var_88]')
(4294996320, 'TST W8, #1')
(4294996324, 'MOV W8, #0x1E3B6CBA')
(4294996332, 'MOV W10, #0xDB3303D3')
(4294996340, 'CSEL W8, W10, W8, NE')
(4294996344, 'MOV W10, #0x667AEC8C')
(4294996352, 'CMP W9, W10')
(4294996356, 'MOV W10, #0xB92A2324')
(4294996364, 'CSEL W10, W10, W9, EQ')
(4294996368, 'MOV W11, #0x65FF66ED')
(4294996376, 'CMP W9, W11')
(4294996380, 'CSEL W8, W8, W10, EQ')
(4294996384, 'MOV W10, #0x5F66DF9D')
(4294996392, 'CMP W9, W10')
(4294996396, 'MOV W9, #0x3E81DC70')
(4294996404, 'B loc_10000727C')
------------------------- Block 16 | Addr 0x100006fc4 -------------------------
(4294995908, 'LDR X0, [X22]; id')
(4294995912, 'BL _objc_release')
------------------------- Block 17 | Addr 0x100007288 -------------------------
(4294996616, 'MOV W8, #0xD259F2E6')
(4294996624, 'CMP W9, W8')
(4294996628, 'CSEL W8, W21, W9, EQ')
(4294996632, 'MOV W10, #0xCF9F21BD')
(4294996640, 'CMP W9, W10')
(4294996644, 'MOV W10, #0x667AEC8C')
(4294996652, 'CSEL W8, W10, W8, EQ')
(4294996656, 'CMP W9, W23')
(4294996660, 'MOV W9, #0xC8F813D0')
(4294996668, 'CSEL W8, W9, W8, EQ')
(4294996672, 'LDUR W9, [X29,#var_5C]')
(4294996676, 'CSEL W9, W24, W9, EQ')
(4294996680, 'STUR W9, [X29,#var_5C]')
(4294996684, 'MOV X10, X24')
(4294996688, 'B loc_100006E70')
------------------------- Block 18 | Addr 0x10000715c -------------------------
(4294996316, 'LDUR W8, [X29,#var_88]')
(4294996320, 'TST W8, #1')
(4294996324, 'MOV W8, #0x1E3B6CBA')
(4294996332, 'MOV W10, #0xDB3303D3')
(4294996340, 'CSEL W8, W10, W8, NE')
(4294996344, 'MOV W10, #0x667AEC8C')
(4294996352, 'CMP W9, W10')
(4294996356, 'MOV W10, #0xB92A2324')
(4294996364, 'CSEL W10, W10, W9, EQ')
(4294996368, 'MOV W11, #0x65FF66ED')
(4294996376, 'CMP W9, W11')
(4294996380, 'CSEL W8, W8, W10, EQ')
(4294996384, 'MOV W10, #0x5F66DF9D')
(4294996392, 'CMP W9, W10')
(4294996396, 'MOV W9, #0x3E81DC70')
(4294996404, 'B loc_10000727C')
------------------------- Block 19 | Addr 0x100007a28 -------------------------
(4294998568, 'LDUR X21, [X29,#var_78]')
(4294998572, 'LDR X0, [X21]; id')
(4294998576, 'LDUR X1, [X29,#var_B8]; SEL')
(4294998580, 'BL _objc_msgSend')

 展平脚本还是不对,猜测是函数返回的结果也会影响执行流。

Step7:硬看 OLLVM。几个函数:

1
2
3
4
5
1. objc_msgSend:当向一个对象发送消息时,objc_msgSend 负责找到对应的方法实现并执行它。

2. objc_retainAutoreleasedReturnValue:当一个方法返回一个自动释放的对象作为其返回值时,此函数负责捕获这个返回值,并将其注册为需要保留(retain)的对象,这样调用者就可以安全地使用它,而不用担心它会在自动释放池清空时被释放。

3. objc_retainAutorelease:将一个对象加入到当前的自动释放池中,同时保留(retain)它。这意味着对象将在自动释放池被清空时被释放(release)。

 可以得到一些信息:

image-20240315205527333

image-20240315205539041

image-20240315205559054

 跟踪此函数,发现有 RC4 算法(主要是问了一下 GPT),然后又发现:

image-20240315211009852

 这题需要读 __dyld_get_image_header/__dyld_get_image_vmaddr_slide,可能必须得动调?直接放弃好吧。草!我怎么这么菜。

0x04 RightBack

1
wmctf{...}

 pyc 文件逆向。使用 pycdc 反编译失败。直接拿到字节码(并且由于 pyc 文件做了某些改动,需要修 dis.py):

1
2
3
4
5
6
import dis
import marshal
f = open("C:\\Users\\wd2711\\Desktop\\RightBack.pyc","rb").read()
code = marshal.loads(f[16:])
dis.dis(code)
print(code)

image-20240325174438346

 可以得到字节码,分析后得到:

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
import struct

def T(num, round):
numArr = bytearray(struct.pack('<I', num))
for i in range(4):
numArr[i] = Sbox[numArr[i]]
newNum = struct.unpack('<I', numArr)[0] ^ Rcon[round]
return newNum

def p1(s, key):
j = 0
k = []
for i in range(256):
s.append(i)
k.append(key[i % len(key)])
for i in range(256):
j = (j + s[i] + ord(k[i])) % 256
s[i], s[j] = s[j], s[i]
return None

def p2(key):
w = [0] * 44
for i in range(4):
w[i] = struct.unpack('<I', key[i*4:(i+1)*4])[0]
cnt = 0
for i in range(4, 44, 1):
if i % 4 == 0:
w[i] = w[i - 4] ^ T(w[i - 1], cnt)
cnt += 1
else:
w[i] = w[i - 4] ^ w[i - 1]
return w

def p3(s, p):
i = j = 0
for z in range(len(p)):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
p[z] ^= s[(s[i] + s[j]) % 256]
return p

REG = {}
def VM(part1, part2):
def F1(part1, part2):
global REG
REG = {'EAX': 0, 'EBX': 0, 'ECX': 0, 'EDX': 0, 'R8': 0, 'CNT': 0, 'EIP': 0}

REG['EAX'] = part1
REG['EBX'] = part2
return None

def F2(v1, v2, v3):
global REG
if v1 == 1:
REG[reg_table[str(v2)]] = extendKey[REG[reg_table[str(v3)]]]
elif v1 == 2:
REG[reg_table[str(v2)]] = REG[reg_table[str(v3)]]
elif v1 == 3:
REG[reg_table[str(v2)]] = v3
REG['EIP'] += 4
return None

def F3(v1, v2, v3):
global REG
if v1 == 1:
REG[reg_table[str(v2)]] = (REG[reg_table[str(v2)]] + extendKey[REG[reg_table[str(v3)]]]) & 4294967295
elif v1 == 2:
REG[reg_table[str(v2)]] = (REG[reg_table[str(v2)]] + REG[reg_table[str(v3)]]) & 4294967295
elif v1 == 3:
REG[reg_table[str(v2)]] = (REG[reg_table[str(v2)]] + v3) & 4294967295
REG['EIP'] += 4
return None

def F4(v1, v2):
global REG
REG[reg_table[str(v1)]] ^= REG[reg_table[str(v2)]]
REG['EIP'] += 3
return None

def F5(v1, v2):
global REG
REG[reg_table[str(v1)]] &= v2
REG['EIP'] += 3
return None

def F6(v1, v2, v3):
global REG
if v1 == 1:
REG[reg_table[str(v2)]] -= extendKey[v3]
elif v1 == 2:
REG[reg_table[str(v2)]] -= REG[reg_table[str(v3)]]
elif v1 == 3:
REG[reg_table[str(v2)]] -= v3
REG['EIP'] += 4
return None

def F7(v1, v2):
global REG
REG[reg_table[str(v1)]] |= REG[reg_table[str(v2)]]
REG['EIP'] += 3
return None

def F8(v1, v2):
global REG
REG[reg_table[str(v1)]] = (REG[reg_table[str(v2)]] >> REG[reg_table[str(v1)]]) & 4294967295
REG['EIP'] += 3
return None

def F9(v1, v2):
global REG
REG[reg_table[str(v1)]] = ((REG[reg_table[str(v1)]] << REG[reg_table[str(v2)]]) & 4294967295)
REG['EIP'] += 3
return None

def FA(v1, v2, v3):
global REG
if v1 == 1:
REG[reg_table[str(v2)]] *= extendKey[v3]
elif v1 == 2:
REG[reg_table[str(v2)]] *= REG[reg_table[str(v3)]]
elif v1 == 3:
REG[reg_table[str(v2)]] *= v3
REG['EIP'] += 4
return None

def FB():
global REG
if REG['CNT'] == 21:
REG['R8'] = -REG['R8']
REG['EIP'] += 1
return None

def WC():
global REG
if REG['R8']:
pass
REG['EIP'] += 1
return None

F1(part1, part2)
EIP = REG['EIP']

while True:
op = opcode[EIP]
if op == 80:
F2(opcode[EIP + 1], opcode[EIP + 2], opcode[EIP + 3])
EIP += 4
elif op == 29:
F3(opcode[EIP + 1], opcode[EIP + 2], opcode[EIP + 3])
EIP += 4
elif op == 113:
F4(opcode[EIP + 1], opcode[EIP + 2])
EIP += 3
elif op == 114:
F5(opcode[EIP + 1], opcode[EIP + 2])
EIP += 3
elif op == 150:
F6(opcode[EIP + 1], opcode[EIP + 2], opcode[EIP + 3])
EIP += 4
elif op == 87:
F7(opcode[EIP + 1], opcode[EIP + 2])
EIP += 3
elif op == 116:
F8(opcode[EIP + 1], opcode[EIP + 2])
EIP += 3
elif op == 41:
F9(opcode[EIP + 1], opcode[EIP + 2])
EIP += 3
elif op == 220:
FA(opcode[EIP + 1], opcode[EIP + 2], opcode[EIP + 3])
EIP += 4
elif op == 7:
FB()
EIP += 1
elif op == 153:
WC()
EIP += 1
else:
break

def Have():
Hello = "Large ASCII art or text here"
print(Hello)
return input('RightBack: ')

def Fun(right):
if len(right) != 64:
print('XD')
exit(0)

back = b''
for i in range(0, 64, 8):
part1 = struct.unpack('>I', right[i:i+4])[0]
part2 = struct.unpack('>I', right[i+4:i+8])[0]
if i != 0:
part1 ^= struct.unpack('>I', back[i-8:i-4])[0]
part2 ^= struct.unpack('>I', back[i-4:i])[0]

VM(part1, part2)
back += struct.pack('>I', REG['EAX'])
back += struct.pack('>I', REG['EBX'])
return back


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

data1 = [228, 244, 207, 251, 194, 124, 252, 61, 198, 145, 97, 98, 89, 25, 92, 208, 155, 38, 34, 225, 98, 206, 234, 245, 223, 54, 214, 137, 35, 86, 180, 66, 223, 234, 90, 136, 5, 189, 166, 117, 111, 222, 39, 156, 163, 173, 36, 174, 47, 144, 15, 160, 45, 239, 211, 11, 190, 181, 24, 164, 234, 114, 174, 27]
data1 = bytes(p3(s, data1))
data2 = [165, 83, 203, 51, 99, 164, 30, 91, 230, 64, 181, 55, 190, 47, 125, 240, 186, 173, 116, 47, 89, 64, 68, 215, 124, 138, 34, 175, 60, 136, 77, 216, 250, 127, 14, 14, 66, 168, 198, 247, 252, 189, 243, 239, 25, 63, 143, 7, 177, 13, 99, 226, 100, 6, 207, 77, 46, 136, 251, 123, 225, 27, 76, 183]
data2 = bytes(p3(s, data2))
data3 = [95, 219, 46, 178, 111, 141, 17, 168, 254, 60, 68, 59, 41, 183, 182, 118, 3, 47, 150, 240, 140, 159, 110, 238]
data3 = bytes(p3(s, data3))

if back == data2:
print('WOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOWOW!!!')
else:
print('I believe you can do it!')

 其中,Fun 函数是一个 vm,经过实验,其步骤为 8 轮相同操作:

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
ECX =  0
EAX = (EAX + extendKey[ECX]) & 0xffffffff
ECX = 1
EBX = (EBX + extendKey[ECX]) & 0xffffffff
CNT = (CNT + 1) & 0xffffffff
EAX = EAX ^ EBX
ECX = EAX
R8 = EBX
EBX = EBX & 31
EAX = (EAX >> EBX) & 0xffffffff
EDX = 32
EDX = EDX - EBX
ECX = (EDX >> ECX) & 0xffffffff
EAX = EAX | ECX
EBX = CNT
EBX = EBX * 2
ECX = extendKey[EBX]
EAX = (EAX + ECX) & 0xffffffff
EBX = R8
EBX = EBX ^ EAX
ECX = EBX
EDX = EAX
EDX = EDX & 31
EBX = (EBX >> EDX) & 0xffffffff
R8 = 32
R8 = R8 - EDX
ECX = (R8 >> ECX) & 0xffffffff
EBX = EBX - ECX
ECX = CNT
ECX = ECX * 2
ECX = (ECX + 1) & 0xffffffff
EDX = extendKey[ECX]
EBX = (EBX + EDX) & 0xffffffff
1
2
3
4
5
6
7
8
9
c   =  (a + extendKey[0]) & 0xffffffff
d = (b + extendKey[1]) & 0xffffffff
e = ((c^d) >> (d&31)) & 0xffffffff
f = ((32-(d&31)) >> (c^d)) & 0xffffffff
EAX = (e|f + extendKey[2])&0xffffffff
h = ((d^EAX)>>(EAX&31))&0xffffffff
i = ((32-(EAX&31))>>(d^EAX))&0xffffffff
g = extendKey[3]
EBX = ((h-i)+g)&0xffffffff

 可以得到 extendKey 与 data2:

1
2
3
4
5
extendKey:
[1835819331, 1853321028, 1768711490, 1432712805, 2177920767, 4020699579, 2261476601, 3551400604, 711874531, 3318306392, 1124217505, 2427199549, 3099853672, 2098025776, 1041196945, 2929936300, 246748610, 1941455090, 1303848803, 3809763535, 1395557789, 546751855, 1830937100, 2385871555, 2516030638, 3043054017, 3628118989, 1450520846, 1825094265, 3651791800, 32069749, 1469868411, 919887482, 4017993154, 4002737591, 3104343244, 4134211933, 420914335, 4152510760, 1317719524, 1990496755, 1873950060, 2553314372, 3602559392]

data2:
[4, 58, 242, 54, 86, 177, 154, 252, 247, 30, 33, 220, 219, 143, 142, 148, 77, 52, 231, 157, 156, 82, 12, 110, 251, 250, 213, 253, 50, 249, 120, 44, 187, 190, 57, 193, 217, 133, 117, 182, 40, 248, 204, 120, 164, 228, 133, 146, 14, 189, 114, 197, 175, 135, 145, 42, 139, 241, 239, 150, 22, 96, 209, 18]

 之后的脚本不想写了(分析了就感觉有点小麻烦),直接给佬的 wp(一个 RC5 脚本):

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
#include <stdio.h>
#include <stdint.h>

static uint32_t extendKey[44] = {1835819331, 1853321028, 1768711490, 1432712805, 2177920767, 4020699579, 2261476601, 3551400604, 711874531, 3318306392, 1124217505, 2427199549, 3099853672, 2098025776, 1041196945, 2929936300, 246748610, 1941455090, 1303848803, 3809763535, 1395557789, 546751855, 1830937100, 2385871555, 2516030638, 3043054017, 3628118989, 1450520846, 1825094265, 3651791800, 32069749, 1469868411, 919887482, 4017993154, 4002737591, 3104343244, 4134211933, 420914335, 4152510760, 1317719524, 1990496755, 1873950060, 2553314372, 3602559392};

typedef struct {
uint32_t p1;
uint32_t p2;
} uint32_pair;

uint32_t ror32(uint32_t v, uint32_t r) {
return (v >> r) | (v << (32 - r));
}

uint32_pair decrypt(uint32_t p1, uint32_t p2) {
for (int i = 21; i > 0; --i) {
p2 -= extendKey[i * 2 + 1];
p2 = ror32(p2, p1 & 31);
p2 ^= p1;
p1 -= extendKey[i * 2];
p1 = ror32(p1, p2 & 31);
p1 ^= p2;
}
p2 -= extendKey[1];
p1 -= extendKey[0];
uint32_pair ret = {p1, p2};
return ret;
}

int main() {
uint8_t flag[] = {4, 58, 242, 54, 86, 177, 154, 252, 247, 30, 33, 220, 219, 143, 142, 148, 77, 52, 231, 157, 156, 82, 12, 110, 251, 250, 213, 253, 50, 249, 120, 44, 187, 190, 57, 193, 217, 133, 117, 182, 40, 248, 204, 120, 164, 228, 133, 146, 14, 189, 114, 197, 175, 135, 145, 42, 139, 241, 239, 150, 22, 96, 209, 18, 0};
uint32_pair last;
for (int i = 0; i < 64; i += 8) {
uint32_t p1 = (flag[i + 0] << 24 | flag[i + 1] << 16 | flag[i + 2] << 8 | flag[i + 3]);
uint32_t p2 = (flag[i + 4] << 24 | flag[i + 5] << 16 | flag[i + 6] << 8 | flag[i + 7]);
uint32_pair cur = decrypt(p1, p2);
if (i != 0) {
cur.p1 ^= last.p1;
cur.p2 ^= last.p2;
}
flag[i + 3] = cur.p1 & 0xff;
flag[i + 2] = (cur.p1 >> 8) & 0xff;
flag[i + 1] = (cur.p1 >> 16) & 0xff;
flag[i + 0] = (cur.p1 >> 24) & 0xff;
flag[i + 7] = cur.p2 & 0xff;
flag[i + 6] = (cur.p2 >> 8) & 0xff;
flag[i + 5] = (cur.p2 >> 16) & 0xff;
flag[i + 4] = (cur.p2 >> 24) & 0xff;
last.p1 = p1;
last.p2 = p2;
}
printf("%s", flag);
return 0;
}
// WMCTF{G00dEv3ning!Y0uAreAwes0m3!!RightBackFromB1ackM1rr0r!WOW!!}

 但是实际上,自己的反编译还是有点错误的,从而导致输入正确的 flag 之后还是报 i believe you can do it。 PZ 是做了一个很严谨的去花:PoZeep (P.Z) · GitHub,还是得细心一点,匠人精神!

留言

2023-08-20

© 2024 wd-z711

⬆︎TOP