starCTF2023

 7.29的startCTF,我以为打两天呢,第二天刚准备上线,没想到只打一天…

0x00 SIMPLEX-WMM

1
2
3
4
有人真的可以模拟或仿真 ARM 机器吗?
使用MD5的目的只是为了排除作者可能没有考虑到的情况,与问题的逻辑无关。据作者所知,该问题只有一个解决方案。
HINT2:F**k 弱内存模型 (WMM)
HINT3:你知道单纯形算法的原理吗?

 本体有两个hint,一个是让了解WMM弱内存模型,第二个是simplex算法。64位arm64。

弱内存模型

 C++的out of thin air:线程1先写入变量X,之后线程2再写入变量X,但是由于内存模型的灵活性,可能出现线程1占主导的情况,这种情况叫做out of thin air,它还会导致其他好多问题,例如数据竞争、死锁与崩溃。

 最经典的内存模型就是串行模型(Sequentially Consistent:SC),即并发程序按串行执行(之前计算机组成原理的知识)。举一个例子:

1
2
3
最初:x=y=0
x=1 || y=1
r1=y || r2=x

 按照原始的内存模型,r1与r2至少有一个为0。

 但是实际上,的确有可能出现r1=r2=0。这是为什么呢?编译器在编译时发现这两个线程并没有数据依赖关系,那么编译器就会根据需要颠倒线程内指令的执行顺序。这样,执行顺序就可能为:r1=y -> y=1 -> r2=x -> x=1,就与原始的SC模型不符,这就叫弱内存模型(Weak Memory Model)。

simplex算法

 单纯形算法。在线性规划中,解空间是由一个多面体决定的,最优解一般多面体的某个顶点。单纯形法的基本思想是:以巧妙的方式从一个角到另一个角移动,直到可以证明最优性为止。

 针对优化问题:

 写成矩阵形式为:

 标准形的形式为:

(1)目标函数要求max

(2)约束条件均为等式

(3)决策变量为非负约束

 普通线性规划如何转化成标准形:

(1)若目标函数为最小化,可以通过取负,求最大化

(2)约束不等式为小于等于不等式,可以在左端加入非负松弛变量,转变为等式,比如:

(3)若存在取值无约束的变量,可转变为两个非负变量的差,比如:

 例如,将如下线性规划转化为标准形:

 其可行域为:

image-20230912175821632

 线性规划问题的目标函数最优解必然在可行域的顶点上达到最优。可以总结出:在标准形中,假设有$m$个约束条件(不包括非负约束),$n$个决策变量,那么有$n>=m$。

单纯形法就是通过设置不同的基向量,经过矩阵的线性变换,求得基可行解(可行域顶点),并判断该解是否最优,否则继续设置另一组基向量,重复执行以上步骤,直到找到最优解。所以,单纯形法的求解过程是一个循环迭代的过程。

 具体步骤如下:

(1)选取$m$个基变量$x_{j}^{\prime}(j=1,2,…,m)$,基变量对应约束系数矩阵的列向量线性无关。通过矩阵的线性变换,基变量可由非基变量表示(值得琢磨)

 如果令非基变量等于0,那么基变量的值为:$x’_i=b_i$。如果为可行解的话,$b_i>0$。

 下面探讨它的几何意义(以上面的线性规划为例):如果选择x2、x3为基变量,那么令x1、x4等于0,可以去求解基变量x2、x3的值。对系数矩阵做行变换,如下所示:

 因此,可以得到:$x_2=\frac{9}{2}$、$x_3=\frac{15}{2}$。

 $X_1=0$表示可行解在y轴上;$X_4=0$表示可行解在$x_1+2x_2=9$的直线上。那么,求得的可行解即表示这两条直线的交点,也是可行域的顶点。所以,通过选择不同的基变量,可以获得不同的可行域的顶点。

(2)那么,如何判断最优呢?首先,目标函数也可以由非基变量表示(因为基变量可以由非基变量表示),如下所示:

 当达到最优解时,所有的$\sigma_j$应小于等于0。当存在$j$,$\sigma_j>0$时,当前解不是最优解。为什么?这是因为:当所有非基变量取0时,有$z=z_0$。由上述分析可知,某个非基变量取0代表可行域的边界。如果可行解逐步离开这个边界,$x’_j$会变大,由于$\sigma_j>0$,那么目标函数取值会变大,因此当前解不是最优解。

(3)如何选择新的基变量?如果存在多个$\sigma_j>0$,选择最大的$\sigma_j>0$对应的变量作为基变量,这表示目标函数随着$x’_j$的增加,增长的最快(我们要保留增长最快的,以获得更大的目标函数)。

(4)如何选择被替换的基变量?

 假如我们选择非基变量$x’_s$作为下一轮的基变量,且基变量$x’_j$变为非基变量。被替换的基变量$x’_j$要满足的原则:替换后应该尽量使$x’_s$尽量大,因为目标函数会随着$x’_s$的增大而增大。

 继续以上例说明,上述例子的最后一行:$x_1$的系数为$\frac{1}{2}>0$,所以选$x2,x3$为基变量并没有使目标函数达到最优。下一轮选取$x_1$作为基变量,替换$x_2$、$x_3$中的某个变量。

探索

Step1:先跑起来再说。发现啥输出都没有。

Step2:IDA瞅瞅。是rust的题目,之前没做过。打算看wp。

佬的wp

链接,没看懂啊,不知道在哪儿去找约束什么的。

 于是,自己看了几道rust的例题,我发现,rust的反汇编代码,就是一坨答辩。直接搬运佬的WP,此题通过ARM的WMM去触发异常分支,此题开了两个线程,分别对内存进行操作,从而引发竞争,最终导致WMM的内存不一致。为了增加竞争的可能性,此题在主线程增加了线程的随机调度。

 X86为传统的TSO内存模型,而ARM是WMM内存模型。因此必须在ARM架构上才能能正常打印flag。

 此外,为了防止动态调试,此题使用了1G的内存污染,如果输入正确,那么就会在一段时间后打印出flag,否则会在3分钟左右打印错误提示。此题可以对数据流进行追踪来解题,或者是找到打印flag的条件(一个全局变量)来解题。我通过后一种方法,交叉引用得到程序逻辑(在sub_A1BC中):

image-20230912131301727

 这是一个线性规划问题,总结如下:

 求其最优解即可。至于单纯性算法的应用,是在验证逻辑中,即:如果输入的答案还能松弛得到非0目标值(还能优化),那么说明输入的答案不是最优解,如果是最优解,就无法松弛。

 最终得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2023/09/12 13:22:52
# @Author: wd-2711
# @Link : https://www.cnblogs.com/joy-1120/p/8529772.html
'''

from scipy import optimize
import numpy as np

# 目标参数
c = np.array([70, 65, 80, 75])
# 小于等于
A = np.array([[4,4,3,7],[6,3,5,4],[5,2,3,3],[6,5,1,2]])
b = np.array([90,120,60,100])

# 求解
res = optimize.linprog(-c,A,b,bounds=((0,None),(0,None),(0,None),(0,None)))
print(res)

# x: array([ 0., 15., 10., 0.])
# max = 1775

 将上值使用float编码并链接:

(1)0的float编码为00000000

(2)15转为二进制1111,二进制部分为1.111,指数部分为3,符号位为0。最终,指数为3+127=b(10000010),尾数为11100000000000000000000,最终转为16进制为:41700000,小端序存放为:00007041。见cpp-reverse-analysis

 最终,flag为:*CTF{0000000000007041000020410000000000e0dd44}。但是在我环境下没跑出来,确定是多核ARM处理器。启动参数为:

1
2
3
4
5
6
7
8
9
10
sudo qemu-system-aarch64 \
-M virt \
-cpu cortex-a57 \
-smp 4 \
-m 4G \
-kernel /home/wd/Desktop/starctf/linux-6.3.8/arch/arm64/boot/Image.gz \
-drive format=raw,file=$1 \
-nographic \
-append "noinitrd root=/dev/vda rw console=ttyAMA0 init=/linuxrc ignore_loglevel" \
-drive file=$PWD/share.img,if=virtio

再探索

 发现WP中给出了原始的rust源码,阅读一波。

 两个线程的运行流程如下:

image-20230912163723117

 正常情况下,sum==COUNT一定成立,但是由于是WMM弱内存模型,因此执行顺序可能发生变化,从而出现sum!=COUNT的情况(上图thread2最后两条语句应该靠左对齐)。

 当第一次出现sum!=COUNT时,wmm_count=1,相继进入place_inp_data()check_inp_data()

(1)若检查输入数据不满足约束,则wmm_count+=1。等待下一次出现sum!=COUNT时,wmm_count=3,然后进入check_result()阶段。若check_result()检查输入的数据自洽(不一定是最优解,不一定满足约束),那么succ_flag+=1

(2)若输入数据满足约束,则等待下一次出现sum!=COUNT时,wmm_count=2,然后进入solve()阶段。solve()函数就利用了单纯性算法进行判断。

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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
// 引入libc
extern crate libc;
mod md5;

use lazy_static::lazy_static;
use rand::Rng;
use std::collections::HashSet;
use std::io;
use std::ptr;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;

fn handle_alarm(_: i32) {
println!("Perhaps your input is incorrect, or it is possible that you are not using an ARM CPU with multiple cores. Bye.");
std::process::exit(0);
}

static mut COUNT: u32 = 20;
static mut TWMM: [u32; 102400000] = [0; 102400000];
static mut AAA: *mut u32 = unsafe { TWMM.as_mut_ptr() };
static mut BBB: u32 = 0;
static mut LOOP: i32 = 0;
static mut succ_flag: i32 = 0;
static mut wmm_count: i32 = 0;

// 定义静态变量inp与inp2,它们是可变的长度为40的u8数组
static mut inp: [u8; 40] = [0; 40];
static mut inp2: [u8; 40] = [0; 40];

static CN: usize = 9;
static BN: usize = 5;
static mut matrix: Vec<Vec<f64>> = Vec::new();

/*
"lazy_static!":Rust 的宏,创建在运行时只初始化一次的静态变量。它可以确保变量只在首次被访问时进行初始化,并且之后的访问都会使用已经初始化好的值。
"P" :静态变量,类型为 Mutex<HashSet<usize>>。
Mutex 包装了 HashSet<usize>,以确保对该集合的并发访问是线程安全的。
可以在多个线程中共享变量 P,并使用 Mutex 来保证对 HashSet 的并发访问的安全性。
在需要对 HashSet 进行写操作时,需要先获取 Mutex 的锁,执行完操作后释放锁。
这样可以确保在同一时间只有一个线程能够修改 HashSet,避免数据竞争和不一致性。
*/
lazy_static! {
static ref P: Mutex<HashSet<usize>> = Mutex::new(HashSet::new());
}

unsafe fn pivot(p: &mut (usize, usize)) -> bool {
let mut x = 0;
let mut y = 0;
let mut cmax = -f64::INFINITY;
let c_s = matrix[0].clone();
// b_c 保存利用输入计算的不等式结果
let mut b_c = Vec::new();
for i in 0..BN {
b_c.push(matrix[i][CN - 1]);
}
let mut p_set = P.lock().unwrap();
for i in 0..c_s.len() {
// if cmax < matrix[0][i] and ![7,6,5,4].contain(i):
if cmax < c_s[i] && !p_set.contains(&i) {
cmax = c_s[i];
y = i;
}

}
// cmax = 80,y = 2
if cmax < 0.0 {
return false;
}
let mut bmin = f64::INFINITY;
for i in 1..BN {
// tmp = 不等式结果/[3/5/3/1]
let tmp = b_c[i] / matrix[i][y];
if matrix[i][y] != 0.0 && bmin > tmp && tmp >= 0.0 {
bmin = tmp;
x = i;
}
if tmp < 0.0 {
bmin = tmp;
}
}
// y = 2,x代表满足min(不等式结果/[3/5/3/1])的等式索引
*p = (x, y);
if bmin < 0.0 {
return false;
}

// p_set -> t_v
let mut t_v: Vec<usize> = Vec::new();
for it in p_set.iter() {
t_v.push(*it);
}
for it in t_v.iter() {
if matrix[x][*it] != 0.0 {
p_set.remove(it);
break;
}
}
p_set.insert(y);
true
}

unsafe fn gaussian(p: (usize, usize)) {
let x = p.0;
let y = p.1;
let norm = matrix[x][y];
for i in 0..CN {
matrix[x][i] /= norm;
}
for i in 0..BN {
if i != x && matrix[i][y] != 0.0 {
let tmp_norm = matrix[i][y];
for j in 0..CN {
matrix[i][j] = matrix[i][j] - tmp_norm * matrix[x][j];
}
}
}
}

unsafe fn solve() -> f64 {
let mut t = (0, 0);
let mut countt = 70;
while countt > 0 {
if !pivot(&mut t) {
break;
}
gaussian(t);
countt -= 1;
}
let ret_value = matrix[0][CN - 1];
if matrix[0][4] == 0.0 && matrix[0][5] == 0.0 && matrix[0][6] == 0.0 && matrix[0][7] == 0.0 {
return f64::NEG_INFINITY;
}
ret_value
}

unsafe fn init_data() {
let mut vectmp0 = vec![70.0, 65.0, 80.0, 75.0, 0.0, 0.0, 0.0, 0.0, 0.0];
matrix.push(vectmp0);
let mut vectmp1 = vec![4.0, 4.0, 3.0, 7.0, 1.0, 0.0, 0.0, 0.0]; //, 90.0];
matrix.push(vectmp1);
let mut vectmp2 = vec![6.0, 3.0, 5.0, 4.0, 0.0, 1.0, 0.0, 0.0]; //, 120.0];
matrix.push(vectmp2);
let mut vectmp3 = vec![5.0, 2.0, 3.0, 3.0, 0.0, 0.0, 1.0, 0.0]; //, 60.0];
matrix.push(vectmp3);
let mut vectmp4 = vec![6.0, 5.0, 1.0, 2.0, 0.0, 0.0, 0.0, 1.0]; //, 100.0];
matrix.push(vectmp4);
}

unsafe fn place_inp_data() {
let mut in_arr: Vec<f32> = Vec::with_capacity(5);
for i in 0..5 {
let start = i * 4;
let end = start + 4;
let bytes_slice = &inp2[start..end];
// 将输入变为 5 个 float32 的值
let value = match bytes_slice {
[b0, b1, b2, b3] => {
let bytes_array: [u8; 4] = [*b0, *b1, *b2, *b3];
// 将字节数组解释为小端字节序的 f32 值
f32::from_le_bytes(bytes_array) // or use f64::from_be_bytes(bytes_array) if big-endian
}
_ => panic!("Invalid input"),
};
in_arr.push(value);
}

// 计算 90-4*x1-4*x2-3*x3-7*x4 等
matrix[1].push(
90.0 - in_arr[0] as f64 * matrix[1][0]
- in_arr[1] as f64 * matrix[1][1]
- in_arr[2] as f64 * matrix[1][2]
- in_arr[3] as f64 * matrix[1][3],
);
matrix[2].push(
120.0
- in_arr[0] as f64 * matrix[2][0]
- in_arr[1] as f64 * matrix[2][1]
- in_arr[2] as f64 * matrix[2][2]
- in_arr[3] as f64 * matrix[2][3],
);
matrix[3].push(
60.0 - in_arr[0] as f64 * matrix[3][0]
- in_arr[1] as f64 * matrix[3][1]
- in_arr[2] as f64 * matrix[3][2]
- in_arr[3] as f64 * matrix[3][3],
);
matrix[4].push(
100.0
- in_arr[0] as f64 * matrix[4][0]
- in_arr[1] as f64 * matrix[4][1]
- in_arr[2] as f64 * matrix[4][2]
- in_arr[3] as f64 * matrix[4][3],
);
}

unsafe fn input_data() {
// 定义数组ss,其可变,元素为uint8,长度为40
// pointer t = ss
let mut ss: [u8; 40] = [0; 40];
let mut t = ss.as_mut_ptr();
let mut input_string = String::new();

// inp_string -> ss
io::stdin().read_line(&mut input_string).expect("read error");
let input_bytes = input_string.trim().as_bytes();
let input_length = input_bytes.len().min(40);
ss[..input_length].copy_from_slice(&input_bytes[..input_length]);

// ss -> inp
inp.copy_from_slice(&ss[..40]);

/*
if t[i] >= '0' && t[i] <= '9':
t[i] -= '0'
elif t[i] >= 'a' && t[i] <= 'f':
t[i] -= 'a' + 0xa;
if i % 2 == 0:
t[i/2] = t[i] << 4
else:
t[i/2] = t[i/2] | (t[i] & 0xf)
*/
for i in 0..40 {
if (*t.offset(i as isize) > b'0' - 1 && *t.offset(i as isize) < b'9' + 1) {
*t.offset(i as isize) -= b'0';
} else if (*t.offset(i as isize) > b'a' - 1 && *t.offset(i as isize) < b'g') {
*t.offset(i as isize) = *t.offset(i as isize) - b'a' + 0xa;
}
if i % 2 == 0 {
*t.offset((i / 2) as isize) = *t.offset(i as isize) << 4;
} else {
*t.offset((i / 2) as isize) =
*t.offset((i / 2) as isize) | (*t.offset(i as isize) & 0xf);
}
}

// ss -> inp2
inp2.copy_from_slice(&ss[..40]);

// md5(ss[:20])
let mut ctx: md5::MD5Context = md5::MD5Context {
size: 0,
buffer: [0; 4],
input: [0; 64],
digest: [0; 16],
};
md5::md5_init(&mut ctx);
md5::md5_update(&mut ctx, &mut ss, 20);
md5::md5_finalize(&mut ctx);


let hash_result2 = "d2edf678c89caf9979ec2b246634d284";
let mut hash_buf: [u8; 32] = [0; 32];
for i in 0..16 {
// ctx.digest[i]的高4位
let u_data1 = (ctx.digest[i] >> 4) & 0xF;
// ctx.digest[i]的低4位
let u_data2 = ctx.digest[i] & 0xF;
// hash_buf = str(ctx.digest)
hash_buf[i * 2] = if u_data1 > 9 {
u_data1 - 10 + b'a'
} else {
u_data1 + b'0'
};
hash_buf[i * 2 + 1] = if u_data2 > 9 {
u_data2 - 10 + b'a'
} else {
u_data2 + b'0'
};
}
let hash_result = String::from_utf8_lossy(&hash_buf);
if hash_result2 == hash_result {
succ_flag += 1;
}
}

// 检查输入是否合格,返回 1 则不合格
unsafe fn check_inp_data() -> bool {
90.0 < matrix[1][CN - 1]
|| 120.0 < matrix[2][CN - 1]
|| 60.0 < matrix[3][CN - 1]
|| 100.0 < matrix[4][CN - 1]
}

unsafe fn check_result() -> bool {
let mut in_arr: Vec<f32> = Vec::with_capacity(5);
for i in 0..5 {
let start = i * 4;
let end = start + 4;
let bytes_slice = &inp2[start..end];
let value = match bytes_slice {
[b0, b1, b2, b3] => {
let bytes_array: [u8; 4] = [*b0, *b1, *b2, *b3];
f32::from_le_bytes(bytes_array) // or use f64::from_be_bytes(bytes_array) if big-endian
}
_ => panic!("Invalid input"),
};
in_arr.push(value);
}
return in_arr[4] as f64
== in_arr[0] as f64 * matrix[0][0]
+ in_arr[1] as f64 * matrix[0][1]
+ in_arr[2] as f64 * matrix[0][2]
+ in_arr[3] as f64 * matrix[0][3];
}

static mut TEMP_FFF: u8 = 1;

unsafe fn thread1() {
// 初始化 matrix
init_data();
// 创建随机数生成器 rng
let mut rng = rand::thread_rng();
// 循环
loop {
// TWMM[:20] = 1
for i in 0..COUNT {
*AAA.offset(i as isize) = 1;
}
BBB = 1;
// 读取 LOOP 的最新值,如果为 0 则等待
while ptr::read_volatile(&LOOP) == 0 {}
// AAA 跳转到 0-1G 的随机位置
AAA = &mut *TWMM.as_mut_ptr().offset(rng.gen_range(0..102300000) as isize);
COUNT = rng.gen_range(0..10);
// AAA[:COUNT] = 0
for j in 0..COUNT {
*AAA.offset(j as isize) = 0;
}
if (TEMP_FFF == 1) && (succ_flag == 2) {
let mut vectmp0 = vec![70.0, 65.0, 80.0, 75.0, 0.0, 0.0, 0.0, 0.0, 0.0];
matrix[0] = vectmp0;
TEMP_FFF = 0;
}
BBB = 0;
LOOP = 0;
}
}

unsafe fn thread2() {
// 可以在多个线程中获取 P 的锁,并将锁定的 MutexGuard 绑定到变量 p_a 上,以进行对 HashSet 的可变访问。在使用完毕后,MutexGuard 会在其作用域结束时自动释放锁,允许其他线程获取同一个锁。
// 通过调用 unwrap(),如果锁定操作成功,就会返回锁定的 MutexGuard 对象;如果锁定操作失败,就会触发 panic。
let mut p_a = P.lock().unwrap();
for i in 0..(BN - 1) {
// 向 HashSet 中插入 7,6,5,4
p_a.insert(CN - i - 2);
}
drop(p_a);
loop {
// 读取 BBB 的最新值,如果为 0 则等待
while ptr::read_volatile(&BBB) == 0 {}
let mut sum: u32 = 0;
/*
for i in [0,COUNT)[::-1]:
sum += AAA[i]
*/
for i in (0..COUNT).rev() {
sum += *AAA.offset(i as isize);
}
if sum != COUNT {
wmm_count += 1;
match wmm_count {
1 => {
place_inp_data();
if check_inp_data() {
wmm_count += 1;
}
}
2 => {
let ret = solve();
if ret == 0.0 {
succ_flag += 1;
}
}
3 => {
if check_result() {
succ_flag += 1;
}
}
_ => {}
}
}
LOOP = 1;
// 读取 LOOP 的最新值,如果为 1 则等待
while ptr::read_volatile(&LOOP) == 1 {}
}
}

fn main() {
// 启动和管理线程的扩展库
use std::os::unix::thread::JoinHandleExt;
unsafe {
// 180s之后,执行handle_alarm函数
libc::alarm(180);
libc::signal(libc::SIGALRM, handle_alarm as usize);
}
unsafe {
input_data();
}
let mut th1;
let mut th2;
unsafe {
// move表示将所有捕获的变量移动到闭包内部
// 创建新线程,返回句柄th1,并运行move+thread1()
th1 = thread::spawn(move || thread1());
th2 = thread::spawn(move || thread2());
}

let mut num_cores: i64;
unsafe {
num_cores = libc::sysconf(libc::_SC_NPROCESSORS_CONF);
}
while unsafe { succ_flag < 3 } {
let cpuid = rand::thread_rng().gen_range(0..num_cores);
unsafe {
let mut cpuset: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_SET(cpuid as usize, &mut cpuset);
libc::pthread_setaffinity_np(
th1.as_pthread_t(),
std::mem::size_of::<libc::cpu_set_t>(),
&cpuset,
);
}

let cpuid = rand::thread_rng().gen_range(0..num_cores);
unsafe {
let mut cpuset: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_SET(cpuid as usize, &mut cpuset);
libc::pthread_setaffinity_np(
th2.as_pthread_t(),
std::mem::size_of::<libc::cpu_set_t>(),
&cpuset,
);
}

thread::sleep(Duration::from_micros(500000));
}
unsafe {
println!("*CTF{{{}}}", String::from_utf8_lossy(&inp));
}
}

 总结一下:

(1)首先注册定时函数,若3分钟没有结果,则输出错误。

(2)thread1与thread2相互竞争,WMM弱内存模型保证其会在一段时间内执行验证程序。

(3)验证程序使用单纯性算法,具体在gaussian()pivot()内,但是没具体看,主要是将原理搞懂了。

(4)题目好难。

0x01 GoGpt

1
2
3
4
5
This is a eazy reverse challenge provided by gpt-4. Show me if you can win over AI !
unzip password: gpt4
Notice: this file may be reported as malicious, please don’t worry about it. It does not contain any malicious code.
这是 gpt-4 提供的简单反向挑战。 告诉我你是否能战胜人工智能!
注意:此文件可能会被报告为恶意文件,请不要担心。 它不包含任何恶意代码。

 64位go。go_parser处理一波。非常简单的逻辑:

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

import base64

arg = "TcR@3t_3hp_5_G1H"
res = "fiAGBkgXN3McFy9hAHRfCwYaIjQCRDFsXC8ZYBFmEDU=".encode()
res = base64.b64decode(res)
for i in range(len(res)):
print(chr(res[i]^ord(arg[i%16])), end = "")
# *CTF{ch@tgpT_3nCRypt10n_4_FUN!!}

0x02 ez_code

1
2
3
小明是一个windows的骨灰级用户,他精通各种脚本语言。有一天他突然发给我一份奇怪的文件,你能帮我看看里面藏了什么吗?
注意:可能存在假flag
请注意,该任务可能存在假flag,请仔细分析

 plaintext。里面的符号有23种,分别为:

1
{'@', ';', '}', '$', '{', ')', ']', '%', "'", '*', '(', '?', '|', '!', ' ', '#', '`', '"', '+', '-', '[', '=', '.'}

 猜测应该是github上某个项目。找了好久没找到(咋搜索的啊),看wp。wp中说是powershell混淆,日了狗。将chall另存为chall.ps1并执行,输出Do you konw PWSH(powershell)?

 首先尝试使用PowerShell日志去混淆,但是一直抓不到日志。使用PowerDecode解码,得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function A{
[CmdletBinding()] param(
[Parameter(Position = 0)]
[String]
$param1
)
$result = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($param1))
return $result
}

Function B={
[CmdletBinding()] param(
[Parameter(Position = 0)]
[String]
$param1
)

$param1 = A -param1 $param1
$result = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($param1))
$result | out-null
}

B = ("S21OMFpudG9hR2hmY0hkemFGOXBjMTlsWVhONVgzSnBaMmgwUDMwPQ==")
echo "Do you konw PWSH?"

 两次decode之后是*ctf{hhh_pwsh_is_easy_right?},这是假的flag。

 看wp,使用ISE下断点,并通过${@*}这条特殊用法,将参数列表作为一个数组展开,然后使用通配符*将数组中的每个元素进行展开,让其解混淆。如下所示:

image-20230913170351801

 因此,可以猜出:程序主逻辑并不在脚本里,而是在参数列表中。将输出的参数列表保存到command.txt,并转为程序,脚本如下所示:

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

with open("C:\\Users\\23957\\Desktop\\ez_code\\command.txt", "r") as f:
data = f.read()

pattern = re.compile(r'\[CHar\]\d+')
m = pattern.findall(data)
program = ""
for mm in m:
mm = chr(int(mm[6:], 10))
program += mm
print(program)

 得到:

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
class chiper():
def __init__(self):
self.d = 0x87654321
k0 = 0x67452301
k1 = 0xefcdab89
k2 = 0x98badcfe
k3 = 0x10325476
self.k = [k0, k1, k2, k3]

def e(self, n, v):
from ctypes import c_uint32

def MX(z, y, total, key, p, e):
temp1 = (z.value >> 6 ^ y.value << 4) + \
(y.value >> 2 ^ z.value << 5)
temp2 = (total.value ^ y.value) + \
(key[(p & 3) ^ e.value] ^ z.value)
return c_uint32(temp1 ^ temp2)
key = self.k
delta = self.d
rounds = 6 + 52//n
total = c_uint32(0)
z = c_uint32(v[n-1])
e = c_uint32(0)

while rounds > 0:
total.value += delta
e.value = (total.value >> 2) & 3
for p in range(n-1):
y = c_uint32(v[p+1])
v[p] = c_uint32(v[p] + MX(z, y, total, key, p, e).value).value
z.value = v[p]
y = c_uint32(v[0])
v[n-1] = c_uint32(v[n-1] + MX(z, y, total,
key, n-1, e).value).value
z.value = v[n-1]
rounds -= 1
return v

def bytes2ints(self,cs:bytes)->list:
new_length=len(cs)+(8-len(cs)%8)%8
barray=cs.ljust(new_length,b'\x00')
i=0
v=[]
while i < new_length:
v0 = int.from_bytes(barray[i:i+4], 'little')
v1 = int.from_bytes(barray[i+4:i+8], 'little')
v.append(v0)
v.append(v1)
i += 8
return v

def check(instr:str,checklist:list)->int:
length=len(instr)
if length%8:
print("Incorrect format.")
exit(1)
c=chiper()
v = c.bytes2ints(instr.encode())
output=list(c.e(len(v),v))
i=0
while(i<len(checklist)):
if i<len(output) and output[i]==checklist[i]:
i+=1
else:
break
if i==len(checklist):
return 1
return 0

if __name__=="__main__":
ans=[1374278842, 2136006540, 4191056815, 3248881376]
# generateRes()
flag=input('Please input flag:')
res=check(flag,ans)
if res:
print("Congratulations, you've got the flag!")
print("Flag is *ctf{your_input}!")
exit(0)
else:
print('Nope,try again!')

 魔改的xxtea,解密脚本为:

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
#include <stdio.h>  
#include <stdint.h>
#define DELTA 0x87654321
#define MX (((z>>6^y<<4) + (y>>2^z<<5)) ^ ((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()
{
uint32_t v[4]= {1374278842, 2136006540, 4191056815, 3248881376};
uint32_t const k[4]= {0x67452301,0xefcdab89,0x98badcfe,0x10325476};
int n=4; //n的绝对值表示v的长度,取正表示加密,取负表示解密
btea(v, -n, k);
printf("解密后的数据:%u %u %u %u\n",v[0],v[1],v[2],v[3]);
return 0;
}
// 解密后的数据:1632980857 812069746 1950368879 1211463504
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2023/09/13 17:38:15
# @Author: wd-2711
'''

data = [1632980857,812069746,1950368879,1211463504]
for d in data:
d = hex(d)[2:]
cc = ""
for i in range(4):
c = chr(int(d[i*2:i*2+2],16))
cc = c + cc
print(cc, end = "")
# yOUar3g0oD@tPw5H

 最终flag为*CTF{yOUar3g0oD@tPw5H}

0x03 flagfile

1
flag format: flag{}

 其中由flag(空文件)、flag.mgc(magic?)、readme.txtreadme.txt内容为:

1
2
3
4
5
6
生成您自己的 flag 文件,使用 file 命令进行验证,如下所示:
$ file -m flag.mgc flag
flag: yes, it's a flag!
$ file --version
file-5.41
magic file from /usr/share/file/magic

 思路:(1)找到这样的 file 命令行程序。(2)搞清file -m的原理。

 在链接1安装file-5.41,在链接2下载file-5.41。但是出问题,搜索发现使用的是ubuntu20.04,好像得用22.04才行。弄完之后,输出:

image-20230920221215302

 搜索了一波file -m,发现是这样的:file -m aa.mgc bbaa.mgc是格式文件,就像010editor的模板文件。我猜测,是将flag文件中放入东西,然后使用flag.mgc模板文件判断输入是否正确。

 gdb调试一波。结合源码,可以发现:

image-20230921140300854

 其中,applyparam表示要将mgc文件读取到magic_t对象中。但是我们的选项中没有添加-f,所以接着往下看hh。看到貌似处理函数process()

image-20230921163206739

 跟进process的源码:

image-20230921163340604

 经过调试,感觉需要关注type字段,于是跟进magic_file函数,在magic_file结束后,会输出文件类型。进而跟进到file_or_fd函数。在其中,有:

image-20230921183450713

file_fsmagic函数表示:在文件中查找魔术值(magic value)。经过调试,此函数返回0,此时在flag文件中存放的12345678。审计一下file_fsmagic函数:

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
// ms 是 magic_set
// fn 是要匹配的文件名
// sb 是文件统计信息
#define MAGIC_MIME 0x0000410
protected int file_fsmagic(struct magic_set *ms, const char *fn, struct stat *sb) {
int ret, did = 0;
int mime = ms->flags & MAGIC_MIME;
int silent = ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION);
if (fn == NULL)
return 0;

#define COMMA ""
ret = stat(fn, sb); /* don't merge into if; see "ret =" above */
...
ret = 1;
if (!mime && !silent) {
...
}
switch (sb->st_mode & S_IFMT) {
...
case S_IFREG:
/*
常规文件
(1)当使用 stat() 函数获取文件大小时,如果文件大小为零(空文件),我们可以跳过打开和读取文件的操作。
(2)如果用户在命令行中使用了 -s 选项,我们将跳过(1)优化。
*/
if ((ms->flags & MAGIC_DEVICES) == 0 && sb->st_size == 0) {
if (mime) {
if (handle_mime(ms, mime, "x-empty") == -1)
return -1;
} else if (file_printf(ms, "%sempty", COMMA) == -1)
return -1;
break;
}
ret = 0;
break;
default:
file_error(ms, 0, "invalid mode 0%o", sb->st_mode);
return -1;
/*NOTREACHED*/
}
if (!silent && !mime && did && ret == 0) {
if (file_printf(ms, " ") == -1)
return -1;
}
if (ret == 1 && silent)
return 0;
return ret;
}

 调迷糊了。看wp。

wp

 wp并不是调试出来的,而是看文档得出的。好,我也看文档,链接

file-5.41magic文件语法,举几个例子:

(1)例1:

1
2
3
4
5
0           string  MZ
>0x18 leshort <0x40 MZ executable (MS-DOS)
>0x18 leshort >0x3f
>>(0x3c.l) string PE\0\0 PE executable (MS-Windows)
>>(0x3c.l) string LX\0\0 LX executable (OS/2)

 解释如下:

1
2
3
4
5
6
7
8
if 检查偏移地址 0 是否以字符串 "MZ" 开头:
if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值小于 0x40:
标识为 MZ executable (MS-DOS)
else if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值大于 0x3f:
if 偏移量 0x3c 处读取一个长整数值,此值为 "PE\0\0":
标识为 PE executable (MS-Windows)
else if 偏移量 0x3c 处读取一个长整数值,此值为 "LX\0\0":
标识为 LX executable (OS/2)

(2)例2:

1
2
3
4
0           string  MZ
>0x18 leshort <0x40
>>(4.s*512) leshort 0x014c COFF executable (MS-DOS, DJGPP)
>>(4.s*512) leshort !0x014c MZ executable (MS-DOS)

 解释如下,其中(4.s*512)=hex(4*512)=0x400

1
2
3
4
5
6
if 检查偏移地址 0 是否以字符串 "MZ" 开头:
if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值小于 0x40:
if 偏移量 0x400 处读取一个小端序的 16 位短整数,此值为 0x014c:
标识为 COFF executable (MS-DOS, DJGPP)
else if 偏移量 0x400 处读取一个小端序的 16 位短整数,此值不为 0x014c:
标识为 MZ executable (MS-DOS)

(3)例3:

1
2
3
4
5
0           string  MZ
>0x18 leshort >0x3f
>>(0x3c.l) string PE\0\0 PE executable (MS-Windows)
>>>&0 leshort 0x14c for Intel 80386
>>>&0 leshort 0x184 for DEC Alpha

 解释如下:

1
2
3
4
5
6
7
8
if 检查偏移地址 0 是否以字符串 "MZ" 开头:
if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值大于 0x3f:
if 偏移量 0x3c 处读取一个长整数值,此值为 "PE\0\0":
标识为 PE executable (MS-Windows)
if 在当前位置(偏移量为 0)读取一个小端序的 16 位短整数字段,并检查其值是否为 0x14c:
标识为 for Intel 80386
else if 在当前位置(偏移量为 0)读取一个小端序的 16 位短整数字段,并检查其值是否为 0x184:
标识为 for DEC Alpha

(4)例4:

1
2
3
4
0             string  MZ
>0x18 leshort <0x40
>>(4.s*512) leshort !0x014c MZ executable (MS-DOS)
>>>&(2.s-514) string LE LE executable (MS Windows VxD driver)

 解释如下:

1
2
3
4
5
6
if 检查偏移地址 0 是否以字符串 "MZ" 开头:
if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值小于 0x40:
if 偏移量 0x400 处读取一个小端序的 16 位短整数字段,检查此值是否不为 0x014c:
标识为 MZ executable (MS-DOS)
if 在当前位置-512 处读取一个长整数值,此值为 "LE":
标识为 LE executable (MS Windows VxD driver)

(5)例5:

1
2
3
4
0                 string  MZ
>0x18 leshort >0x3f
>>(0x3c.l) string LE\0\0 LE executable (MS-Windows)
>>>(&0x7c.l+0x26) string UPX \b, UPX compressed

 解释如下:

1
2
3
4
5
6
if 检查偏移地址 0 是否以字符串 "MZ" 开头:
if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值大于 0x3f:
if 偏移量 0x3c 处读取一个长整数值,此值为 "LE\0\0":
标识为 LE executable (MS-Windows)
if 在当前位置+0x7c+0x26 处读取一个长整数值,此值为 "UPX":
标识为 \b, UPX compressed

(6)例6:

1
2
3
4
0                string  MZ
>0x18 leshort >0x3f
>>(0x3c.l) string LE\0\0 LE executable (MS-Windows)
>>>&(&0x54.l-3) string UNACE \b, ACE self-extracting archive

 解释如下:

1
2
3
4
5
6
if 检查偏移地址 0 是否以字符串 "MZ" 开头:
if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值大于 0x3f:
if 偏移量 0x3c 处读取一个长整数值,此值为 "LE\0\0":
标识为 LE executable (MS-Windows)
if 在当前位置+0x51 处读取一个长整数值,此值为 "UNACE":
标识为 \b, ACE self-extracting archive

(7)例7:

1
2
3
4
5
0                 string       MZ
>0x18 leshort >0x3f
>>(0x3c.l) string PE\0\0 PE executable (MS-Windows)
>>>&0xf4 search/0x140 .idata
>>>>(&0xe.l+(-4)) string PK\3\4 \b, ZIP self-extracting archive

 解释如下:

1
2
3
4
5
6
7
8
if 检查偏移地址 0 是否以字符串 "MZ" 开头:
if 偏移量为 0x18 处的字段是否是一个小端序的 16 位短整数,并且其值大于 0x3f:
if 偏移量 0x3c 处读取一个长整数值,此值为 "PE\0\0":
标识为 PE executable (MS-Windows)
if 在当前位置+0xf4 ,向后搜索长度为 0x140 字节的数据块,并找到 ".idata":
if 在当前位置-4 处读取一个长整数值,此值为 "PK\3\4":
处读取一个长整数值,此值为 "UNACE":
标识为 \b, ZIP self-extracting archive

(8)例8:

1
2
3
4
5
>18     clear
>18 lelong 1 one
>18 lelong 2 two
>18 default x
>>18 lelong x unmatched 0x%x

 解释如下:

1
2
3
4
5
6
7
8
9
偏移地址 0x18 清除之前设置的条件
if 偏移地址 0x18 检查一个小端序的长整数,且该值为 1:
标识为 one
else if 偏移地址 0x18 检查一个小端序的长整数,且该值为 2:
标识为 two
else:
偏移地址 0x18 设置为 "x"
if 偏移地址 0x18 检查一个小端序的长整数,且该值为 "x":
标识为 unmatched 0x%x

 关键问题是,flag.mgc如何转成上述这样的格式?

 就要用到如下脚本(flag.mgc (binary)--> flag.magic (code)):

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

f = open("filepath", 'rb')

# 每次 0x178 字节,但是每 1 行是 0x30 bytes
b = f.read(0x178)
indexes = []
table = ''
while True:
b = f.read(0x178)
if len(b) != 0x178:
break
line = b[:0x30]
# type
_type = line[6]
# offset
# line 的偏移量 0x0c 处开始,按照小端序解析数据,将该位置的 4 个字节解析为一个无符号的 32 位整数
off = struct.unpack_from('<I', line, 0x0c)[0]
s = f'type: {_type:02X}, off: {off:02X}'
if _type == 5:
# 5 代表解析 line[0x20:] 的字符串
s += ', str: '+line[0x20:].decode()
elif _type == 1:
# 1 代表 line[0x18]^line[0x20] 异或的 byte
n1, n2 = line[0x18], line[0x20]
v = n1 ^ n2
s += f', byte: {n1:02X} {n2:02X} {v:02X} {chr(v)}'
table += chr(v)
elif _type == 10:
# 10 代表 line[0x18]^line[0x20] 异或的 16 比特数
n1, n2 = line[0x18], line[0x20]
v = n1 ^ n2
s += f', leshort: {n1:02X} {n2:02X} {v:02X} {v}'
indexes.append(v)
print(s)

 上述脚本中,table为字符,index为字符所在的位置。最后可以得到flag:flag{_oh_yes_you_got_the_flag___^_^__}

总结:不知道他这脚本咋写出来的,感觉主要得靠猜(可能是我看的不仔细吧呜呜)

0x04 boring cipher

 有一个cipher-release,输入字符串,输出与cipher-release同样大小的加密文件。看到用了llvm混淆+rust,感觉到了shit。

 题目正向逻辑非常简单:

(1)输入32位字符串,根据每8位输入将0->15做混淆,混淆20轮。

(2)根据0->15的混淆对data[256]做混淆,混淆15轮。

(3)根据data[256]对当前文件做self_exe[ii] += data[self_exe[ii]]处理。

 相关正向脚本如下:

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
# (1)(2) 步对输入的混淆,得到 data[256]
import struct
data = []
with open("D:\\Code\\Python\\re\\boringcipher\\boringcipher-data", "rb") as f:
d = f.read()
for i in range(1260):
dd = d[i*4:(i+1)*4]
dd = struct.unpack_from('<I', dd, 0)[0]
data.append(dd)

def func():
v1 = 0
v46 = [0 for i in range(256)]
v31 = 0
# 输入
inp_origin = "!eX`vLk maQt;$_!b-2/2 *;U3([+47%"
while True:
inp = inp_origin[8*v1:8*v1+8]
d = [i for i in range(0x15)]
inp = [hex(ord(i))[2:] for i in inp]
inp = "".join(inp)
v4 = int(inp, 16)
v7 = [2432902008176640000,121645100408832000,6402373705728000,355687428096000,20922789888000,1307674368000,87178291200,6227020800,479001600,39916800,3628800,362880,40320,5040,720,120,24,6,2,1]
jj = 0
while True:
j = jj + v4 // v7[jj]
v4 = v4 % v7[jj]
tmp = d[j]
d[j] = d[jj]
d[jj] = tmp
jj += 1
if jj == 20:
break
for i in range(0x15):
v16 = 15 * (21 * v1 + d[i])
for j in range(15):
tmp = data[v16 + j]
if tmp != 0xFFFFFFFF:
if j == 14:
v31 = tmp
if tmp > 0xFF or j == 14:
v46[v31] += i
break
v46[tmp] += i
v1 += 1
if v1 == 4:
break
print(d)
print(v46)
print([hex(i) for i in v46])
func()

 应该得到的data[256]数组为:

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
# target data[256]
with open("D:\\Code\\Python\\re\\boringcipher\\origin-elf", "rb") as f:
origin_elf = f.read()
with open("D:\\Code\\Python\\re\\boringcipher\\output", "rb") as f:
output = f.read()

MIN = -10000000
v46 = [MIN for i in range(256)]
for i in range(0x8000):
tmp = output[i] - origin_elf[i]
if v46[origin_elf[i]] == MIN:
v46[origin_elf[i]] = tmp
elif v46[origin_elf[i]] != tmp:
print(i, v46[origin_elf[i]], tmp)

miss = 0
for i in v46:
if i == MIN:
miss += 1
print("miss:", miss)

for i in range(len(v46)):
if v46[i] < 0:
v46[i] = 0xffffffff - (-v46[i]) + 1
print(v46)
"""
[39, 3, 53, 38, 17, 35, 12, 0, 21, 1, 24, 18, 68, 18, 3, 11, 3, 40, 16, 38, 21, 52, 5, 63, 34, 21, 8, 7, 24, 24, 40, 4, 40, 51, 56, 14, 19, 20, 9, 31, 15, 9, 8, 21, 0, 15, 18, 4, 29, 22, 32, 17, 16, 23, 35, 25, 26, 11, 14, 33, 18, 35, 9, 17, 42, 31, 30, 19, 36, 22, 43, 19, 11, 20, 29, 40, 24, 20, 13, 9, 18, 48, 20, 19, 6, 35, 5, 38, 43, 29, 44, 37, 37, 20, 25, 16, 7, 17, 17, 2, 41, 24, 18, 25, 10, 14, 57, 43, 46, 24, 27, 10, 38, 17, 26, 17, 37, 52, 57, 18, 17, 50, 44, 6, 21, 29, 30, 43, 11, 11, 4, 36, 59, 24, 33, 8, 15, 13, 15, 13, 30, 37, 21, 14, 18, 49, 3, 40, 22, 18, 39, 44, 16, 36, 1, 20, 25, 35, 21, 10, 55, 28, 6, 13, 13, 18, 36, 28, 14, 32, 0, 11, 4, 14, 40, 30, 53, 7, 11, 14, 21, 9, 41, 29, 26, 13, 5, 2, 23, 7, 27, 16, 17, 8, 59, 18, 35, 7, 24, 36, 19, 8, 6, 37, 35, 41, 17, 15, 4, 5, 12, 19, 23, 2, 32, 2, 10, 8, 23, 4294967086, 23, 4294967087, 3, 1, 30, 4294967095, 13, 4294967072, 8, 21, 10, 4294967070, 16, 4294967069, 4294967078, 12, 4294967090, 4294967070, 4294967074, 4294967099, 12, 4294967062, 4294967076, 4294967074, 1, 4294967081, 4294967066, 5, 4294967079, 4294967056, 0, 4294967054, 4294967066, 4294967064, 4294967065, 4294967103]
"""

 如何由data[256]得到输入?这步没做出来。看wp(没找到wp..)

0x05 总结

 拖了好久,终于基本完成了。遇到了 powershell 混淆,rust 等,学习到了很多,仍要再接再厉!

留言

© 2024 wd-z711

⬆︎TOP