Offensive and defensive world difficulty 6 wp.

历经千辛万苦,终于到难度6啦。


easy_Maze

 64位ELF。

image-20230522105434362

 第一个红框生成迷宫,第二个红框开始走迷宫。跟踪Step_2,得到:

image-20230522105513692

 动态调试得到迷宫:

1
2
3
4
5
6
7
1001111
1011001
1110111
0001100
1111000
1000111
1111101

 从左上角走到右下角,最后flag为UNCTF{ssddwdwdddssaasasaaassddddwdds}

ey-or

 题目说此文件包含一个标志,并提取。tar.gz文件。文件比较大,23MB。

任务1:找到print函数是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
调用链:
1. print sub_300000114AC8
2. print sub_600000603960
3. print sub_600000DE9230
4. print sub_300000115A74
# 5. print sub_600000DE9230 jmp 00003000001164F5 (没打断点)
6. print sub_300000114AC8 (没打断点)
7. print 0000600000E1FD00
8. print 600000163050h, jmp 60000053B110
9. print sub_3000001164F5
10.print sub_300000114AC8
11.000030000011B284
12.没有 call, syscall

输入存在哪儿:

1
2
3
4
5
6
7
8
9
10
11
12
13
# flag{afffffffffffffffffffffffffffffffff}
# 1lag{afffffffffffffffffffffffffffffffff}
# 1234567812345678123456781234567812345678
最初:0000600000652878-000060000065289F
之后:000060000064D0A8-000060000064D0CF(用于判断长度)
最后:000060000064C188-000060000064C1AF(用于判断是否是0-9)
还有:0000600000656088-00006000006560AF
还调试到程序拿 000060000064C188 中的字符作比较,判断是否在 "0-9" 之间
000060000064C168 + F40
000060000064C188 third
000060000064D0A8 + 57D0 second
复制flag中的字符到0000600000651FA8中(应该不是)
0000600000651190 ascii 码表(4个)

 调试了一天半,没做出来,仍需努力。(感觉还是不够耐心,看到代码太多太复杂就没有勇气看了

0x01 wp复现

 佬们说这是Elymas语言。说实话,这个,就算我再看一天,应该也看不出来。(搜索资料的能力太差了。)

 其他师傅首先看文件字符串,看到有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
] == f
secret len == l
[ ] == buffer
0 == i
0 == j
"Enter Password line by line\n" sys .out .writeall
{
#str .fromArray secret bxor
txt .consume .u
=j
[ buffer _ len dearray j ] = buffer
[ secret _ len dearray j eq { } { 1 sys .exit } ? * ] = secret
i 1 add = i
i l eq {
buffer f bxor str .fromArray sys .out .writeall
0 sys .exit
} { } ? *
} sys .in .eachLine
} "ey_or" sys .freeze
1
2
3
4
5
	Groovy 编程语言编写的程序,用于读取用户输入的密码并进行加密解密操作。

首先,程序输出一条提示信息,要求用户逐行输入密码。然后,程序使用一个循环来逐行读取用户输入的密码。在每次循环中,程序会将输入的密码和预设的密钥进行异或操作,并将结果存储在一个名为 buffer 的数组中。同时,程序也会将预设的密钥和输入的密码存储在名为 secret 的数组中,以便后续的解密操作使用。

在每次循环结束后,程序会将循环计数器 i 的值加 1。当 i 的值等于密码数组的长度时,程序会将 buffer 数组和预设的密钥数组进行异或操作,并将结果输出到标准输出。然后,程序会结束执行并退出。

 反正我没根据上述程序搜到Elymas语言,直接给chatGPT,它的解释如上所示。整理一下程序思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
i = 0
j = 0
print("Enter Password line by line")
secret = [??]
f = [??]
l = len(secret)
buffer = []
while True:
j = readline()
buffer.append(j)
if secert[i] != j:
exit(0)
i += 1
if i == l:
print(buffer ^ f)
exit(0)

 那么我们就可以:输入一个字符,如果程序结束,则说明输入的字符不对,如果程序继续运行,那么程序的输入就是正确的。

 下面是使用subprocess操控程序的脚本,自己写的。

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 time
import subprocess
from tqdm import tqdm

result = []
while True:
# 遍历每一个字符
for inp in tqdm(range(256)):
process = subprocess.Popen(['./ey_or'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

# 构造输入
all_inp_str = ""
for i in result:
all_inp_str += i
inp_str = str(inp) + "\n"
all_inp_str += inp_str

process.stdin.write(all_inp_str.encode())
process.stdin.close()

return_code = process.wait(0.5)

# 如果程序结束,则返回 1
if return_code == 1:
continue
# 程序正常结束,返回 0
elif return_code == 0:
print("input:", result)
exit(0)
else:
result.append(inp_str)
break

process = subprocess.Popen(['./ey_or'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
result = ['36\n', '30\n', '156\n', '30\n', '43\n', '6\n', '116\n', '22\n', '211\n', '66\n', '151\n', '89\n', '36\n', '82\n', '254\n', '81\n', '182\n', '134\n', '24\n', '90\n', '119\n', '6\n', '88\n', '137\n', '64\n', '197\n', '251\n', '15\n', '116\n', '220\n', '161\n', '94\n', '154\n', '252\n', '139\n', '11\n', '41\n', '215\n', '27\n', '158\n', '143\n', '140\n', '54\n', '189\n', '146\n', '48\n', '167\n', '56\n', '84\n', '226\n', '15\n', '188\n', '126\n', '24\n']
all_inp_str = ""
for i in result:
all_inp_str += i
stdout_data, stderr_data = process.communicate(input=all_inp_str.encode())
print(stdout_data)
# 32C3_wE_kNoW_EvErYbOdY_LiKeS_eLyMaS

 总结一下:不要头铁,多找找题目信息。一股脑调试2天,自己真傻逼呀。

libdroid

 apk文件,用jeb打开,审计字节码。

 发现:

image-20230524125534936

 但是getFlag是在本地实现的,其声明如下:

1
public static native String getFlag() {}

 于是猜测是不是在apk自带的库中,发现:

image-20230524125711635

 这不是flag,返回的值为v2函数处理后的,v2是由v1得来的,v1是此函数的参数,并不清楚此函数参数的传值。所以打算使用mumu+jeb3动态调试此程序。

 结果此apk不支持调试,所以打算加上调试选项(重签名等操作),最后得到:

image-20230524142701240

 对此apk进行动态调试,如下所示:

image-20230524165057620

 其中,v7v4分别为:

1
2
3
v7 = ['FE', 'A0', 'AD', '80', '20', '59', 'AB', '12', 'D7', 'C3', '9C', '88', 'FA', '2C', '1D', 'FC', '81', '46', '0D', 'DC', 'E9', 'CE', 'CC', '57', '78', 'F5', '41', '5F', '52', '2', '36', 'D5', '33', '18', '66', '3A', '40', '26', 'E8', '6E', 'B6', 'CD', '72', 'B7', '3C', '1', '66', 'B1', '4F', '99', '23', '63', '95', '77', '34', '61', '69', 'F6', 'A9', '53', '40', '37', '41', '43', '4F', '98', '95', '2C', '7A', '27', '3C', '98', '68', '1A', '88', 'A8', 'B7', '85', 'BB', '15', '4F', '1A', '1', '4D', 'C9', 'C8', '9B', '75', '78', '57', '7F', '98', '0D', 'D8', '51', 'A8', '22', 'B9', '5E', '59', '4D', '71', '4F', '1A', '81', 'A9', 'BF', '7', '29', 'ED', 'FD', '83']

v4 = ['21', '0', '86', 'B7', 'FF', '86', '5D', '6', '2D', '30', 6 次输入]

 其中,phoneHome函数如下所示:

image-20230524173252277

 这是一个静态函数,保存在liblibdroid.so中,里面第18行、19行、20行调用的别的函数,是静态链接到bytecode上的。

image-20230524173332616

 下面需要调研一下静态链接是怎么连接到apk文件中的。

 没咋调研出来,准备直接猜上面函数的各种操作,如下所示:

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
from tqdm import tqdm
import time
a3_init = ['FE', 'A0', 'AD', '80', '20', '59', 'AB', '12', 'D7', 'C3', '9C', '88', 'FA', '2C', '1D', 'FC', '81', '46', '0D', 'DC', 'E9', 'CE', 'CC', '57', '78', 'F5', '41', '5F', '52', '2', '36', 'D5', '33', '18', '66', '3A', '40', '26', 'E8', '6E', 'B6', 'CD', '72', 'B7', '3C', '1', '66', 'B1', '4F', '99', '23', '63', '95', '77', '34', '61', '69', 'F6', 'A9', '53', '40', '37', '41', '43', '4F', '98', '95', '2C', '7A', '27', '3C', '98', '68', '1A', '88', 'A8', 'B7', '85', 'BB', '15', '4F', '1A', '1', '4D', 'C9', 'C8', '9B', '75', '78', '57', '7F', '98', '0D', 'D8', '51', 'A8', '22', 'B9', '5E', '59', '4D', '71', '4F', '1A', '81', 'A9', 'BF', '7', '29', 'ED', 'FD', '83']
a4_init = ['21', '0', '86', 'B7', 'FF', '86', '5D', '6', '2D', '30']
print(len(a3_init))
all_in = [0x20, ]
for i1 in tqdm(all_in):
for i2 in all_in:
for i3 in all_in:
for i4 in all_in:
for i5 in all_in:
for i6 in all_in:
a3 = [int(i, 16) for i in a3_init]
print([hex(i) for i in a3])
a4 = [int(i, 16) for i in a4_init] + [i1, i2, i3, i4, i5, i6]
print([hex(i) for i in a4])
assert len(a4) == 16

v8 = a4
v10 = v8[0] | (v8[3] << 24) | (v8[1] << 8) | (v8[2] << 16)
v11 = (v8[5] << 8) | v8[4] | (v8[7] << 24) | (v8[6] << 16)
v12 = v8[8] | (v8[11] << 24) | (v8[9] << 8) | (v8[10] << 16)
v13 = v8[12] | (v8[15] << 24) | (v8[13] << 8) | (v8[14] << 16)

v14 = 1
v15 = 8 * ((len(a3)-1) >> 3) + 9
while True:
v16 = 0xD5B7DDE0
v17 = (a3[v14] << 8) | a3[v14-1] | (a3[v14+2] << 24) | (a3[v14+1] << 16)
v18 = (a3[v14+4] << 8) | a3[v14+3] | (a3[v14+6] << 24) | (a3[v14+5] << 16)
while True:
v18 = (v18 - ((v17 + v16) ^ (16 * v17 + v12) ^ (v13 + (v17 >> 5)))) & 0xffffffff
v17 = (v18 - ((v18 + v16) ^ (16 * v18 + v10) ^ (v11 + (v18 >> 5)))) & 0xffffffff
v16 = (v16 + 0x21524111) & 0xffffffff
if v16 == 0:
break
v14 += 8
v17_bytelow = (v17) & 0xff
v17_byte1 = (v17 >> 8) & 0xff
v17_byte2 = (v17 >> 16) & 0xff
v17_bytehigh = (v17 >> 24) & 0xff

v18_bytelow = (v18) & 0xff
v18_byte1 = (v18 >> 8) & 0xff
v18_byte2 = (v18 >> 16) & 0xff
v18_bytehigh = (v18 >> 24) & 0xff

# a3[v14-8] = v17_byte1
# a3[v14-7] = v17_byte2
# a3[v14-4] = v18_byte1

a3[v14-9] = v17_bytelow
a3[v14-8] = v17_byte1
a3[v14-7] = v17_byte2
a3[v14-6] = v17_bytehigh

a3[v14-5] = v18_bytelow
a3[v14-4] = v18_byte1
a3[v14-3] = v18_byte2
a3[v14-2] = v18_bytehigh

# a3[v14-3] = v18_byte2
# a3[v14-6] = v17_bytehigh
# a3[v14-2] = v18_bytehigh

if v14 == v15:
break
print([hex(i) for i in a3])
tt = ""
for i in range(10):
tt += chr(a3[i])
if "ratula" in tt:
print(i1, i2, i3, i4, i5, i6)

 上述脚本一直跑不出来,看到其他人的wp所示:https://blog.csdn.net/qq_41071646/article/details/90232128

 思路很正确,感觉是我脚本写的有问题,明天再看看。改完之后的,脚本有点小问题:

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
from tqdm import tqdm
import time
a3_init = ['FE', 'A0', 'AD', '80', '20', '59', 'AB', '12', 'D7', 'C3', '9C', '88', 'FA', '2C', '1D', 'FC', '81', '46', '0D', 'DC', 'E9', 'CE', 'CC', '57', '78', 'F5', '41', '5F', '52', '2', '36', 'D5', '33', '18', '66', '3A', '40', '26', 'E8', '6E', 'B6', 'CD', '72', 'B7', '3C', '1', '66', 'B1', '4F', '99', '23', '63', '95', '77', '34', '61', '69', 'F6', 'A9', '53', '40', '37', '41', '43', '4F', '98', '95', '2C', '7A', '27', '3C', '98', '68', '1A', '88', 'A8', 'B7', '85', 'BB', '15', '4F', '1A', '1', '4D', 'C9', 'C8', '9B', '75', '78', '57', '7F', '98', '0D', 'D8', '51', 'A8', '22', 'B9', '5E', '59', '4D', '71', '4F', '1A', '81', 'A9', 'BF', '7', '29', 'ED', 'FD', '83']

a4_init = ['21', '0', '86', 'B7', 'FF', '86', '5D', '6', '2D', '30']

all_in = [0x20, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39]
for i1 in tqdm(all_in):
for i2 in all_in:
for i3 in all_in:
for i4 in all_in:
for i5 in all_in:
for i6 in all_in:
a3 = [int(i, 16) for i in a3_init]
a4 = [int(i, 16) for i in a4_init] + [i1, i2, i3, i4, i5, i6]

v8 = a4
v10 = v8[0] | (v8[3] << 24) | (v8[1] << 8) | (v8[2] << 16)
v11 = (v8[5] << 8) | v8[4] | (v8[7] << 24) | (v8[6] << 16)
v12 = v8[8] | (v8[11] << 24) | (v8[9] << 8) | (v8[10] << 16)
v13 = v8[12] | (v8[15] << 24) | (v8[13] << 8) | (v8[14] << 16)
v14 = 1
v15 = 8 * ((len(a3)-1) >> 3) + 9
while True:
v16 = 0xD5B7DDE0
v17 = (a3[v14] << 8) | a3[v14-1] | (a3[v14+2] << 24) | (a3[v14+1] << 16)
v18 = (a3[v14+4] << 8) | a3[v14+3] | (a3[v14+6] << 24) | (a3[v14+5] << 16)
while True:
v18 = (v18 - ((v17 + v16) ^ (16 * v17 + v12) ^ (v13 + (v17 >> 5))) & 0xffffffff) & 0xffffffff
v17 = (v17 - ((v18 + v16) ^ (16 * v18 + v10) ^ (v11 + (v18 >> 5))) & 0xffffffff) & 0xffffffff
v16 = (v16 + 0x21524111) & 0xffffffff
if v16 == 0:
break
v14 += 8
v17_bytelow = (v17) & 0xff
v17_byte1 = (v17 >> 8) & 0xff
v17_byte2 = (v17 >> 16) & 0xff
v17_bytehigh = (v17 >> 24) & 0xff

v18_bytelow = (v18) & 0xff
v18_byte1 = (v18 >> 8) & 0xff
v18_byte2 = (v18 >> 16) & 0xff
v18_bytehigh = (v18 >> 24) & 0xff

a3[v14-9] = v17_bytelow
a3[v14-8] = v17_byte1
a3[v14-7] = v17_byte2
a3[v14-6] = v17_bytehigh

a3[v14-5] = v18_bytelow
a3[v14-4] = v18_byte1
a3[v14-3] = v18_byte2
a3[v14-2] = v18_bytehigh

if v14 == v15:
break

tt = ""
for i in range(10):
tt += chr(a3[i])
if "ratula" in tt:
print(hex(i1), hex(i2), hex(i3), hex(i4), hex(i5), hex(i6))
for i in a3:
print(chr(i), end = "")
exit(0)
# 0x31 0x20 0x33 0x38 0x37 0x35
# Congratulations! The rootkit is sucessfully installed. The Flag is 32C3_this_is_build_for_flag_ship_phones

leaked-license-64(未做出)

 解压后,有两个文件:admin@nsa.gov.us_licenseLeaked_Lisence.dlladmin@nsa.gov.us_license的值为7e43ecf0b4e27dacfb5e613437b17acb46e8deab2c70510dc71844b492a691ecLeaked_Lisence.dll是一个32位dll。

 思路:将7e43ecf0b4e27dacfb5e613437b17acb46e8deab2c70510dc71844b492a691ec输入到某个函数(dll中存储)中,出flag。其中有一个verify函数,是Leaked_Lisence.dll唯一的导出函数。

 感觉这个题,我的思路就是一点一点分析,或者就是直接调用dll函数。

image-20230528220804201

1
2
3
4
5
arg_0   char* 长度为16
arg_10 &int
arg_14 &int
arg_18 char* 长度为16
arg_2C &int

 写了个程序调试此dll,如下:

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
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

typedef bool (*MYFUNC)(char*, int*, int*, char*, int*);

int _tmain(int argc, _TCHAR* argv[])
{
HINSTANCE hdll = LoadLibrary(TEXT("d:\\code\\c\\ctf\\genlisence-dll.dll"));
if (hdll != NULL)
{
MYFUNC myfunc = (MYFUNC)GetProcAddress(hdll, "verify");
if (myfunc != NULL)
{
char* arg1 = "string11111111111111111111111";
int arg2;
int arg3;
char* arg4 = "string2";
int arg5;
bool result = myfunc(arg1, &arg2, &arg3, arg4, &arg5);
printf("result: %d\n", result);
}
else
{
printf("failed to get function address.\n");
}
FreeLibrary(hdll);
}
else
{
printf("failed to load dll.\n");
}
return 0;
}

 上述脚本一直在控制台上输出奇怪的字符。

 此问题就是动态加载dll,每次调试都要对dll中的verify函数重新组织成函数,所以想静态调试。

 使用pexports.exe将dll变为lib(但是函数的可执行代码并没有放到lib中),之后修改上述脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

#pragma comment(lib, "C:\\Users\\23957\\Desktop\\maze\\leaked-license-64-dir\\Leaked_Lisence.lib")
extern "C" bool verify(char*, int*, int*, char*, int*);
int _tmain(int argc, _TCHAR* argv[])
{
char* arg1 = "string";
int arg2;
int arg3;
char* arg4 = "string2";
int arg5;
bool result = verify(arg1, &arg2, &arg3, arg4, &arg5);
return 0;
}

 此脚本一直报堆栈错误,说调用的genlisence-dll.dll有bug(其实是没有bug的,只是静态链接的原因而已)。最后脚本如下:

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
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <windows.h>

//typedef bool (*MYFUNC)(char*, int*, int*, char*, int*);
//int _tmain(int argc, _TCHAR* argv[])
//{
// HINSTANCE hdll = LoadLibrary(TEXT("d:\\code\\c\\ctf\\genlisence-dll.dll"));
// if (hdll != NULL)
// {
// MYFUNC myfunc = (MYFUNC)GetProcAddress(hdll, "verify");
// if (myfunc != NULL)
// {
// char arg1[16] = {'a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'f'};
// int arg2 = 1;
// int arg3 = 2;
// char arg4[16] = {'c', 'd'};
// int arg5 = 3;
// bool result = myfunc(arg1, &arg2, &arg3, arg4, &arg5);
// }
// else
// {
// printf("failed to get function address.\n");
// }
// FreeLibrary(hdll);
// }
// else
// {
// printf("failed to load dll.\n");
// }
//
// return 0;
//}



//#pragma comment(lib, "c:\\users\\23957\\desktop\\maze\\leaked-license-64-dir\\leaked_lisence.lib")

extern "C" bool verify(char*, int*, int*, char*, int*);
int _tmain(int argc, char* argv[])
{

char arg1[16];
strncpy(arg1, argv[1], 15);
arg1[15] = '\0';

int arg2;
int arg3;

char arg4[16];
strncpy(arg4, argv[2], 15);
arg4[15] = '\0';

int arg5;
bool result = verify(arg1, &arg2, &arg3, arg4, &arg5);
return 0;
}

 最后始终没有静态链接,就这么调试吧,唉。

 折磨,直接看wp吧。好多代码,看着心烦。

0x01 wp

 全是转的外网的博客,外网的博客现在也没了。中文的有:https://www.cnblogs.com/DirWang/p/11469346.html

 思路就是构造一个消息结构,之后dll调用。感觉之后还得仔细调试。不能太浮躁!!

 思绪良久,打算总结一下这道题的答案。dll中verify函数的具体参数为两个magic数据结构,magic数据结构如下所示:

1
2
3
4
5
6
7
8
struct  magic { 
union {
char *s1;
char s2[16];
};
int size;
int real_length;
};

 算法本身的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def verify(ID, licence):
with open("secret.key") as secret_file:
secret = secret_file.read()
checkstring = sha256(secret).decode("hex")
if len(id) < 32:
id = id + id
secretFromUser = ""
license = license.decode("hex")
from x in range(32):
secretFromUser += chr(ord(id[i])) ^ ord(license[i])
check2 = sha256(secretFromUser).decode("hex")
if check2 == checkstring:
return 1
return 0

 下一道下一道!!

pingpong

 apk文件。程序逻辑如下:

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
def main:
p = 0
num = 0
ttt = 1000000
tt = ttt
def ping:
if tt % 2 == 1:
p = 0
num = 0
tt = ttt
tt--
p = lib_ping(p, num)
num++
if num >= 7:
num = 0
if tt == 0:
print("success",p)
def pong:
if tt % 2 == 0:
p = 0
num = 0
tt = ttt
tt--
p = lib_pong(p, num)
num++
if num >= 7:
num = 0
if tt == 0:
print("success",p)

 我们想要tt=0,这样的话就要先ping,再pong,循环往复1000000/2次,才可以。

 两个函数pingpong保存在pp库中:

image-20230530110719172

pp库使用了OLLVM保护。如下所示:

image-20230530111102933

 原来的那个脚本失效了。所以得手撕OLLVM。

0x00 手撕OLLVM的尝试

1
2
3
4
5
6
pong 分析
R0 <- R0+R4
R0 <- R0 % 0x0A + R4
([var_30] * [var_30+4]) % 0x0A + R4
([var_30] * [var_30+4]) % 0x0A + p
ret <- ([var_30] * [var_30+4]) % 0x0A + p
1
2
3
4
5
ping 分析
ret <- ([var_2C] * [var_2C+4]) % 0x0A + var_1C
var_2C = (1<<1E)*n1
var_2C+4 = (1<<1E)
var_1C = p+n2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ping 
v2 | v3(p) | v4(num) | ([var_2C] * [var_28]) % 0x0A
E <- 9 2 5
4 <- 0 0 4
1C <- 16 4 6
11 <- C 2 5
1E <- 19 4 5
2A <- 27 6 3
32 <- 30 3 2
27 <- 25 6 2
2A <- 28 1 2
2F <- 2C 3 3
3A <- 31 5 9
40 <- 3C 0 4
4D <- 48 2 5
R0 R1 R2 R3 R4 R5 R6 R7 var_1C var_2C+4 var_2C var_20 var_24
1<<1e 0x12ABCDA2 0 num 3 num 1 p 1<<1e 0 0 1<<1e
0xC40FA35D
1 1/0 0xD175216E 0x9EE75616 0x3CD203AF
2 0xE5925FB 0xF133E736/0xE5925FB var_18
0x65982633
0x72FA0C29 1 num
0xE5D890A5
3 0x18C23607 0x8B06AA34/0x18C23607
0xA 0xE5D890A5 0xA*1 num-1
0xA 0xE5D890A5 0xA**num 0
4 0x18C23607 0x8B06AA34/0x18C23607
5(var_1C%2=1) 1 0xF4B872E4/0xF10822ED 0xFFFFFFF0^var_1C var_1C num 1
6(var_1C%3!=0) 0/1 0xCDB35A57/0xC5866837 0xC5866837
end [var_2C*(var_2C+4)]%0xA+var_1C 0xA 0xA**num 0xC5866837
6(var_1C%3=0) 0/1 0xCDB35A57/0xC5866837 0xC5866837
1+var_1C 0xC5866837 p+1
end [var_2C*(var_2C+4)]%0xA+var_1C 0xA 0xA**num 0xC5866837
5(var_1C%2=0) 1 0xF4B872E4/0xF10822ED 0xFFFFFFF0^var_1C var_1C num
var_1C+1 0xF10822ED p+1
2 0xE5925FB 0xF133E736/0xE5925FB var_18
-1 0x12ABCDA2 var_24 0 var_20*var_24 5 2 var_2C+4 var_2C+(var_2C+4) var_24 0
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
label_B:
if var_20 > var_24:
var_18 = 1
R1 = 0xD175216E
else:
var_18 = 0
R1 = 0xD175216E

if var_18 == 1:
R1 = 0xF133E736
var_23 = var_24
var_24 = 0
var_2C = var_2C+(var_2C+4)
go label_B
else:
R1 = 0xE5925FB
R5 = 0xA ** num
if var_1C % 2 == 1:
R1 = 0xF10822ED
if var_1C % 3 == 0:
R1 = 0xCDB35A57
var_1C += 1
go label_A
else:
label_A:
R1 = 0xC5866837
return [var_2C*(var_2C+4)]%0xA+var_1C
else:
R1 = 0xF4B872E4
var_1C += 1
if var_1C % 3 == 0:
R1 = 0xCDB35A57
var_1C += 1
go label_A
else:
label_A:
R1 = 0xC5866837
return [var_2C*(var_2C+4)]%0xA+var_1C
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
var_1C = p
var_2C+4 = 1<<0x1e

label_B:
if var_20 > var_24:
var_18 = 1
else:
var_18 = 0

if var_18 == 1:
var_23 = var_24
var_24 = 0
var_2C = var_2C+(var_2C+4)
go label_B
else:
R5 = 0xA ** num
if var_1C % 2 == 1:
if var_1C % 3 == 0:
var_1C += 1
go label_A
else:
label_A:
return [var_2C*(var_2C+4)]%0xA+var_1C
else:
var_1C += 1
if var_1C % 3 == 0:
var_1C += 1
go label_A
else:
label_A:
return [var_2C*(var_2C+4)]%0xA+var_1C

 撕不出来啊,看wp。

0x01 WP

 其实不难看出,只要用模拟执行,模拟出ping/pong循环往复的过程,就可以。

 wp中使用了frida进行模拟执行。

 远古时候曾经用过frida:https://wd-2711.tech/2022/11/15/android-app-100-wp/

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
import frida, sys

def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
// 将 JavaScript 的执行上下文切换到 Java 线程中,从而可以直接访问和操作 Java 对象和方法。
Java.perform(function(){

//遍历模块找基址
Process.enumerateModules({
onMatch: function (exp) {
send(exp.name + "|" + exp.base + "|" + exp.size + "|" + exp.path);
if (exp.name == 'libpp.so') {
send('enumerateModules find');
send(exp.name + "|" + exp.base + "|" + exp.size + "|" + exp.path);
send(exp);
return 'stop';
}
},
onComplete: function () {
send('enumerateModules stop');
}
});

//通过模块名直接查找libpp基址
var soAddr = Module.findBaseAddress("libpp.so");
send("soAddr:" + soAddr);

// 从libpp中导出ping与pong两个函数
// 总是比ida看的地址+1,对于so文件,符号执行验证了这一点
var aping = 0x1308 + 1
var fping = Module.findExportByName("libpp.so", "Java_com_geekerchina_pingpongmachine_MainActivity_ping")
fping = new NativePointer(soAddr).add(aping);
// var ping = new NativeFunction(fping, "int", ['pointer','pointer','int', 'int']);
var ping = new NativeFunction(fping, "int", ['pointer','pointer','int', 'int']);

var apong = 0x1564 + 1
var fpong = Module.findExportByName("libpp.so", "Java_com_geekerchina_pingpongmachine_MainActivity_pong")
fpong = new NativePointer(soAddr).add(apong);
var pong = new NativeFunction(fpong, "int", ['pointer','pointer','int', 'int']);

// 模拟运行
var env = Java.vm.getEnv(); // 获得当前线程的JNI环境,JNI(Java Native Interface)是 Java 提供的一种机制,用于在 Java 和本地代码之间相互调用。
var obj = ptr(0); // 定义一个指针
var tt = 1000000;
var p = 0;
var num = 0;
while(true) {
if (tt % 2 == 1){
// pong函数
--tt;
p = pong(env, obj, p, num);
++num;
if(num >= 7) {
num = 0;
}
} else {
// ping函数
--tt;
p = ping(env, obj, p, num);
++num;
if(num >= 7){
num = 0;
}
}
if (tt == 0) {
send("tt:" + tt + "|num:" + num)
send("FLAG : "+"BCTF{MagicNum" + p+ "}");
break;
}
}


});

"""
process = frida.get_usb_device().attach(9009)
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

 上述脚本一直找不到libpp.so,搜索发现是模拟器一般是x86 或者x86-64架构,只会加载对应lib/x86 和lib/x86-64下面的so文件,真机才会加载lib/arm*的so文件,所以想让app加载arm 可以真机,或者arm架构的模拟器。也就是说,frida只会搜索x86下的文件夹,而armabi下的文件夹无法搜索。所以,我首先把armabi下的libpp.so复制到x86文件夹下,发现不行。

 然后,我并没有找到arm架构的模拟器。所以就用了自己的本机,但是本机没有root,所以运行不了frida-server,root风险太高,所以就打算用frida-gadget(非root)的方法,但是最后还是不能正常调试,显示Failed to attach: unable to connect to remote frida-server: closed

 就这样吧,下一道,wp为BCTF{MagicNum4500009}

childRE

 没有pdb文件的PE64。

 动调了一下,发现可能有反调试。验证了一下没有hh,只是调试器加参数的时候加错了。

image-20230531214310135

 程序逻辑如下:

1
2
3
4
1. 输入长度为31的字符串
2. 使用输入构成满二叉树(询问newbing得到)
3. 使用深度优先遍历遍历二叉树,得到v5
4. UnDecorateSymbolName将v5变成outputstring

 容易得到outputstring=private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *),使用newbing,可以得到其原始值为?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z,之后只要构成满二叉树,按层遍历即可。满二叉树如下所示:

image-20230531214746719

 相应脚本如下:

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2023/05/31 18:43:43
# @Author: wd-2711
'''

import hashlib

target1 = '(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&'
target2 = '55565653255552225565565555243466334653663544426565555525555222'
change_arr = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0x61, 0x73, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x41, 0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x7A, 0x78, 0x63, 0x76, 0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F, 0x00]
ouputString = ""
for i in range(62):
right = target1[i]
reminder = change_arr.index(ord(right))
right = target2[i]
quotient = change_arr.index(ord(right))
n = quotient * 23 + reminder
ouputString += chr(n)
print(ouputString)

"""
private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
| NewBing
?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z
| Plot
Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP
"""
plaintext = "Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP"
md5 = hashlib.md5()
md5.update(plaintext.encode())
print("flag{" + md5.hexdigest() + "}")
# flag{63b148e750fed3a33419168ac58083f5}

MyDriver2-397

 发现是一个sys驱动文件。通常用于存储设备驱动程序。sys文件不能直接运行,需要通过注册表配置和加载驱动程序的命令来安装和启动。

 尝试动调一下此程序。

0x00 环境部署

 首先部署windbg双机调试环境,见tool-use。

 其次,安装驱动文件sys,见tool-use,具体而言:关闭win7的强制签名bcdedit /set testsigning on,之后使用OsrLoader安装驱动。

 之后,打开win7虚拟机进入调试模式,之后立即打开windbg,命令行为:

1
windbg.exe -b -k com:pipe,port=\\.\pipe\com_1,resets=0

 然后在sys文件上打断点,如bp MyDriver2+0xxx。运行windbg进入桌面,之后使用OsrLoader打开驱动。

0x01 苦逼的调试过程

参考链接:https://estongsy.wordpress.com/2012/07/17/debugging-a-driver-sys-file-using-windbg/

1
2
3
4
5
windbg.exe -b -k com:pipe,port=\\.\pipe\com_1,resets=0
bp MyDriver2+0x16F8 // 程序入口
info address : fffffa80`19864000
bp MyDriver2+0x17f7 // sub_113C8
bp MyDriver2+0x1194 // v5 <- 修改前的CreateFile
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
nt!NtCreateFile:
fffff800`041b107c 4c8bdc mov r11,rsp
fffff800`041b107f 4881ec88000000 sub rsp,88h
fffff800`041b1086 33c0 xor eax,eax
fffff800`041b1088 498943f0 mov qword ptr [r11-10h],rax
fffff800`041b108c c744247020000000 mov dword ptr [rsp+70h],20h
fffff800`041b1094 89442468 mov dword ptr [rsp+68h],eax
fffff800`041b1098 498943d8 mov qword ptr [r11-28h],rax
fffff800`041b109c 89442458 mov dword ptr [rsp+58h],eax
fffff800`041b10a0 8b8424e0000000 mov eax,dword ptr [rsp+0E0h]
fffff800`041b10a7 89442450 mov dword ptr [rsp+50h],eax
fffff800`041b10ab 488b8424d8000000 mov rax,qword ptr [rsp+0D8h]
fffff800`041b10b3 498943c0 mov qword ptr [r11-40h],rax
fffff800`041b10b7 8b8424d0000000 mov eax,dword ptr [rsp+0D0h]
fffff800`041b10be 89442440 mov dword ptr [rsp+40h],eax
fffff800`041b10c2 8b8424c8000000 mov eax,dword ptr [rsp+0C8h]
fffff800`041b10c9 89442438 mov dword ptr [rsp+38h],eax
fffff800`041b10cd 8b8424c0000000 mov eax,dword ptr [rsp+0C0h]
fffff800`041b10d4 89442430 mov dword ptr [rsp+30h],eax
fffff800`041b10d8 8b8424b8000000 mov eax,dword ptr [rsp+0B8h]
fffff800`041b10df 89442428 mov dword ptr [rsp+28h],eax
fffff800`041b10e3 488b8424b0000000 mov rax,qword ptr [rsp+0B0h]
fffff800`041b10eb 49894398 mov qword ptr [r11-68h],rax
fffff800`041b10ef e8bcb71600 call nt!IopCreateFile (fffff800`0431c8b0)
fffff800`041b10f4 4881c488000000 add rsp,88h
fffff800`041b10fb c3 ret
1
2
3
4
5
v5 = 0xfffffa80`1a282150
fffff800`041b107c 4c8bdc mov r11,rsp
fffff800`041b107f 4881ec88000000 sub rsp,88h
fffff800`041b1086 33c0 xor eax,eax
fffff800`041b1088 498943f0 mov qword ptr [r11-10h],rax
1
bp MyDriver2+0x11F6   // v9 <- v5 <- 修改前的CreateFile
1
2
3
4
5
v9 = fffffa80`1ac088b0
fffff800`041b107c 4c8bdc mov r11,rsp
fffff800`041b107f 4881ec88000000 sub rsp,88h
fffff800`041b1086 33c0 xor eax,eax
fffff800`041b1088 498943f0 mov qword ptr [r11-10h],rax
1
bp MyDriver2+0x12AC   // return
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
nt!NtCreateFile:
fffff800`041b107c ff2500000000 jmp qword ptr [nt!NtCreateFile+0x6 (fffff800`041b1082)]
fffff800`041b1082 d0a4a30580f8ff shl byte ptr [rbx-77FFBh],1
fffff800`041b1089 ff9090c74424 call qword ptr [rax+2444C790h]
fffff800`041b108f 7020 jo nt!NtCreateFile+0x35 (fffff800`041b10b1)

fffff800`041b1091 0000 add byte ptr [rax],al
fffff800`041b1093 008944246849 add byte ptr [rcx+49682444h],cl
fffff800`041b1099 8943d8 mov dword ptr [rbx-28h],eax
fffff800`041b109c 89442458 mov dword ptr [rsp+58h],eax
fffff800`041b10a0 8b8424e0000000 mov eax,dword ptr [rsp+0E0h]
fffff800`041b10a7 89442450 mov dword ptr [rsp+50h],eax
fffff800`041b10ab 488b8424d8000000 mov rax,qword ptr [rsp+0D8h]

fffff800`041b10b1 0000 add byte ptr [rax],al

fffff800`041b10b3 498943c0 mov qword ptr [r11-40h],rax
fffff800`041b10b7 8b8424d0000000 mov eax,dword ptr [rsp+0D0h]
fffff800`041b10be 89442440 mov dword ptr [rsp+40h],eax
fffff800`041b10c2 8b8424c8000000 mov eax,dword ptr [rsp+0C8h]
fffff800`041b10c9 89442438 mov dword ptr [rsp+38h],eax
fffff800`041b10cd 8b8424c0000000 mov eax,dword ptr [rsp+0C0h]
fffff800`041b10d4 89442430 mov dword ptr [rsp+30h],eax
fffff800`041b10d8 8b8424b8000000 mov eax,dword ptr [rsp+0B8h]
fffff800`041b10df 89442428 mov dword ptr [rsp+28h],eax
fffff800`041b10e3 488b8424b0000000 mov rax,qword ptr [rsp+0B0h]
fffff800`041b10eb 49894398 mov qword ptr [r11-68h],rax
fffff800`041b10ef e8bcb71600 call nt!IopCreateFile (fffff800`0431c8b0)
fffff800`041b10f4 4881c488000000 add rsp,88h
fffff800`041b10fb c3 ret

image-20230601212329344

 这个call qword ptr [rax+2444C790h]很奇怪啊,难不成调用nt!NtCreateFile的时候rax都是固定的?

1
bp fffff800`041b107c   // 在nt!NtCreateFile上打断点

 调试后发现完全不一样啦。

 发现第一行jmp到了sub_114D0函数。如下所示:

image-20230601215414383

1
bp MyDriver2+0x1552   // 调用wcsstr的时候

 发现wcsstr(*(*(a3 + 16) + 8i64)指向\??\MountPointManagerSubStr指向P_giveMe_flag_233.txt。那我想,运行此驱动,然后立即打开P_giveMe_flag_233.txt,不就得了。但是先别停,这样不稳,继续调下去才稳。

1
2
3
4
r rax=1
bp MyDriver2+0x156A // 判断dword_16428 != -1
bp MyDriver2+0x1577 // 判断(v9 + 1) >= 8 (也要修改一下)
bp MyDriver2+0x15A0 // sub_115DC()

 查看此时r12对应的值,如下所示:

image-20230601223939213

 写入文件的值为:the flag is A_simple_Inline_hook_Drv。Flag为RCTF{A_simple_Inline_hook_Drv}

0x02 题目总结

 题目逻辑如下:钩取CreateFile函数,如果调用此函数的文件是P_giveMe_flag_233.txt,那么就把flag写入到文件中,否则直接使系统崩溃。

Junk_Instruction

 西湖论剑预选赛的题目,既然是预选赛,看来应该很简单。看来re还是任重道远。但是庆幸的是,今天终于可以re啦!

 是一个32位的GUI程序,输入字符串并且判断是否正确,不正确输出error。没有符号表。有很多的垃圾指令。

0x00 探索

 猜测是MFC程序。查看之前做过的题,链接为:https://wd-2711.tech/2022/11/23/re-part-4/。其中的MFC逆向-200,还有就是:https://wd-2711.tech/2022/12/04/mfc-study/

 使用xspy,程序输出:

1
2
3
4
5
6
message map=0x004BF724(Junk_Instruction.exe+0x17f724)
msg map entries at 0x004BF730(Junk_Instruction.exe+0x17f730)
OnMsg:WM_SYSCOMMAND(0112),func=0x003421F0(Junk_Instruction.exe+0x0021f0)
OnMsg:WM_PAINT(000f),func=0x00342300(Junk_Instruction.exe+0x002300)
OnMsg:WM_QUERYDRAGICON(0037),func=0x00342410(Junk_Instruction.exe+0x002410)
OnCommand: notifycode=0000 id=03e9,func=0x00342420(Junk_Instruction.exe+0x002420)

 补充:xspy 输出的 OnMsg 与 OnCommand 有一些区别。OnMsg 表示程序收到了一个普通的消息,例如 WM_PAINT、WM_LBUTTONDOWN 等,这些消息通常是由系统或用户的操作产生的。OnCommand 表示程序收到了一个命令消息,例如 ID_FILE_OPEN、ID_EDIT_COPY 等,这些消息通常是由菜单或工具栏上的按钮产生的。

 所以,猜测OnCommand是面板中的check按钮。定位到0x400000+0x002420=0x402420。发现是一个SEH(异常处理函数)。

image-20230615122346601

 相当于:

image-20230615122955662

 而:

image-20230615123053074

 相当于:

image-20230615123742945

 还有把call $+5改为push 下一条指令地址

 最后,可以写脚本:

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2023/06/15 16:49:09
# @Author: wd-2711
'''

arr = [0x71, 0x31, 0x28, 0x97, 0x7D, 0x13, 0x48, 0xD4, 0x50, 0x1B, 0x73, 0x79, 0xC2, 0x1A, 0xB5, 0xE7, 0x29, 0x64, 0xEB, 0x85, 0x1C, 0x00, 0x77, 0x16, 0xFD, 0x23, 0x43, 0xC9, 0xD0, 0x7C, 0xFF, 0xC3, 0xA5, 0xED, 0xA3, 0xAA, 0xD9, 0x66, 0xFB, 0x92, 0x38, 0x74, 0x04, 0x9E, 0xC1, 0x3A, 0x08, 0x68, 0x37, 0xF6, 0x4B, 0x09, 0x45, 0x35, 0x6C, 0x1E, 0x14, 0xAE, 0x3C, 0xA9, 0xFE, 0x4A, 0x03, 0x62, 0x87, 0x2E, 0x2C, 0x55, 0xE6, 0xAB, 0x7F, 0x12, 0x0B, 0x21, 0x2B, 0x94, 0xC7, 0x96, 0x65, 0x11, 0x95, 0xC0, 0x6B, 0x0D, 0xF1, 0xBF, 0x8A, 0x2D, 0xB8, 0x81, 0xE3, 0xFC, 0xDF, 0xD2, 0x7E, 0xA6, 0x0E, 0x6A, 0x36, 0x70, 0x01, 0x88, 0x6F, 0xBD, 0x99, 0x3E, 0x2F, 0x5D, 0xAF, 0xDA, 0x75, 0x34, 0xCB, 0xD5, 0x47, 0x22, 0x90, 0x58, 0x9A, 0xA4, 0x3F, 0xB0, 0x15, 0x7A, 0xF2, 0x5F, 0xEE, 0xF4, 0xF0, 0xDC, 0x61, 0x76, 0x06, 0xC8, 0xD6, 0xDE, 0xDB, 0x69, 0x8C, 0x0F, 0x8B, 0x44, 0x40, 0x9C, 0x41, 0xF7, 0x30, 0x67, 0x27, 0xF8, 0xCA, 0x24, 0x39, 0xA2, 0x05, 0x63, 0x51, 0xC4, 0xD3, 0xE2, 0xF3, 0x93, 0x0C, 0x07, 0x2A, 0x59, 0xA7, 0x0A, 0x82, 0x1D, 0xBC, 0x9B, 0x3D, 0xF9, 0x10, 0xB9, 0xCC, 0xB1, 0x5C, 0x84, 0xB2, 0x32, 0xEF, 0xB7, 0xB4, 0x25, 0xBE, 0x52, 0x86, 0xCF, 0x57, 0x17, 0x1F, 0x6E, 0xEA, 0x4D, 0x4E, 0x18, 0xCE, 0x5A, 0x4C, 0x91, 0xE1, 0xEC, 0x42, 0xBA, 0x33, 0xDD, 0x5B, 0xD1, 0xFA, 0x8D, 0xAD, 0xB3, 0xD7, 0x78, 0x9F, 0xD8, 0xA0, 0xE5, 0x4F, 0xE0, 0xE4, 0xE8, 0xB6, 0x3B, 0xBB, 0x53, 0x6D, 0xC6, 0xC5, 0x19, 0x54, 0xE9, 0xCD, 0xAC, 0x8E, 0x83, 0x80, 0xF5, 0xA1, 0x89, 0x60, 0x8F, 0x72, 0x98, 0xA8, 0x5E, 0x02, 0x49, 0x26, 0x20, 0x46, 0x56, 0x9D, 0x7B, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x00]
tar = [0x00, 0x88, 0xEA, 0x4F, 0x00, 0x38, 0x19, 0x6A, 0x00, 0x68, 0x07, 0x6A, 0x00, 0xD8, 0xF6, 0x4F, 0x00, 0x5B, 0xD6, 0xD0, 0x26, 0xC8, 0xDD, 0x19, 0x7E, 0x6E, 0x3E, 0xCB, 0x16, 0x91, 0x7D, 0xFF, 0xAF, 0xDD, 0x76, 0x64, 0xB0, 0xF7, 0xE5, 0x89, 0x57, 0x82, 0x9F, 0x0C, 0x00, 0x9E, 0xD0, 0x45, 0xFA]
tar = tar[::-1][:32]
tar = tar[::-1]
v6 = 32
v4 = 123
for i in range(32):
i = 31 - i
tar[i] ^= arr[(arr[v4] + arr[v6]) % 256]
v7 = arr[v4]
arr[v4] = arr[v6]
arr[v6] = v7
v4 = (v4 - arr[v6]) % 256
v6 = (v6 - 1) % 256

res = ""
for i in tar[:32]:
res += chr(i)

res = res[16:][::-1] + res[:16][::-1]
print("flag{" + res + "}")

 结果:

image-20230615165135998

 patch后的程序如下:https://github.com/WD-2711/re_files/tree/main/%E6%94%BB%E9%98%B2%E4%B8%96%E7%95%8C/Junk_Instruction

icekey

 64位程序。.NET是由微软开发的一个软件框架,用于开发和运行各种类型的应用程序,包括桌面应用程序、Web应用程序、移动应用程序、服务端应用程序等。.NET框架提供了一个通用的基础设施,让开发人员可以使用多种编程语言(如C#VB.NETF#等)来编写应用程序,并利用.NET框架提供的库和工具来进行开发、测试、调试和部署。

 ILSpy是.net反编译工具。dnSpy也是一个.net反编译工具。找到一个main函数:

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
internal unsafe static int main()
{
string b = "3ACF8D62AAA0B630C4AF43AF327CE129D46F0FEB98D9040F713BE65502A5107A";
<Module>.std.operator<<<struct\u0020std::char_traits<char>\u0020>(<Module>.__imp_std.cout, (sbyte*)(&<Module>.??_C@_0CP@LOCJIKOA@flag?$CIformat?3xxxxxxxxxxxxxxxxxxx@));
IceKey iceKey;
<Module>.IceKey.{ctor}(ref iceKey, 0); // 初始化icekey
try{
byte[] bytes = Encoding.ASCII.GetBytes(Console.ReadLine());
int num = bytes.Length;
ref byte byte& = ref bytes[0];
byte[] array = new byte[num];
ref byte byte&2 = ref array[0];
MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
byte key;
try{
MD5CryptoServiceProvider md5CryptoServiceProvider2 = md5CryptoServiceProvider;
key = ref Encoding.ASCII.GetBytes(BitConverter.ToString(md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes("iriszero"))).Replace("-", "").ToLower())[0]; // key=md5('iriszero')
}
catch{
...
}
((IDisposable)md5CryptoServiceProvider).Dispose();
<Module>.IceKey.Set(ref iceKey, ref key); // 设置icekey的key,函数具体细节先忽略
if (0 < num){
long num2 = 0L;
uint num3 = (num - 1 >> 3) + 1;
do{
<Module>.IceKey.Encrypt(ref iceKey, ref byte& + num2, ref byte&2 + num2); // 对于输入,每8位使用Icekey加密
num2 += 8L;
num3 += uint.MaxValue;
} while (num3 > 0U);
}
sbyte* val = ref (BitConverter.ToString(array).Replace("-", "").ToString() == b) ? ref <Module>.??_C@_04LOAJBDKD@true@ : ref <Module>.??_C@_05LAPONLG@false@; // 判断加密是否等于b
<Module>.std.basic_ostream<char,std::char_traits<char>\u0020>.<<(<Module>.std.operator<<<struct\u0020std::char_traits<char>\u0020>(<Module>.__imp_std.cout, val), <Module>.__unep@??$endl@DU?$char_traits@D@std@@@std@@$$FYAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@@Z);
if (0 < num){
long num4 = 0L;
uint num5 = (num - 1 >> 3) + 1;
do{
<Module>.IceKey.Decrypt(ref iceKey, ref byte&2 + num4, ref byte& + num4);
num4 += 8L;
num5 += uint.MaxValue;
} while (num5 > 0U);
}
}
catch{
...
}
<Module>.IceKey.{dtor}(ref iceKey);
return 0;
}

 可以看到,程序中还提供了解密字段。那么很简单,我们只需要动态调试,并在if(0<num)时将array字段改为3ACF8D62AAA0B630C4AF43AF327CE129D46F0FEB98D9040F713BE65502A5107A即可,最终得到:5acb06231724c8c369bae711166dbe85。flag为flag{5acb06231724c8c369bae711166dbe85}

xx

 64位程序。程序逻辑挺清晰的。输入字符串,进行某些操作,与另一些字符串作比较。输入长度为19。

 其中使用了tea加密(find_crypt与newbing都这么说。),但是仔细瞅了瞅,应该使用了XXTEA(主要是看移位)。

0x00 某些逻辑

image-20230616151545998

 其主要操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
v20[3]=v20[0]^v20[3]
v20[4]=v20[0]^v20[4]
v20[5]=v20[0]^v20[5]
v20[6]=v20[0]^v20[1]^v20[6]
v20[7]=v20[0]^v20[1]^v20[7]
v20[8]=v20[0]^v20[1]^v20[8]
v20[9]=v20[0]^v20[1]^v20[2]^v20[9]
v20[10]=v20[0]^v20[1]^v20[2]^v20[10]
v20[11]=v20[0]^v20[1]^v20[2]^v20[11]
v20[12]=v20[0]^v20[1]^v20[2]^v20[3]^v20[12]
v20[13]=v20[0]^v20[1]^v20[2]^v20[3]^v20[13]
v20[14]=v20[0]^v20[1]^v20[2]^v20[3]^v20[14]
v20[15]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[15]
v20[16]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[16]
v20[17]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[17]
v20[18]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[18]
v20[19]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[19]
v20[20]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[20]
v20[21]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[21]
v20[22]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[22]
v20[23]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[23]

 其逆如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
v20[23]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[23]
v20[22]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[22]
v20[21]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[21]
v20[20]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[20]
v20[19]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[19]
v20[18]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[18]
v20[17]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[17]
v20[16]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[16]
v20[15]=v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[15]
v20[14]=v20[0]^v20[1]^v20[2]^v20[3]^v20[14]
v20[13]=v20[0]^v20[1]^v20[2]^v20[3]^v20[13]
v20[12]=v20[0]^v20[1]^v20[2]^v20[3]^v20[12]
v20[11]=v20[0]^v20[1]^v20[2]^v20[11]
v20[10]=v20[0]^v20[1]^v20[2]^v20[10]
v20[9]=v20[0]^v20[1]^v20[2]^v20[9]
v20[8]=v20[0]^v20[1]^v20[8]
v20[7]=v20[0]^v20[1]^v20[7]
v20[6]=v20[0]^v20[1]^v20[6]
v20[5]=v20[0]^v20[5]
v20[4]=v20[0]^v20[4]
v20[3]=v20[0]^v20[3]

image-20230616152533142

 其逆如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
v19[21]=v20[23]
v19[23]=v20[22]
v19[20]=v20[21]
v19[22]=v20[20]
v19[17]=v20[19]
v19[19]=v20[18]
v19[16]=v20[17]
v19[18]=v20[16]
v19[13]=v20[15]
v19[15]=v20[14]
v19[12]=v20[13]
v19[14]=v20[12]
v19[9]=v20[11]
v19[11]=v20[10]
v19[8]=v20[9]
v19[10]=v20[8]
v19[5]=v20[7]
v19[7]=v20[6]
v19[4]=v20[5]
v19[6]=v20[4]
v19[1]=v20[3]
v19[3]=v20[2]
v19[0]=v20[1]
v19[2]=v20[0]

 对于XXTEA,其加密数据为:输入(19字节)+0x0013000000(注意顺序),密钥为:输入的前4位(4个字节)+0x00*12。delta为默认值,即0x9e3779b9

 最终脚本如下:

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2023/06/16 19:54:01
# @Author: wd-2711
'''

target = [0xCE, 0xBC, 0x40, 0x6B, 0x7C, 0x3A, 0x95, 0xC0, 0xEF, 0x9B, 0x20, 0x20, 0x91, 0xF7, 0x02, 0x35, 0x23, 0x18, 0x02, 0xC8, 0xE7, 0x56, 0x56, 0xFA]
v20 = target
v20[23] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[23]
v20[22] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[22]
v20[21] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[6]^v20[21]
v20[20] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[20]
v20[19] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[19]
v20[18] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[5]^v20[18]
v20[17] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[17]
v20[16] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[16]
v20[15] = v20[0]^v20[1]^v20[2]^v20[3]^v20[4]^v20[15]
v20[14] = v20[0]^v20[1]^v20[2]^v20[3]^v20[14]
v20[13] = v20[0]^v20[1]^v20[2]^v20[3]^v20[13]
v20[12] = v20[0]^v20[1]^v20[2]^v20[3]^v20[12]
v20[11] = v20[0]^v20[1]^v20[2]^v20[11]
v20[10] = v20[0]^v20[1]^v20[2]^v20[10]
v20[9] = v20[0]^v20[1]^v20[2]^v20[9]
v20[8] = v20[0]^v20[1]^v20[8]
v20[7] = v20[0]^v20[1]^v20[7]
v20[6] = v20[0]^v20[1]^v20[6]
v20[5] = v20[0]^v20[5]
v20[4] = v20[0]^v20[4]
v20[3] = v20[0]^v20[3]

v19 = [0 for i in range(24)]
v19[21]=v20[23]
v19[23]=v20[22]
v19[20]=v20[21]
v19[22]=v20[20]
v19[17]=v20[19]
v19[19]=v20[18]
v19[16]=v20[17]
v19[18]=v20[16]
v19[13]=v20[15]
v19[15]=v20[14]
v19[12]=v20[13]
v19[14]=v20[12]
v19[9]=v20[11]
v19[11]=v20[10]
v19[8]=v20[9]
v19[10]=v20[8]
v19[5]=v20[7]
v19[7]=v20[6]
v19[4]=v20[5]
v19[6]=v20[4]
v19[1]=v20[3]
v19[3]=v20[2]
v19[0]=v20[1]
v19[2]=v20[0]

for i in v19:
print(hex(i), end = " ")

# 0xbc 0xa5 0xce 0x40 0xf4 0xb2 0xb2 0xe7 0xa9 0x12 0x9d 0x12 0xae 0x10 0xc8 0x5b 0x3d 0xd7 0x6 0x1d 0xdc 0x70 0xf8 0xdc
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
#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;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
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) /* Decoding Part */
{
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()
{
// python脚本得出的结果
uint32_t v[6]= {0x40cea5bc,0xe7b2b2f4,0x129d12a9,0x5bc810ae,0x1d06d73d,0xdcf870dc};

// 猜测key是 "flag"
uint32_t const k[4]= {0x67616c66,0,0,0};
int n=6;
btea(v, -n, k);
for(int i=0; i<6; i++){
int now = v[i];
while(1){
int tmp1 = now & 0xff;
printf("%c", tmp1);
now = now >> 8;
if(now == 0)
break;
}
}
return 0;
}
// flag{CXX_and_++tea}

easyvm

 64位的ELF,看题目名称是个虚拟机。运行如下:

image-20230616200402015

 打开后要求输入32位的flag。

image-20230616200842305

 重点是红框部分,其中应该有关于验证的具体逻辑。其中,sub_400c1e函数如下:

image-20230616201658404

 它是对上上图第一个红框中的v3进行值的分配。off_4010a8是好多函数的偏移。经过分析,上上图第2个红框是sub_400806函数,很明显是一个虚拟机switch ... case ...函数。

 分析之后,总结其流程如下(伪代码):

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
*(_QWORD *)(a1 + 24) = arr2
*(_QWORD *)(a1 + 32) = input

labelA:
*(_BYTE *)(a1 + 16) = *(_BYTE *)(*(_QWORD *)(a1 + 32) + *(unsigned __int8 *)(a1 + 18))
*(_BYTE *)(a1 + 16) -= *(_BYTE *)(a1 + 18)
*(_BYTE *)(a1 + 17) ^= *(_BYTE *)(a1 + 16)
*(_BYTE *)(a1 + 16) = -51
*(_BYTE *)(a1 + 16) ^= *(_BYTE *)(a1 + 17)

if ( *(a1 + 16) == *(*(a1 + 24) + *(a1 + 18)) )
{
*(a1 + 20) = 0;
}
else
{
if ( *(a1 + 16) >= *(*(a1 + 24) + *(a1 + 18)) )
*(a1 + 20) = 1;
else
*(a1 + 20) = -1;
}
*(_BYTE *)(a1 + 17) = *(_BYTE *)(a1 + 16);
if (*(_DWORD *)(a1 + 20))
return 0LL;
++*(_BYTE *)(a1 + 18);
*(_DWORD *)(a1 + 20) = *(_BYTE *)(a1 + 18) > 0x1Fu;
if (*(_DWORD *)(a1 + 20) != 1)
goto labelA
print("good")

 经过究极简化,例如:

1
2
3
4
5
6
7
*(_QWORD *)(a1 + 24)          ----> arr2
*(_QWORD *)(a1 + 32) ----> input
*(unsigned __int8 *)(a1 + 18) ----> i
*(_BYTE *)(a1 + 18) ----> i
*(_BYTE *)(a1 + 16) ----> j
*(_BYTE *)(a1 + 17) ----> m
*(_DWORD *)(a1 + 20) ----> k

最终可以得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
i = 0
m = 0
while True:
j = m ^ (input[i] - i) ^ 0xCD
if j == arr2[i]:
k = 0
else:
return 0
m = arr2[i]
i += 1
k = i > 31
if k == 1:
break
print("good")

 可以写脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2023/06/16 23:06:53
# @Author: wd-2711
'''

arr2 = [0xF4, 0x0A, 0xF7, 0x64, 0x99, 0x78, 0x9E, 0x7D, 0xEA, 0x7B, 0x9E, 0x7B, 0x9F, 0x7E, 0xEB, 0x71, 0xE8, 0x00, 0xE8, 0x07, 0x98, 0x19, 0xF4, 0x25, 0xF3, 0x21, 0xA4, 0x2F, 0xF4, 0x2F, 0xA6, 0x7C]

m = 0
print("UNCTF{", end = "")
for i in range(32):
out = (arr2[i] ^ m ^ 0xCD) + i
m = arr2[i]
print(chr(out), end = "")
print("}", end = "")
# UNCTF{942a4115be2359ffd675fa6338ba23b6}

seven

 是sys驱动文件!

留言

2023-05-22

© 2024 wd-z711

⬆︎TOP