​ 感觉之前一道题写一个post的方式太繁琐了,所以今后打算某一个难度的题写到一个post里,例如re-part-3就是逆向难度为3的问题。


debug-wp

0x00

image-20221117211352019

​ .NET+dotfuscator,搜了一下用了混淆。回顾一下知识点dnsPY用于动态分析,ILSPY用于静态分析。直接用dnsPY和ILSPY打开瞅瞅,发现:

image-20221117232028350

​ 发现有乱码,应该是用dotfuscator混淆过后的。在网上搜了搜,说是用de4dot对刚才的程序去混淆。

​ 混淆之后,发现:

image-20221117233213291

image-20221117233400997

image-20221117233440092

image-20221117233647179

​ 逆向成python脚本,如下:

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2022/11/18 10:54:52
# @Author: wd-2711
'''
import hashlib

def smethod_0(int_0, int_1):
arr = [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, \
31, 37, 41, 43, 47, 53, 59, 61, 67, 71, \
73, 79, 83, 89, 97, 101, 103, 107, 109, 113
]
tmp1 = arr[int_1] ^ ord(int_0)
return chr(tmp1)

def smethod_1(string_0):
string_0 = string_0.encode('utf-8')
m = hashlib.md5()
m.update(string_0)
resp = m.hexdigest()
return "flag{" + resp + "}"

def smethod_2(string_0, int_0):
resp = ""
for i in range(len(string_0)):
c = string_0[i]
num2 = 1
while True:
c = smethod_0(c, num2)
num2 += 1
if num2 >= 15: break
resp += c
resp = smethod_1(resp)
return resp


if __name__ == "__main__":
string_2 = "CreateByTenshine"
value = 11
resp = smethod_2(string_2, value)
print(resp)
# flag{967dddfbcd32c1f53527c221d9e40a0b}

​ 试了试不行,不知道啥原因,然后用dnsPY动态调试一下,发现:

image-20221118111643987

​ 发现是大写,离谱。


2ex1-wp

0x00

​ 题目有两个文件,分别为mxout,推测out是输出的文件,将mx放到exeinfope里面,发现是32位的MIPS。

image-20221119201513503

​ 看了看mx里面,发现好多函数,直接看蒙了。之前也没做过关于mips的题目,所以果断wp。

0x01

​ 妈的,这个题确实离谱。这个题是一个base64换码表的题,这需要一种逆向嗅觉

​ 首先,这么多函数,肯定不能一个一个看啊,通过查找字符串,可以找到:

image-20221119204907182

​ 哎,这里有一个64长度的数组,再结合out中的内容:_r-+_Cl5;vgq_pdme7#7eC0=,猜测可能是一个base64换码表,之后直接上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
# script 1
from base64 import b64decode
miwen = "_r-+_Cl5;vgq_pdme7#7eC0="
key1 = list("@,.1fgvw#`/2ehux$~\"3dity%_;4cjsz^+{5bkrA&=}6alqB*-[70mpC()]89noD")
base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
secret = ""
for i in miwen:
k = key1.index(i)
secret += base64[k]
print(secret)
print(len(secret))
print(b64decode(secret))
# flag{change53233}

​ 看了脚本之后,发现他的流程是这样的:

  1. 根据miwenkey1中对应的索引值。
  2. 根据索引值找base64中对应的值。
  3. 对找到的值进行base64解密。

还学到python的maketrans方法,开了眼儿了。

1
2
3
4
5
6
7
8
9
10
11
# script 2
import base64
std_base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
mg_base = '@,.1fgvw#`/2ehux$~\"3dity%_;4cjsz^+{5bkrA&=}6alqB*-[70mpC()]89noD'

#de_trantab 输出 {mg_base: std_base}的映射
de_trantab = str.maketrans(mg_base,std_base)

flag = base64.b64decode('_r-+_Cl5;vgq_pdme7#7eC0='.translate(de_trantab))

print(flag)

0x02

​ 感觉这题和mips没啥关系啊。

其实感觉自己能做出来,就是遇到没见过的,心里慌了。

知识点1:Angr,基于python的二进制框架。一个多架构的二进制分析平台,具备对二进制文件的动态符号执行能力和多种静态分析能力。值得一学。

知识点2:三大主流架构:mips,Arm,x86。


catch-me-wp

0x00

​ 看一下格式:

image-20221119210623146

​ 不是exe文件,而是XZ package格式。直接改一下后缀,改成1.xz,之后用xz -d 1.xz,报错。

​ 之后用7-Zip解压,解压之后放到exeinfope里看一下,发现是tar文件,再改后缀,再解压,就得到了原始的文件。原来这个文件原始的后缀名是1.tar.xz啊。对应linux解压文件命令应该是:tar -xf 1.tar.xz

​ Catch_Me的话,是一个Debian x64的ELF。

0x01

​ 瞅一眼:

image-20221119214359818

​ 主要就是第2个红框,这是判断第1个红框中生成的hatstack是否正确的条件。

​ 分析第2个红框:

​ 其中,_mm_cvtsi128_si32意思是提取最低的32位。

_mm_ add_epi32意思是:

image-20221119215702950

_mm_srli_si128函数意思是(右移imm*8位):

image-20221119215848981

_mm_unpacklo_epi8函数的意思是(低64位按8位交错):

image-20221119223429312

image-20221119230903508

_mm_unpackhi_epi8函数的意思是:(交错就是相互叠加,差不多这意思,8指的是按8位交错,最后就是高64位按8为交错

image-20221119223347090

​ 可以得到v13=2248, v12=2240

1
2
3
4
5
6
7
8
for v13 in range(4, 1000000):
if v13 + (v13 >> 4) == 2388:
print(v13)
# v13 = 2248
for v12 in range(4, 1000000):
if v12 + (v12 >> 8) == 2248:
print(v12)
# v12 = 2240

​ 再反推v10v11,由于:

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
v5 = _mm_load_si128((const __m128i *)haystack);
v6 = _mm_unpacklo_epi8(v5, (__m128i)0LL);
v7 = _mm_unpackhi_epi8(v5, (__m128i)0LL);
v8 = _mm_add_epi32(
_mm_unpackhi_epi16(v7, (__m128i)0LL),
_mm_add_epi32(
_mm_add_epi32(
_mm_unpackhi_epi16(v6, (__m128i)0LL),
_mm_unpacklo_epi16(v6, (__m128i)0LL)
),
_mm_unpacklo_epi16(v7, (__m128i)0LL))
);
v9 = _mm_load_si128((const __m128i *)&xmmword_601290);
v10 = _mm_unpackhi_epi8(v9, (__m128i)0LL);
v11 = _mm_unpacklo_epi8(v9, (__m128i)0LL);
v12 = _mm_add_epi32(
_mm_add_epi32(
_mm_add_epi32(
_mm_add_epi32(v8, _mm_unpacklo_epi16(v11, (__m128i)0LL)),
_mm_unpackhi_epi16(v11, (__m128i)0LL)
),
_mm_unpacklo_epi16(v10, (__m128i)0LL)
),
_mm_unpackhi_epi16(v10, (__m128i)0LL)
);

​ 得到:

image-20221120151624311

image-20221120151643925

image-20221120151716087

​ 最终我们得到:

image-20221120152631957

​ 其中v9数组是haystack的后128位,v5数组是haystack的前128位。那就继续分析haystack,这是由byte_6012A8数组的来的,而byte_6012A8数组又是由v3数组得来的,而v3数组又满足(*(_DWORD *)getenv("CTF") ^ v3) == 0xFEEBFEEB条件,如下图所示:

image-20221120121001273

​ 其中,HIBYTE函数的意思是得到高一半字节。由于v3指向ebx,那么动调得v3=0xB11924E1,要满足(*(_DWORD *)getenv("CTF") ^ v3) == 0xFEEBFEEB条件,可以计算出CTF=0x4FF2DA0A

​ 分析了很久,觉得getenv("ASIS")getenv("CTF")没啥用,因为这俩是环境变量,你本地没有这个变量,得到个屁。

​ 在把程序从上到下捋一遍,先上图:

image-20221120144030730

  1. 动调得到v3=0xB11924E1haystack=[0x87, 0x29, 0x34, 0xC5, 0x55, 0xB0, 0xC2, 0x2D, 0xEE, 0x60, 0x34, 0xD4, 0x55, 0xEE, 0x80, 0x7C, 0xEE, 0x2F, 0x37, 0x96, 0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED, 0xC1, 0x2B, 0xB1]
  2. 动调得到第2步的4个数组值:[0xb1,0x19,0x4,0xa1]
  3. 第3步得到 aCTF=0x4FF2DA0A
  4. 第4步猜测dword_6012AC=0x4FF2DA0A,则结合第2步结果,可以得到byte_6012A8=[0xb1,0x19,0x4,0xa1,0x4f,0xf2,0xda,0xa][0xb1,0x19,0x4,0xa1,0xa,0xda,0xf2,0x4f](基础没记牢,不知道字符串怎么放的了,事后发现是按后者存放的,那么记录一下:对于ELFx64,字符串低位放低地址,属于小端序)
  5. 第5步对haystack做操作:
1
2
3
4
5
6
7
8
9
10
11
12
haystack=[0x87, 0x29, 0x34, 0xC5, 0x55, 0xB0, 0xC2, 0x2D, 0xEE, 0x60, 0x34, 0xD4, 0x55, 0xEE, 0x80, 0x7C, 0xEE, 0x2F, 0x37, 0x96, 0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED, 0xC1, 0x2B, 0xB1]
byte_6012A8 = [0xb1,0x19,0x4,0xa1,0xa,0xda,0xf2,0x4f]
result = []
for i in range(33):
ii = haystack[i] ^ byte_6012A8[i & 7]
result.append(ii)

print("ASIS{", end = "")
for i in result:
print(chr(i), end = "")
print("}")
# ASIS{600d_j0b_y0u_4r3_63771n6_574r73d}

​ 还要验证得到的result是否满足:

image-20221120153706632

​ 发现不满足,感觉是中间操作的进位什么的忽略了,所以导致了错误

​ 得到flag,太鸡儿幸福了。

补充:HIBYTE是取32位的高8位,BYTE2是取32位的次高8位,以此类推。

0x02

​ 其实,第4步不猜测也可以做出来,脚本为:

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2022/11/20 17:26:41
# @Author: wd-2711
'''

from tqdm import tqdm

def _mm_load_si128(arg1):
if len(arg1) > 16:
return arg1[:16]
elif len(arg1) == 16:
return arg1
else:
return -1

def _mm_unpacklo_epi8(arg1, arg2):
res = []
arg1, arg2 = arg1[:8], arg2[:8]
for i in range(8):
res.extend([arg1[i], arg2[i]])
return res

def _mm_unpackhi_epi8(arg1, arg2):
res = []
arg1, arg2 = arg1[8:], arg2[8:]
for i in range(8):
res.extend([arg1[i], arg2[i]])
return res

def _mm_unpacklo_epi16(arg1, arg2):
res = []
arg1, arg2 = arg1[:8], arg2[:8]
for i in range(4):
res.extend([arg1[i*2], arg1[i*2 + 1], arg2[i*2], arg2[i*2 + 1]])
return res

def _mm_unpackhi_epi16(arg1, arg2):
res = []
arg1, arg2 = arg1[8:], arg2[8:]
for i in range(4):
res.extend([arg1[i*2], arg1[i*2 + 1], arg2[i*2], arg2[i*2 + 1]])
return res

def _mm_unpacklo_epi32(arg1, arg2):
res = []
arg1, arg2 = arg1[:8], arg2[:8]
for i in range(2):
res.extend([
arg1[i*4],
arg1[i*4 + 1],
arg1[i*4 + 2],
arg1[i*4 + 3],
arg2[i*4],
arg2[i*4 + 1],
arg2[i*4 + 2],
arg2[i*4 + 3],
])
return res

def _mm_unpackhi_epi32(arg1, arg2):
res = []
arg1, arg2 = arg1[8:], arg2[8:]
for i in range(2):
res.extend([
arg1[i*4],
arg1[i*4 + 1],
arg1[i*4 + 2],
arg1[i*4 + 3],
arg2[i*4],
arg2[i*4 + 1],
arg2[i*4 + 2],
arg2[i*4 + 3],
])
return res

def _mm_add_epi32(arg1, arg2):
arg1Arr, arg2Arr = [], []
for i in range(4):
arg1Tmp, arg2Tmp = 0, 0
for j in range(4):
arg1Tmp += arg1[i*4 + j] << (j * 8)
arg2Tmp += arg2[i*4 + j] << (j * 8)
arg1Arr.append(arg1Tmp)
arg2Arr.append(arg2Tmp)
arg1Tmp, arg2Tmp = 0, 0


tmp = []
for i in range(4):
tmp.append((arg1Arr[i] + arg2Arr[i]) & 0xffffffff)

ans = []
for i in range(4):
tt = tmp[i]
tt = hex(tt)[2:].zfill(8)
for j in range(4):
j = 3 - j
ans.append(int(tt[2*j:2*j+2], 16))

return ans

def _mm_srli_si128(arg1, arg2):
ans = [0 for i in range(16)]
for i in range(16 - arg2):
ans[i] = arg1[arg2 + i]
return ans

def _mm_cvtsi128_si32(arg1):
arg1 = arg1[:4]
res = 0
for i in range(4):
res += arg1[i] << (i * 8)
return res

def program(haystack):
zero = [0 for i in range(33)]
v5 = _mm_load_si128(haystack)
zero = _mm_load_si128(zero)
v6 = _mm_unpacklo_epi8(v5, zero)
v7 = _mm_unpackhi_epi8(v5, zero)
v8 = _mm_add_epi32(
_mm_unpackhi_epi16(v7, zero),
_mm_add_epi32(
_mm_add_epi32(_mm_unpackhi_epi16(v6, zero), _mm_unpacklo_epi16(v6, zero)),
_mm_unpacklo_epi16(v7, zero)))
v9 = _mm_load_si128(haystack[16:])
v10 = _mm_unpackhi_epi8(v9, zero)
v11 = _mm_unpacklo_epi8(v9, zero)
v12 = _mm_add_epi32(
_mm_add_epi32(
_mm_add_epi32(
_mm_add_epi32(v8, _mm_unpacklo_epi16(v11, zero)),
_mm_unpackhi_epi16(v11, zero)),
_mm_unpacklo_epi16(v10, zero)),
_mm_unpackhi_epi16(v10, zero))
v13 = _mm_add_epi32(v12, _mm_srli_si128(v12, 8))
return _mm_cvtsi128_si32(_mm_add_epi32(v13, _mm_srli_si128(v13, 4))) == 2388

def printFlag(arg):
print("ASIS{", end = "")
for i in arg:
print(chr(i), end = "")
print("}")


if __name__ == "__main__":

# solution 1
haystack = [54, 48, 48, 100, 95, 106, 48, 98, 95, 121, 48, 117, 95, 52, 114, 51, 95, 54, 51, 55, 55, 49, 110, 54, 95, 53, 55, 52, 114, 55, 51, 100, 0]
print(program(haystack))
printFlag(haystack)

# solution 2
haystack = [0x87, 0x29, 0x34, 0xC5, 0x55, 0xB0, 0xC2, 0x2D, 0xEE, 0x60, 0x34, 0xD4, 0x55, 0xEE, 0x80, 0x7C, 0xEE, 0x2F, 0x37, 0x96, 0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED, 0xC1, 0x2B, 0xB1]
byte_6012A8 = [0xb1,0x19,0x4,0xa1, 0, 0, 0, 0]
for i in tqdm(range(256)):
byte_6012A8[4] = i
for j in range(256):
byte_6012A8[5] = j
for k in range(256):
byte_6012A8[6] = k
for l in range(256):
byte_6012A8[7] = l
result = []
for i in range(33):
ii = haystack[i] ^ byte_6012A8[i & 7]
result.append(ii)
if program(result):
printFlag(result)
exit(0)

​ 上述solution2跑的太慢了,而且C语言竟然支持_mm_load_si128等函数..(早知道就不写这么多垃圾函数了。。

​ 直接上C脚本:

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
// FuseCode.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <cstdio>
#include <stdint.h>
#include <emmintrin.h>


bool program(unsigned char * byte_6012A8)
{

__m128i v5;
__m128i v6;
__m128i v7;
__m128i v8;
__m128i v9;
__m128i v10;
__m128i v11;
__m128i v12;
__m128i v13;
__m128i zero;

unsigned char haystack[33] = {
0x87, 0x29, 0x34, 0xC5, 0x55, 0xB0, 0xC2, 0x2D, 0xEE, 0x60, 0x34, 0xD4, 0x55, 0xEE, 0x80, 0x7C,
0xEE, 0x2F, 0x37, 0x96, 0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED, 0xC1, 0x2B,
0xB1
};
unsigned char xmmword_601290[16] = {
0xEE, 0x2F, 0x37, 0x96, 0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED, 0xC1, 0x2B,
};
unsigned char zeros[33] = {
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0
};
for (int i = 0LL; i != 33; ++i ){
haystack[i] ^= byte_6012A8[i & 7];
if (i > 15 && i < 32)
xmmword_601290[i - 16] ^= byte_6012A8[i & 7];

}

v5 = _mm_load_si128((__m128i *)haystack);
zero = _mm_load_si128((__m128i *)zeros);
v6 = _mm_unpacklo_epi8(v5, zero);
v7 = _mm_unpackhi_epi8(v5, zero);
v8 = _mm_add_epi32(
_mm_unpackhi_epi16(v7, zero),
_mm_add_epi32(
_mm_add_epi32(_mm_unpackhi_epi16(v6, zero), _mm_unpacklo_epi16(v6, zero)),
_mm_unpacklo_epi16(v7, zero)));
v9 = _mm_load_si128((__m128i *)xmmword_601290);
v10 = _mm_unpackhi_epi8(v9, zero);
v11 = _mm_unpacklo_epi8(v9, zero);
v12 = _mm_add_epi32(
_mm_add_epi32(
_mm_add_epi32(
_mm_add_epi32(v8, _mm_unpacklo_epi16(v11, zero)),
_mm_unpackhi_epi16(v11, zero)),
_mm_unpacklo_epi16(v10, zero)),
_mm_unpackhi_epi16(v10, zero));
v13 = _mm_add_epi32(v12, _mm_srli_si128(v12, 8));
if (_mm_cvtsi128_si32(_mm_add_epi32(v13, _mm_srli_si128(v13, 4))) == 2388)
return true;
else
return false;
}

int main()
{
// unsigned char byte_6012A8[] = {0xb1,0x19,0x4,0xa1,0xa,0xda,0xf2,0x4f};
unsigned char byte_6012A[8] = {0xb1,0x19,0x4,0xa1,0,128,141,2};
printf("program result:%d\n", program(byte_6012A));
unsigned char byte_6012A8[8] = {0xb1,0x19,0x4,0xa1,0x0,0x0,0x0,0x0};
for(int i = 0; i < 256; i++)
{
byte_6012A8[4] = i;
printf("round = %d\n", i);
for(int j = 0; j < 256; j++)
{
byte_6012A8[5] = j;
for(int k = 0; k < 256; k++)
{
byte_6012A8[6] = k;
for(int l = 0; l < 256; l++)
{
byte_6012A8[7] = l;
if (program(byte_6012A8) == true)
{
if (byte_6012A8[4] == 0xa && byte_6012A8[5] == 0xda && byte_6012A8[6] == 0xf2 && byte_6012A8[7] == 0x4f)
{
printf("should stop\n");
}
for (int m = 0; m < 8; m++)
printf("%d\n", byte_6012A8[m]);
return 0;
}

}
}

}
}
return 0;
}

​ 奇怪的点就是顺序跑的时候竟然byte_6012A[8] = {0xb1,0x19,0x4,0xa1,0,128,141,2};也满足条件,就离谱。看一看其他大佬的wp。

0x03

​ 其他大佬验证getenv("ASIS")是通过导入环境变量完成的。

image-20221120183831867

知识点1:xz是一种压缩文件格式,采用LZMA SDK压缩,目标文件较gzip压缩文件(.gz或.tgz)小30%,较bz2小15%,是对一串文本压缩。xz是绝大数linux默认就带的一个压缩工具。

知识点2:getenv函数,从环境中取字符串,获取环境变量环境变量值,不是运行时传入参数!!!C 库函数 char *getenv(const char *name) 搜索 name 所指向的环境字符串,并返回相关的值给字符串。

1
2
3
4
5
6
7
8
9
10
11
// program.c
#include <stdio.h>
#include <stdlib.h>
int main ()
{
printf("PATH : %s\n", getenv("PATH"));
printf("HOME : %s\n", getenv("HOME"));
printf("ROOT : %s\n", getenv("ROOT"));

return(0);
}

输出:

1
2
3
PATH : /sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
HOME : /
ROOT : (null)

知识点3:SSE2,Streaming SIMD Extensions 2,是一种IA-32架构的SIMD指令集。

  1. IA-32属于X86体系结构的32位版本,即具有32位内存地址和32位数据操作数的处理器体系结构。
  2. SIMD全称Single Instruction Multiple Data,单指令多数据流。以加法指令为例,单指令单数据SISD的CPU对加法指令译码后,执行部件先访问内存,取得第一个操作数之后再一次访问内存,取得第二个操作数;随后才能进行求和运算。而在SIMD型的CPU中,指令译码后几个执行部件同时访问内存,一次性获得所有操作数进行运算。这个特点使SIMD特别适合于多媒体应用等数据密集型运算。
  3. SSE2指令的声明都在emmintrin.h头文件中可以找得到。

举例:

image-20221119214233466

知识点4:IDA调试控制台程序,要传递参数。 在debugger->procession options里

知识点5:ELF文件段分布

image-20221120124915414

知识点6:大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。 这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。 小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。

知识点7:补充一些基本知识:(注意:export只能在当前终端中有效,终端关闭就没用了)

1
2
3
# (通过printf可以导入十六进制整数类型,值得积累)
export ASIS="$(printf "\x0a\xda\xf2\x4f")" #注意参数是从低位到高位的
export CTF="$(printf "\x0a\xda\xf2\x4f")"

知识点8:

image-20221120184022765


zorropub

0x00

​ 查看文件类型为ubuntu 64位的ELF。

0x01

image-20221121224557578

​ 感觉还是比较简单的,中间需要自己手动分析一下,直接上C脚本:(我是在ubuntu 20.04上跑的)

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
/*
gcc -o zorropub_wp zorropub_wp.cpp -lssl -lcrypto -lstdc++
*/
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string.h>
#include<cstddef>
#include"openssl/md5.h"

using namespace std;

/*统计数字的2进制中1的个数*/
int count_bit_one(unsigned int n)
{
int count = 0;
while (n)
{
if (n % 2 == 1)
count++;
n = n / 2;
}
return count;
}

int main()
{
MD5_CTX ctx;
int i;
unsigned int dword_6020C0[30] = {
0x000003C8, 0x00000032, 0x000002CE, 0x00000302, 0x0000007F, 0x000001B8, 0x0000037E, 0x00000188,
0x00000349, 0x0000027F, 0x0000005E, 0x00000234, 0x00000354, 0x000001A3, 0x00000096, 0x00000340,
0x00000128, 0x000002FC, 0x00000300, 0x0000028E, 0x00000126, 0x0000001B, 0x0000032A, 0x000002F5,
0x0000015F, 0x00000368, 0x000001EB, 0x00000079, 0x0000011D, 0x0000024E
};
size_t v3;
char v10[96];
char s[32];
char v12[32];
unsigned char v11[16];
char s1[40];
int seed;
int v9;
// 遍历找seed
for(int j = 0; j < 100000000000; j++)
{
if (count_bit_one(j) == 10)
{
seed = j;
v9 = 10;
srand(seed);
MD5_Init(&ctx);
for (i = 0; i <= 29; ++i)
{
v9 = rand() % 1000;
sprintf(s, "%d", v9);
v3 = strlen(s);
MD5_Update(&ctx, s, v3);
v12[i] = v9 ^ dword_6020C0[i];
}
v12[i] = 0;
MD5_Final(v11, &ctx);
for ( i = 0; i <= 15; ++i )
sprintf(&s1[2 * i], "%02x", (u_int)v11[i]);
if(strcmp(s1, "5eba99aff105c9ff6a1a913e343fec67") == 0)
{
printf("seed: %d\n", seed);
printf("nullcon{%s}\n", v12);
return 0;
}
}
}
return 0;
}
// seed: 59306
// nullcon{nu11c0n_s4yz_x0r1n6_1s_4m4z1ng}

​ 本来想用python写脚本的,但是python的rand什么的得调用C的函数,比较麻烦。

0x02

​ 看了一下其他师傅的wp,发现也可以用pwn做,自己也试一试。(不看wp脚本,自己写

对题目再次进行分析,发现首先发送了一个v5,然后再发送v5v6。我们要满足16 < v6 <= 0xFFFF,之后计算seed=0 xor v6_1 xor v6_2 xor .... xor v6_{v5}并且还要满足:

1
2
3
4
5
6
7
i = seed
v9 = 0
while True:
if i <= 0: break
v9 += 1
i &= (i - 1)
assert v9 == 10

​ 最后写脚本:

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2022/11/22 11:08:41
# @Author: wd-2711
# @Platform: ubuntu 64
'''
from pwn import *
import random

def if_valid(vv):
seed = 0
for v in vv:
seed ^= v
i = seed
v9 = 0
while True:
if i <= 0: break
v9 += 1
i &= (i - 1)
return True if v9 == 10 else False

if __name__ == "__main__":
v5 = 10
for _ in range(1000000):
# 准备好要发送的参数
while True:
v6 = [random.randint(17, 0xffff) for _ in range(v5)]
if(if_valid(v6)):
break
# 交互
# Ubuntu自己做了很多缓冲。当手动确保 pwnTools 对 stdin 和 stdout 使用伪终端时,它可以工作
# 这是真的坑
pty = process.PTY
sh = process("./zorropub", stdin = pty, stdout = pty, raw = True)
a = sh.recvuntil("want?")
sh.sendline(str(v5))
b = sh.recvuntil("drink ids:")
for vv in v6:
sh.sendline(str(vv))
c = sh.recvrepeat()
sh.close()
if b"flag" in c:
print(c)
break

​ 看运气,一般3min能跑出来。

知识点1:C语言的sprintf。把格式化的数据写入某个字符串缓冲区。如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
int main(void){
char buffer[200], s[] = "computer", c = 'l';
int i = 35, j;
float fp = 1.7320534f;
// 格式化并打印各种数据到buffer
j = sprintf( buffer, " String: %s\n", s ); // C4996
j += sprintf( buffer + j, " Character: %c\n", c ); // C4996
j += sprintf( buffer + j, " Integer: %d\n", i ); // C4996
j += sprintf( buffer + j, " Real: %f\n", fp );// C4996

printf( "Output:\n%s\ncharacter count = %d\n", buffer, j );
return 0;
}
// Output:
// String: computer
// Character: l
// Integer: 35
// Real: 1.732053
// character count = 79

知识点2:vscode中写C时,include路径报错,直接在c_cpp_properties.json中添加路径即可。

image-20221121215055049

知识点3:size_t是标准C库中定义的,在64位系统中为long long unsigned int,非64位系统中为long unsigned int。在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的,这样利用该类型可以增强程序的可移植性。定义在cstddefstddef.h中。

知识点4:gcc与g++

image-20221121220805081

image-20221121222504413

知识点5:C语言的strcmp

​ strcmp(str1,str2),若str1=str2,则返回零;若str1str2,则返回正数。

知识点6:pwn的基本使用

https://www.jianshu.com/p/355e4badab50


secret-string-400

0x00

​ 下载下来就显示是gz后缀的压缩包,放入exeinfope.exe中发现是tar包,用7-Zip解压,发现是js+html类型的。

​ 仔细瞅了一眼mechine.js,是js的虚拟机。(之前没遇到过,仔细瞅瞅

​ 看了一下,里面有好多的opcode,首先load字节码,之后逐步运行字节码。在run()函数上打console.log(command, command.args),随机输入字符串,发现输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Opcode11  [1,"Object"]
Opcode12 [1, 'x', "return document.getElementsByTagName('input')[0].value//"]
Opcode15 [3, 1, 'x']
Opcode14 [3, 1, 'userinput']
Opcode12 [1, 'y', 'window.machine.end = function(){this.code=[];this.PC=173}//']
Opcode15 [3, 1, 'y']
Opcode12 [1, 'z', 'alert(1);//\vêObjectÿ\tÿÿÿ\f\nalert(2);//\fêxÿvar f=win…\f\nalert(4);//\nalert(5);//\nalert(6);//\nalert(7);//']
Opcode12 [1, 'g', 'var i =0;while(i<window.machine.code.length){if(wi…ode[i] == 255 ) window.machine.code[i] = 0;i++}//']
Opcode12 [1, 'h', 'window.machine.PC=172//']
Opcode15 [0, 1, 'g']
Opcode15  [0, 1, 'h']
Opcode11  [234, 'Object']
Opcode9 [12]
Opcode12 [234, 'x', "var f=window.machine.registers[1].userinput//\nvar …\n}else{\nalert('NOPE!');\n}}else{alert('NOPE!');}//"]
Opcode9  [12]
Opcode15 [1, 234, 'x']
Opcode9 [12]
Opcode10 []

​ 结合每一个Opcodeprototype,对上述输出进行处理:

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
obj.registers[1] = eval('new Object');

obj.registers[1]['x'] = Function('return document.getElementsByTagName('input')[0].value//');

obj.registers[3] = obj.registers[1]['x']();

obj.registers[1]['userinput'] = obj.registers[3];

obj.registers[1]['y'] = Function('window.machine.end = function(){this.code=[];this.PC=173}//');

obj.registers[3] = obj.registers[1]['y']();

obj.registers[1]['z'] = Function('"alert(1);// êObjectÿ ÿÿÿ alert(2);// êxÿvarf=window.machine.registers[1].userinput//var i = f.length//var nonce = 'groke';//var j = 0;//var out = [];//var eq = true;//while(j < i){//out.push(f.charCodeAt(j) ^ nonce.charCodeAt(j%5))//j++;//}//var ex = [1, 30, 14, 12, 69, 14, 1, 85, 75, 50, 40, 37, 48, 24, 10, 56, 55, 46, 56, 60];//if (ex.length == out.length) {//j = 0;//while(j < ex.length){//if(ex[j] != out[j])//eq = false;//j += 1;//}//if(eq){//alert('YOU WIN!');//}else{alert('NOPE!');}}else{alert('NOPE!');}//ÿ ÿÿÿ alert(3);//êxÿ ÿÿÿ alert(4);//alert(5);//alert(6);//alert(7);//"');

obj.registers[1]['g'] = Function("var i =0;while(i<window.machine.code.length){if(window.machine.code[i] == 255 ) window.machine.code[i] = 0;i++}//");

obj.registers[1]['h'] = Function('window.machine.PC=172//');

obj.registers[0] = obj.registers[1]['g']();

obj.registers[0] = obj.registers[1]['h']();

obj.registers[234] = eval('new Object');

obj.PC = (obj.PC + 12) % obj.code.length;

obj.registers[234]['x'] = Function("var f=window.machine.registers[1].userinput//var i = f.length//var nonce = 'groke';//var j = 0;//var out = [];//var eq = true;//while(j < i){//out.push(f.charCodeAt(j) ^ nonce.charCodeAt(j%5))//j++;//}//var ex = [1, 30, 14, 12, 69, 14, 1, 85, 75, 50, 40, 37, 48, 24, 10, 56, 55, 46, 56, 60];//if (ex.length == out.length) {//j = 0;//while(j < ex.length){//if(ex[j] != out[j])//eq = false;//j += 1;//}//if(eq){//alert('YOU WIN!');//}else{alert('NOPE!');}}else{alert('NOPE!');}//");

obj.PC = (obj.PC + 12) % obj.code.length;

obj.registers[1] = obj.registers[234]['x']();

obj.PC = (obj.PC + 12) % obj.code.length;

obj.PC = obj.callstack.pop();

​ 再继续转成js代码:

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
obj = new Object();
obj.x = function() {
return document.getElementsByTagName('input')[0].value;
};
obj.userinput = obj.x;
obj.y = function() {
window.machine.end = function(){
this.code = [];
this.PC = 173;
}
};
obj.z = function() {
alert(1);
alert(2);
var i = f.length
var nonce = 'groke';
var j = 0;
var out = [];
var eq = true;
while(j < i){
out.push(f.charCodeAt(j) ^ nonce.charCodeAt(j % 5));
j++;
}
var ex = [1, 30, 14, 12, 69, 14, 1, 85, 75, 50, 40, 37, 48, 24, 10, 56, 55, 46, 56, 60];
if (ex.length == out.length) {
j = 0;
while(j < ex.length){
if(ex[j] != out[j])
eq = false;
j += 1;
}
if(eq){
alert('YOU WIN!');
}else{
alert('NOPE!');
}
}
else{
alert('NOPE!');
}
alert(3);
alert(4);
alert(5);
alert(6);
alert(7);
};
obj.g = function() {
var i = 0;
while(i < window.machine.code.length){
if(window.machine.code[i] == 255)
window.machine.code[i] = 0;
i++;
}
}
obj.h = function() {
window.machine.PC = 172;
}
// ...
// 后面感觉用处不大了,其实是懒了

​ 直接上脚本:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2022/11/22 14:55:30
# @Author: wd-2711
'''
ex = [1, 30, 14, 12, 69, 14, 1, 85, 75, 50, 40, 37, 48, 24, 10, 56, 55, 46, 56, 60]
nonce = 'groke'
for id, e in enumerate(ex):
print(chr(ord(nonce[id % 5]) ^ e), end = "")
# flag is: WOW_so_EASY

​ 嘿嘿。

image-20221122145952791

​ 突然想到一个好玩的,就是把code改一下,让他报错不输出"NOPE!",而是输出自己想要的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2022/11/22 15:43:05
# @Author: wd-2711
'''
# 要改的字符串
target = "xx"

initcode = [11, 1, 79, 98, 106, 101, 99, 116, 0, 12, 1, 120, 0, 114, 101, 116, 117, 114, 110, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 103, 101, 116, 69, 108, 101, 109, 101, 110, 116, 115, 66, 121, 84, 97, 103, 78, 97, 109, 101, 40, 39, 105, 110, 112, 117, 116, 39, 41, 91, 48, 93, 46, 118, 97, 108, 117, 101, 47, 47, 0, 15, 3, 1, 120, 0, 14, 3, 1, 117, 115, 101, 114, 105, 110, 112, 117, 116, 0, 12, 1, 121, 0, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 101, 110, 100, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 40, 41, 123, 116, 104, 105, 115, 46, 99, 111, 100, 101, 61, 91, 93, 59, 116, 104, 105, 115, 46, 80, 67, 61, 49, 55, 51, 125, 47, 47, 0, 15, 3, 1, 121, 0, 12, 1, 122, 0, 97, 108, 101, 114, 116, 40, 49, 41, 59, 47, 47, 11, 234, 79, 98, 106, 101, 99, 116, 255, 9, 255, 255, 255, 12, 10, 97, 108, 101, 114, 116, 40, 50, 41, 59, 47, 47, 12, 234, 120, 255, 118, 97, 114, 32, 102, 61, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 114, 101, 103, 105, 115, 116, 101, 114, 115, 91, 49, 93, 46, 117, 115, 101, 114, 105, 110, 112, 117, 116, 47, 47, 10, 118, 97, 114, 32, 105, 32, 61, 32, 102, 46, 108, 101, 110, 103, 116, 104, 47, 47, 10, 118, 97, 114, 32, 110, 111, 110, 99, 101, 32, 61, 32, 39, 103, 114, 111, 107, 101, 39, 59, 47, 47, 10, 118, 97, 114, 32, 106, 32, 61, 32, 48, 59, 47, 47, 10, 118, 97, 114, 32, 111, 117, 116, 32, 61, 32, 91, 93, 59, 47, 47, 10, 118, 97, 114, 32, 101, 113, 32, 61, 32, 116, 114, 117, 101, 59, 47, 47, 10, 119, 104, 105, 108, 101, 40, 106, 32, 60, 32, 105, 41, 123, 47, 47, 10, 111, 117, 116, 46, 112, 117, 115, 104, 40, 102, 46, 99, 104, 97, 114, 67, 111, 100, 101, 65, 116, 40, 106, 41, 32, 94, 32, 110, 111, 110, 99, 101, 46, 99, 104, 97, 114, 67, 111, 100, 101, 65, 116, 40, 106, 37, 53, 41, 41, 47, 47, 10, 106, 43, 43, 59, 47, 47, 10, 125, 47, 47, 10, 118, 97, 114, 32, 101, 120, 32, 61, 32, 32, 91, 49, 44, 32, 51, 48, 44, 32, 49, 52, 44, 32, 49, 50, 44, 32, 54, 57, 44, 32, 49, 52, 44, 32, 49, 44, 32, 56, 53, 44, 32, 55, 53, 44, 32, 53, 48, 44, 32, 52, 48, 44, 32, 51, 55, 44, 32, 52, 56, 44, 32, 50, 52, 44, 32, 49, 48, 44, 32, 53, 54, 44, 32, 53, 53, 44, 32, 52, 54, 44, 32, 53, 54, 44, 32, 54, 48, 93, 59, 47, 47, 10, 105, 102, 32, 40, 101, 120, 46, 108, 101, 110, 103, 116, 104, 32, 61, 61, 32, 111, 117, 116, 46, 108, 101, 110, 103, 116, 104, 41, 32, 123, 47, 47, 10, 106, 32, 61, 32, 48, 59, 47, 47, 10, 119, 104, 105, 108, 101, 40, 106, 32, 60, 32, 101, 120, 46, 108, 101, 110, 103, 116, 104, 41, 123, 47, 47, 10, 105, 102, 40, 101, 120, 91, 106, 93, 32, 33, 61, 32, 111, 117, 116, 91, 106, 93, 41, 47, 47, 10, 101, 113, 32, 61, 32, 102, 97, 108, 115, 101, 59, 47, 47, 10, 106, 32, 43, 61, 32, 49, 59, 47, 47, 10, 125, 47, 47, 10, 105, 102, 40, 101, 113, 41, 123, 47, 47, 10, 97, 108, 101, 114, 116, 40, 39, 89, 79, 85, 32, 87, 73, 78, 33, 39, 41, 59, 47, 47, 10, 125, 101, 108, 115, 101, 123, 10, 97, 108, 101, 114, 116, 40, 39, 78, 79, 80, 69, 33, 39, 41, 59, 10, 125, 125, 101, 108, 115, 101, 123, 97, 108, 101, 114, 116, 40, 39, 78, 79, 80, 69, 33, 39, 41, 59, 125, 47, 47, 255, 9, 255, 255, 255, 12, 10, 97, 108, 101, 114, 116, 40, 51, 41, 59, 47, 47, 15, 1, 234, 120, 255, 9, 255, 255, 255, 12, 10, 97, 108, 101, 114, 116, 40, 52, 41, 59, 47, 47, 10, 97, 108, 101, 114, 116, 40, 53, 41, 59, 47, 47, 10, 97, 108, 101, 114, 116, 40, 54, 41, 59, 47, 47, 10, 97, 108, 101, 114, 116, 40, 55, 41, 59, 47, 47, 0, 12, 1, 103, 0, 118, 97, 114, 32, 105, 32, 61, 48, 59, 119, 104, 105, 108, 101, 40, 105, 60, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 99, 111, 100, 101, 46, 108, 101, 110, 103, 116, 104, 41, 123, 105, 102, 40, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 99, 111, 100, 101, 91, 105, 93, 32, 61, 61, 32, 50, 53, 53, 32, 41, 32, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 99, 111, 100, 101, 91, 105, 93, 32, 61, 32, 48, 59, 105, 43, 43, 125, 47, 47, 0, 12, 1, 104, 0, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 80, 67, 61, 49, 55, 50, 47, 47, 0, 15, 0, 1, 103, 0, 15, 0, 1, 104, 0]
initcode = "".join([chr(i) for i in initcode])
finalcode = initcode.replace('NOPE!', target)
finalcode = [ord(i) for i in finalcode]
print(finalcode)

image-20221122154853100


babymips

0x00

​ 发现是32位的MIPS。

知识回顾:三大主流架构:mips,Arm,x86。

image-20221122164626308

image-20221122164647429

​ 题不难,直接上wp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2022/11/22 16:47:49
# @Author: wd-2711
'''

fdata = "Q|j{g"
off_410D04 = [0x52, 0xFD, 0x16, 0xA4, 0x89, 0xBD, 0x92, 0x80, 0x13, 0x41, 0x54, 0xA0, 0x8D, 0x45, 0x18, 0x81, 0xDE, 0xFC, 0x95, 0xF0, 0x16, 0x79, 0x1A, 0x15, 0x5B, 0x75, 0x1F, 0x00]
# off_410D04.reverse()
for id, fd in enumerate(fdata):
print(chr(ord(fd) ^(32 - id)), end = "")
for i in range(5, 32):
if i % 2 == 1:
f = (off_410D04[i - 5] << 2 | off_410D04[i - 5] >> 6) & 0x7f
else:
f = (off_410D04[i - 5] >> 2 | off_410D04[i - 5] << 6) & 0x7f
f = f ^ (32-i)
print(chr(f), end = "")
print("")
# qctf{ReA11y_4_B@89_mlp5_4_XmAn_}

知识点1:C语言中,int类型在32位与64位中都是4字节。

知识点2:arm和x86架构是小端序,mips架构是大端序。大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。但是数据存放的时候还是按正常的来。


EASYHOOK

0x00

​ 发现是32位可执行PE。

​ 打开之后,找到main函数,首先分析下图所示的sub_401220()

image-20221122193822401

sub_401220()如下图所示:

image-20221122194737266

在这里补充常见的win函数:

函数名 作用
GetCurrentProcessId 返回值是该进程的进程标识符。
OpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId) dwDesiredAccess表示对进程的访问权限,bInheritHandle表示新创建的进程是否继承句柄,dwProcessId表示要打开进程的标识符,返回值为返回值是指定进程的打开句柄或者Null。
LoadLibraryA(lpLibFileName) lpLibFileName表示模块名称(可以是dll也可以是exe),返回值为模块的句柄或者Null。
GetProcAddress(hModule,lpProcName) hModule表示模块的句柄,lpProcName表示函数或变量名称,返回值是此函数或变量的地址,或者Null。
CreateFileA(lpFileName,dwDesiredAccess,dwShareMode, lpSecurityAttributes,dwCreationDisposition,dwFlagsAndAttributes,hTemplateFile) lpFileName表示要创建的文件名称,dwDesiredAccess表示权限,dwShareMode表示共享模式,lpSecurityAttributes为指向一个结构的指针,dwCreationDisposition不重要(没看懂),dwFlagsAndAttributes表示文件属性,hTemplateFile不重要,返回值为文件句柄。
WriteFile(hFile,lpBuffer,nNumberOfBytesToWrite,lpNumberOfBytesWritten,lpOverlapped ) hFile表示要写入文件的句柄,lpBuffer表示要写入的内容的指针,nNumberOfBytesToWrite表示要写入的字节数,lpNumberOfBytesWrittenlpOverlapped不重要。

​ 看上去这个函数是对内存做了一些操作的,返回值为Bool值。(这是因为sub_4010D0的返回值为Bool值)。详细说一下sub_401220的流程:

1
2
3
4
1. v2表示进程ID,hProcess表示再打开一个相同的进程,v0为kernel32.dll的句柄,lpAddress为kernel32.dll中WriteFile函数的地址。
2. 将unk_40c9b4(.data:0040c9b4)指向WriteFile函数的地址。
3. 将byte_40C9BC(.data:0040C9BC)=-23,dword_40C9BD(.data:0040C9BD)设置为sub_401080的函数地址-lpAddress-5。
4. 进入sub_4010D0函数。

​ 再来看sub_4010D0函数:

image-20221122210524091

函数名 作用
VirtualProtectEx(hProcess,lpAddress,dwSize,flNewProtect, lpflOldProtect) hProcess表示要更改内存保护的进程句柄,lpAddress表示要更改保护属性的地址指针,dwSize表示更改访问的区域大小,flNewProtect不重要,lpflOldProtect接收此更改地址的先前访问保护属性,返回值非零(成功)。
WriteProcessMemory(hProcess, lpBaseAddress,lpBuffer,nSize,*lpNumberOfBytesWritten) hProcess表示要写入的进程句柄,lpBaseAddress表示写入到的地址,lpBuffer表示要写入的数据,nSize表示要写入的字节数,*lpNumberOfBytesWritten不重要。

​ 详细说一下sub_4010D0的流程:就是在刚刚创建的hProcesslpAddress(WriteFile)改为0040C9BC

​ 接着看主函数:

1
2
3
1. 运行完sub_401220()之后,创建一个名为"Your_input"的文件句柄v4,并把buffer(输入值)写入到Your_input文件中,写入19个字符。
2. 之后进入sub_401240(Buffer, &NumberOfBytesWritten)函数。
3. 当NumberOfBytesWritten == 1时,输出正确的结果。

​ 之后看一下sub_401240函数:

image-20221122214832380

​ 具体流程:

1
2
1. v4="This_is_not_the_flag",a1为输入的字符串。
2. 要保证v4[a1 - v4 + result] == v4[result]连续21次成立。

0x01

最终没看明白,动调发现main函数的14行(如下图)进入了sub_401080函数(hook所在)。

image-20221122224443170

​ 突然灵光一现,原来sub_401220函数是将WriteFile hook 到了sub_401080里了。

​ 分析一下此函数:

image-20221122230313911

​ 可以看到,参数还是WriteFile的参数。sub_401080过程如下

1
2
3
1. 调用sub_401000,返回值给v5。
2. 调用sub_401140函数。
3. 调用WriteFile函数。

sub_401000函数如下图所示:

image-20221122231247994

​ 卧槽,这个a2不就是nNumberOfBytesToWrite么,NumberOfBytesWritten == 1就是我们要找的正确的结果啊!!!!!妈的,这个出题人太狗了。

​ 回过头来再看sub_401080,做注释:

image-20221122231949033

​ 其实仔细想想,自己就是对hook不熟悉,一会儿总结一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2022/11/22 18:47:40
# @Author: wd-2711
'''

if __name__ == "__main__":
target = [0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F, 0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E, 0x00]
flag = [0 for i in range(20)]
for i, t in enumerate(target):
if i == 18:
flag[i] = chr(t ^ 0x13)
else:
v3 = t ^ i
if i % 2 == 1:
flag[i] = chr(v3 + i)
else:
flag[i + 2] = chr(v3)
for i in flag:
print(i, end = "")

#0lag{Ho0k_w1th_Fun}&
# flag 应该是flag{Ho0k_w1th_Fun}

这个题就是:出题人故意写了一个错误的函数流程,永远不可能成立。然后他把系统函数writeFile给hook了,让他指向了很隐藏的一个函数,这个很隐藏的函数又能影响最终结果。妈的!

知识点1:hook。

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
BOOL sub_401140()
{
VirtualProtectEx(new_process, lpAddress, 5u, 4u, &flOldProtect);
// 又写入初始的 hook 地址
WriteProcessMemory(new_process, lpAddress, &random_addr, 5u, 0);
return VirtualProtectEx(new_process, lpAddress, 5u, flOldProtect, &v1);
}


int hook_to_func(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
{
printf("hook success");
// 又给 hook 回来以执行 WriteFile
sub_401140();
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
return 0;
}

BOOL func1()
{
DWORD v1;
DWORD flOldProtect;
v1 = 0;
// 将构造好的 (random_addr + 8) 写入到新进程的 lpAddress
VirtualProtectEx(new_process, lpAddress, 5u, 4u, &flOldProtect);
WriteProcessMemory(new_process, lpAddress, &random_addr + 8, 5u, 0);
return VirtualProtectEx(new_process, lpAddress, 5u, flOldProtect, &v1);
}

int hook()
{
v2 = GetCurrentProcessId(); // 获取当前进程ID
new_process = OpenProcess(0x1F0FFFu, 0, v2); // 打开一个新进程 new_process
v0 = LoadLibraryA("kernel32.dll"); // 导入 kernel32.dll
lpAddress = GetProcAddress(v0, "WriteFile"); // 找到 WriteFile 的地址
random_addr = *(_DWORD *)lpAddress;
// 以下 3 行就是构造 hook 的地址,使其 hook 到 hook_to_func 函数
*((_BYTE *)&random_addr + 4) = *((_BYTE *)lpAddress + 4); // 保存初始的 WriteFile 地址,,长度一共为5。
//以下是要 hook 到的地址,长度一共为5。
*(random_addr + 8) = -23; // 0xe9 jmp指令
*(random_addr + 9) = (char *)hook_to_func - (char *)lpAddress - 5;
return func1();
}

int main()
{
scanf("%31s", Buffer);
hook(); // 编写hook函数
v4 = CreateFileA("1.txt", 0x40000000u, 0, 0, 2u, 0x80u, 0);
WriteFile(v4, Buffer, 0x13u, &NumberOfBytesWritten, 0); // 触发hook
return result;
}

留言

2022-11-17

© 2024 wd-z711

⬆︎TOP