题解

中科大的比赛, 个人感觉难度挺高, 甚至很多知识技能闻所未闻.
网站链接

进制十六——参上

一个很典型的十六进制编辑器界面, 左边是十六进制, 右边是字符.

题图

直接对着左边去推右边的flag即可.

淦! 当时对着图抄都能抄错, 而且还抄错好几次, 少一行或者缺个字母. 当时一直觉得flag很奇怪[笑哭].

在这里推荐一个十六进制编辑器: HxD Hex Editor
这个感觉用起来比较舒服, 可以看16进制表示的整数, 浮点数或者是指令. 可以打开超大文件的同时, 也可以打开磁盘或者某程序的内存空间!
感觉比WinHex好用, 最重要的是这个不用收费.

去吧!追寻自由的电波

flag就在音频里, 但是音频被加速了, 完全听不出来.
用PotPlayer打开, 尝试减速, 结果… 声音频率没变, 还是听不出来.
那就用GoldWave看看吧, 当年剪一段音频的时候用的就是这个.

识别过程

开0.5倍, 整个音频很清晰. 所有单词除了leftbracket和rightbracket代表左右花括号外, 其他的都是首字母代表译文的一个字母.

听了一个学长的分享, 这个表达方式叫北约音标字母, 传送门

卖瓜

题目要求我们放x个6斤的瓜和y个9斤的瓜, 让称上的结果为20斤.
先尝试用小数, 发现小数部分会被截断. 负数也被提示无效输入, What’s up? 然后就把这题跳了.

几天后看这题通过人数, 发现还挺多的, 就再来试试.
输入1024819115206086201(因为它等于ceil(LONG_MAX, 9))看看有什么情况, 发现结果溢出了变成了负数.
查找相关资料, php的整数在过大时会自动转换为浮点数, 但是浮点数表示较大整数是有误差的, 而且数越大误差越大.
照这个思路, 我们想办法得到一个有误差的大负数, 然后加上一个正数, 就可能让结果为20.
然后开始一个一个试, 从1024819115206086201开始的好几百个数, 结果都是-9223372036854775808. 但是这个数表示我们要的, 因为20减去它并不是3的倍数.
直到输入1024819115206086600, 结果变成了-9223372036854771712, 这个行了, 然后填入合适的6斤的瓜.
$ 20+9223372036854771712=1537228672809128622 \times 6 $.

值得注意的是, 这两个数不能同时填入. 得先填9斤的让结果溢出, 再填上6斤的把数加回来.

透明的文件

打开文件发现是这么个格式

1
[0;0H[20;58H[8;34H[13;27H[4;2H[38;2;1;204;177m [39m[14;10H[20;51H[23;4H[12;2H[38;2;2;207;173m 

DNA动了! 高三学C时在手机上写了几个控制台小游戏, 清楚地记得这种奇怪的格式.(传送门)
意思就是说, \033(或者\x1e)+左方括号+一堆数字和分隔符+某个字母可以产生一些特别的效果.
比如清屏, 光标移动, 字符颜色什么的(甚至unistd.h里面一些宏都是拿printf这么干的).
观察一下这个文件, 发现每个左中括号前面的控制字符都不见了, 那就记事本打开把[全替换成\033[, 再把所有空格改成能看见的字符, 空格替换为#.
把这个丢到printf里作为格式串, 在linux或者android里面都可以跑出来, 在cmd里面是看不出结果的.

但是, 在cmd中真的不能看到结果吗?

在Windows中有一个API SetConsoleMode, 这个API可以设置控制台的某些属性, 我之前写过一个小工具让控制台能够接受鼠标的点击, 它的核心点也是这个.
下面是让cmd成功显示flag的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

#include <windows.h>

static BOOL enableCtl(){
BOOL ret = FALSE;
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdOut != INVALID_HANDLE_VALUE){
DWORD mode;
if (GetConsoleMode(hStdOut, &mode)){
mode |= ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
ret = SetConsoleMode(hStdOut, mode);
}
}
return ret;
}

int main(){
enableCtl();
printf("格式化好的字符串, 太长了不贴了, 源码在下面");
return 0;
}
源码

命令行: gcc test.cpp -o test & cls & test & pause > nul

运行结果(我习惯白底的cmd, 然后就是这样):
flag

Windows yyds!!!

中间省略一万字

别问, 问就是不想写[傲娇]
其实是不会写

Amnesia #1

写一个Hello world, 但会把你的.data段和.rodata段清除.

.data段存放程序数据, 一般是全局数组之类的; .rodata段存放只读的数据, 像字符串就在此处. 所以你的程序不能出现字符串, 除非换一个段, 比如下面的程序能跑:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

__attribute__((section(".fake")))
static const char msg[] = "Hello, world!";

int main(){
puts(msg);
return 0;
}

但这种方法是很不稳定的. 假如你使用的是printf, 编译器会把你带换行符的msg一并优化为puts(msg2);, 其中msg2是不带换行符的msg. 这样, .fake段就是msg, .rodata段就是msg2, 而优化后的程序使用的是msg2, 所以还是会被清掉.

不使用数据段的话可以这样:

1
2
3
4
5
6
7
#include <stdio.h>

int main(){
char s[] = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n'};
for (int i = 0; i < sizeof (s); i++) putchar(s[i]);
return 0;
}

Micro World

用Spy++查看窗口类名, 是pygame, 确定这是python写的. 像这种比较大的程序一般都是脚本打包, 而不是C++写的(Qt什么的就另说).
pyinstxtractor解包并修复后, 得到pyc文件. 使用uncompyle6反编译, 成功输出py脚本.
然后运行发现是黑框框, 什么都没有???

然后就看源码吧, 把一些不合逻辑的代码改一改, 最后再把速度取反, 就能反向运行啦

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
#game.pyw
import time, pygame, random, math
WIDTH = 800
HEIGHT = 480
FPS = 30
RADIUS = 6
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Micro world')
clock = pygame.time.Clock()
running = True
count = 0
list_ = [数据太长不贴了, 源码在下面]

class Point:

def __init__(self, pos, vx, vy):
self.x, self.y = pos
self.vx = vx
self.vy = vy
self.flag = 1


def dotproduct(v1, v2):
return v1[0] * v2[0] + v1[1] * v2[1]


def checkcrush(point1, point2):
distance = math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2)
a = (point1.vx - point2.vx) ** 2 + (point1.vy - point2.vy) ** 2
b = 2 * (point1.x - point2.x) * (point1.vx - point2.vx) + 2 * (point1.y - point2.y) * (point1.vy - point2.vy)
c = distance ** 2 - 4 * RADIUS ** 2
delta = b ** 2 - 4 * a * c
if delta < 0:
return
time = (-b - math.sqrt(delta)) / (2 * a)
if time > 0:
if time < 1:
return time
return


def get_new_point(time, point1, point2):
distance = math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2)
standard_displacement = ((point2.x - point1.x) / distance, (point2.y - point1.y) / distance)
v_1 = (point1.vx, point1.vy)
v_2 = (point2.vx, point2.vy)
v_par_1 = dotproduct(standard_displacement, v_1)
v_par_2 = dotproduct(standard_displacement, v_2)
v_ver_1 = (point1.vx - v_par_1 * standard_displacement[0], point1.vy - v_par_1 * standard_displacement[1])
v_ver_2 = (point2.vx - v_par_2 * standard_displacement[0], point2.vy - v_par_2 * standard_displacement[1])
v_after_par_1 = v_par_2
v_after_par_2 = v_par_1
v_after_1 = (v_after_par_1 * standard_displacement[0] + v_ver_1[0], v_after_par_1 * standard_displacement[1] + v_ver_1[1])
v_after_2 = (v_after_par_2 * standard_displacement[0] + v_ver_2[0], v_after_par_2 * standard_displacement[1] + v_ver_2[1])
afterpos_1 = (point1.x + point1.vx * time + v_after_1[0] * (1 - time), point1.y + point1.vy * time + v_after_1[1] * (1 - time))
afterpos_2 = (point2.x + point2.vx * time + v_after_2[0] * (1 - time), point2.y + point2.vy * time + v_after_2[1] * (1 - time))
return (Point(afterpos_1, v_after_1[0], v_after_1[1]), Point(afterpos_2, v_after_2[0], v_after_2[1]))


def drawpoint(screen, list_):
for item in list_:
pygame.draw.circle(screen, BLUE, (round(item.x), round(item.y)), RADIUS, 3)


def next_pos_list(Pointlist):
pointlist = []
for i in range(len(Pointlist)):
for point in Pointlist[i + 1:]:
times = checkcrush(Pointlist[i], point)
if times != None:
a, b = get_new_point(times, Pointlist[i], point)
pointlist.extend([a, b])
Pointlist[i].flag = 0
point.flag = 0
for item in Pointlist:
if item.flag != 0:
pointlist.append(Point((item.x + item.vx, item.y + item.vy), item.vx, item.vy))
for poi in pointlist:
poi.x = poi.x % WIDTH
poi.y = poi.y % HEIGHT
return pointlist


Pointlist = []
for item in list_.__reversed__():
Pointlist.append(Point((item[0], item[1]), -item[2], -item[3]))
else:

def value(lis):
count = 0
for item in lis:
count = count + (item.x - round(item.x)) ** 2 + (item.y - round(item.y)) ** 2
else:
return count

while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(BLACK)
drawpoint(screen, Pointlist)
Pointlist = next_pos_list(Pointlist)
pygame.display.flip()

pygame.quit()

源码

程序运行的结果如下, 不知道为啥只有鼠标放在窗口上面才会运转, 不过这样更好, 容易看出 flag
microworld

uncompyle真jier坑[生气]

minecRaft

打开游戏, 没玩懂, 那就直接F12吧, 看到下面的:
资源
保存一下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
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
const _0x22517d = _0x2c9e; (function(_0x2018e5, _0xd122c5) {
const _0x4a600d = _0x2c9e,
_0x2e34d2 = _0x2018e5();
while ( !! []) {
try {
const _0x4d38c4 = -parseInt(_0x4a600d(0x1b1)) / 0x1 * (parseInt(_0x4a600d(0x1ad)) / 0x2) + -parseInt(_0x4a600d(0x1b2)) / 0x3 * (parseInt(_0x4a600d(0x1b6)) / 0x4) + -parseInt(_0x4a600d(0x1ae)) / 0x5 * ( - parseInt(_0x4a600d(0x1b4)) / 0x6) + parseInt(_0x4a600d(0x1ab)) / 0x7 * (parseInt(_0x4a600d(0x1af)) / 0x8) + parseInt(_0x4a600d(0x1b5)) / 0x9 + -parseInt(_0x4a600d(0x1b3)) / 0xa + -parseInt(_0x4a600d(0x1a9)) / 0xb * ( - parseInt(_0x4a600d(0x1a7)) / 0xc);
if (_0x4d38c4 === _0xd122c5) break;
else _0x2e34d2['push'](_0x2e34d2['shift']());
} catch(_0x416145) {
_0x2e34d2['push'](_0x2e34d2['shift']());
}
}
} (_0x381b, 0x21c08), String['prototype'][_0x22517d(0x1a8)] = function(_0x6a2659) {
const _0x13519e = _0x22517d,
_0x267e12 = new Array(0x2),
_0x11a961 = new Array(0x4);
let _0x1bf548 = '';
plaintext = escape(this);
for (var _0x485827 = 0x0; _0x485827 < 0x4; _0x485827++) _0x11a961[_0x485827] = Str4ToLong(_0x6a2659[_0x13519e(0x1a6)](_0x485827 * 0x4, (_0x485827 + 0x1) * 0x4));
for (_0x485827 = 0x0; _0x485827 < plaintext[_0x13519e(0x1b8)]; _0x485827 += 0x8) {
_0x267e12[0x0] = Str4ToLong(plaintext['slice'](_0x485827, _0x485827 + 0x4)),
_0x267e12[0x1] = Str4ToLong(plaintext[_0x13519e(0x1a6)](_0x485827 + 0x4, _0x485827 + 0x8)),
code(_0x267e12, _0x11a961),
_0x1bf548 += LongToBase16(_0x267e12[0x0]) + LongToBase16(_0x267e12[0x1]);
}
return _0x1bf548;
});
function _0x2c9e(_0x49e6ff, _0x310d40) {
const _0x381b4c = _0x381b();
return _0x2c9e = function(_0x2c9ec6, _0x2ec3bd) {
_0x2c9ec6 = _0x2c9ec6 - 0x1a6;
let _0x4769df = _0x381b4c[_0x2c9ec6];
return _0x4769df;
},
_0x2c9e(_0x49e6ff, _0x310d40);
}
function code(_0x167a71, _0x762113) {
let _0x412874 = _0x167a71[0x0],
_0x3f9c14 = _0x167a71[0x1];
const _0x540f95 = (0x52cfb2de + 0x4b67c6db),
_0x2bdc23 = _0x540f95 * 0x20;
let _0x4f8e47 = 0x0;
while (_0x4f8e47 != _0x2bdc23) {
_0x412874 += (_0x3f9c14 << 0x4 ^ _0x3f9c14 >>> 0x5) + _0x3f9c14 ^ _0x4f8e47 + _0x762113[_0x4f8e47 & 0x3],
_0x4f8e47 += _0x540f95,
_0x3f9c14 += (_0x412874 << 0x4 ^ _0x412874 >>> 0x5) + _0x412874 ^ _0x4f8e47 + _0x762113[_0x4f8e47 >>> 0xb & 0x3];
}
_0x167a71[0x0] = _0x412874,
_0x167a71[0x1] = _0x3f9c14;
}
function Str4ToLong(_0x288936) {
const _0xf57f33 = _0x22517d;
let _0x283da9 = 0x0;
for (let _0x1bfa1a = 0x0; _0x1bfa1a < 0x4; _0x1bfa1a++) _0x283da9 |= _0x288936[_0xf57f33(0x1ac)](_0x1bfa1a) << _0x1bfa1a * 0x8;
return isNaN(_0x283da9) ? 0x0: _0x283da9;
}
function LongToBase16(_0xad4470) {
let _0x4176bf = '';
for (let _0x3c7880 = 0x3; _0x3c7880 >= 0x0; _0x3c7880--) {
let _0x43811c = (_0xad4470 >> 0x8 * _0x3c7880 & 0xff)['toString'](0x10);
if (parseInt('0x' + _0x43811c) <= 0xf) _0x43811c = '0' + _0x43811c;
_0x4176bf += _0x43811c;
}
return _0x4176bf;
}
function Base16ToLong(_0x203413) {
const _0x27c0c4 = _0x22517d;
let _0x48728d = 0x0;
for (let _0x239fca = 0x0; _0x239fca < 0x8; _0x239fca += 0x2) {
let _0x24e56c = parseInt('0x' + _0x203413[_0x27c0c4(0x1a6)](_0x239fca, _0x239fca + 0x2));
_0x48728d = (_0x48728d << 0x8) + _0x24e56c;
}
return _0x48728d;
}
function _0x381b() {
const _0x4af9ee = ['encrypt', '33MGcQht', '6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c', '14021KbbewD', 'charCodeAt', '808heYYJt', '5DlyrGX', '552oZzIQH', 'fromCharCode', '356IjESGA', '784713mdLTBv', '2529060PvKScd', '805548mjjthm', '844848vFCypf', '4bIkkcJ', '1356853149054377', 'length', 'slice', '1720848ZSQDkr'];
_0x381b = function() {
return _0x4af9ee;
};
return _0x381b();
}
function LongToStr4(_0x2f2e9e) {
const _0x416d95 = _0x22517d,
_0x106afc = String[_0x416d95(0x1b0)](_0x2f2e9e & 0xff, _0x2f2e9e >> 0x8 & 0xff, _0x2f2e9e >> 0x10 & 0xff, _0x2f2e9e >> 0x18 & 0xff);
return _0x106afc;
}
function gyflagh(_0x111955) {
const _0x50051f = _0x22517d;
let _0x3b790d = _0x111955[_0x50051f(0x1a8)](_0x50051f(0x1b7));
if (_0x3b790d === _0x50051f(0x1aa)) return !! [];
return ! [];
}

这个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
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
String.prototype.encrypt = function(s) {
const l = new Array(0x2),
v = new Array(0x4);
let r = '';
plaintext = escape(this);
for (var i = 0x0; i < 0x4; i++) v[i] = Str4ToLong(s.slice(i * 0x4, (i + 0x1) * 0x4));
for (i = 0; i < plaintext.length; i += 8) {
l[0] = Str4ToLong(plaintext.slice(i, i + 0x4)),
l[1] = Str4ToLong(plaintext.slice(i + 0x4, i + 0x8)),
code(l, v),
r += LongToBase16(l[0x0]) + LongToBase16(l[0x1]);
}
return r;
};
String.prototype.decrypt = function(s) {
const l = new Array(0x2),
v = new Array(0x4);
let r = '';
plaintext = escape(this);
for (var i = 0x0; i < 0x4; i++) v[i] = Str4ToLong(s.slice(i * 0x4, (i + 0x1) * 0x4));
for (i = 0; i < plaintext.length; i += 16) {
l[0] = Base16ToLong(plaintext.slice(i, i + 8)),
l[1] = Base16ToLong(plaintext.slice(i + 8, i + 16)),
decode(l, v),
r += LongToStr4(l[0x0]) + LongToStr4(l[0x1]);
}
return r;
};
function code(l, v) {
let l0 = l[0x0],
l1 = l[0x1];
const _0x540f95 = 0x9E3779B9,
_0x2bdc23 = _0x540f95 * 0x20;
let i = 0x0;
while (i != _0x2bdc23) {
l0 += (l1 << 0x4 ^ l1 >>> 0x5) + l1 ^ i + v[i & 0x3],
i += _0x540f95,
l1 += (l0 << 0x4 ^ l0 >>> 0x5) + l0 ^ i + v[i >>> 0xb & 0x3];
}
l[0x0] = l0,
l[0x1] = l1;
}
function decode(l, v) {
let l0 = l[0x0],
l1 = l[0x1];
const _0x540f95 = 0x9E3779B9,
_0x2bdc23 = _0x540f95 * 0x20;
let i = _0x2bdc23;
while (i != 0) {
l1 -= (l0 << 0x4 ^ l0 >>> 0x5) + l0 ^ i + v[i >>> 0xb & 0x3];
i -= _0x540f95;
l0 -= (l1 << 0x4 ^ l1 >>> 0x5) + l1 ^ i + v[i & 0x3];
}
l[0x0] = l0,
l[0x1] = l1;
}
function Str4ToLong(s) {
let r = 0;
for (let i = 0; i < 4; i++) r |= s.charCodeAt(i) << i * 0x8;
return isNaN(r) ? 0: r;
}
function LongToBase16(l) {
let s = '';
for (let i = 0x3; i >= 0x0; i--) {
let _0x43811c = (l >> 0x8 * i & 0xff)['toString'](0x10);
if (parseInt('0x' + _0x43811c) <= 0xf) _0x43811c = '0' + _0x43811c;
s += _0x43811c;
}
return s;
}
function Base16ToLong(_0x203413) {
let _0x48728d = 0x0;
for (let _0x239fca = 0x0; _0x239fca < 0x8; _0x239fca += 0x2) {
let _0x24e56c = parseInt('0x' + _0x203413.slice(_0x239fca, _0x239fca + 0x2));
_0x48728d = (_0x48728d << 0x8) + _0x24e56c;
}
return _0x48728d;
}
function LongToStr4(_0x2f2e9e) {
const _0x106afc = String.fromCharCode(_0x2f2e9e & 0xff, _0x2f2e9e >> 0x8 & 0xff, _0x2f2e9e >> 0x10 & 0xff, _0x2f2e9e >> 0x18 & 0xff);
return _0x106afc;
}
function gyflagh(flag) {
let e = flag.encrypt('1356853149054377');
if (e === '6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c') return !! [];
return ! [];
}
var e = '6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c'.decrypt('1356853149054377');
console.log(e);
console.log(gyflagh(e));

然后结果包上flag{}就行.

超 OI 的 Writeup 模拟器 #1

这个我是人工反编译的, 不会写脚本, 后面十五小题就没看了.
快进到关键函数, 发现这个函数被扁平化了:
流程图

然后分析了很久, 最后还是坐不住换了个方法. 由于i与输入参数无关, 那我们就在每个分支打断点, 然后跑一次查看流程, 结果是1跑了十一次, 2跑了一次:
代码

然后写解密代码:

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
N = 0x10000000000000000

def exgcd(a, b):
if b == 0: return a, 1, 0
d, x, y = exgcd(b, a % b)
return d, y, x - y * int(a / b)

def solve(a, b, n):
_, x, _ = exgcd(a, n)
return x * b % n

def backward(a, b):
return solve((0x21DBA181B3901BAE - a * 0x58C5EC8BD3B2A4AA) + 1, b, N), a

def toascii(n):
s = ''
for _ in range(8):
s = s + chr(n & 0xFF)
n = n >> 8
return s

a = solve(0xD71E1226CB47657F, 0xCB8D3677A9C1A3B2, N)
b = solve(0xC5E13B34BC5473E3, 0xA9C10ABDFFAB71F6, N)
for i in range(11):
a, b = backward(a, b)
print(toascii(a) + toascii(b))
# RrMAb3N4zXNYfOyT

外面包上flag{}即可.

密码生成器

题目意思大概是让我们从时间反向推出密码生成器的密码, 注意这里有个魔鬼细节: “差了八个小时”.
先不管啦, 直接分析网站上给出的密码生成器.
IDA打开, 发现很多代码. spy++看窗口类名, 呃好像是Qt写的, 感觉就不会了(立一个反向flag哈哈)
程序一般生成的是伪随机数, 也就是根据时间, CPU温度什么的用一系列算法生成随机数.
先找了下导入表中有没有srand函数和rand函数, 只有一个结果, 但打开看发现那个不是我们想要的.
再找一下time, 有一个_time64, 点开看发现很可疑. 在这里下个断点, 运行程序发现一切正常, 点生成密码按钮命中断点.
那应该就是这里了. 另外找一下ABC…XYZ这个字符串, 有十几个, 但到Z结束的只有前两个, 正是我们想要的, 旁边的特殊字符表和数字表也找到了:
字母表
然后开始看有time64的那个函数(为了阅读方便一些符号已经重命名而且添加了注释):

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
// local variable allocation has failed, the output may be wrong!
// positive sp value has been detected, the output may be wrong!
void **__fastcall gen_rand(void *a1, void *a2)
{
__int64 v2; // rdx
void **v3; // rcx
void **v4; // rbp
__int64 v5; // rsi
bool v6; // zf
DWORD v7; // eax
__int64 v8; // rdx
DWORD v9; // ecx
int v10; // ecx
int v11; // er12
__int64 idx; // rcx
unsigned int v13; // edx
__int64 table_1; // rax
bool v15; // cc
wchar_t *v16; // rax
wchar_t v17; // r14
float rand; // xmm0_4
float v19; // xmm6_4
float v20; // xmm0_4
int len; // edx
int pos; // eax
bool v23; // al
void *v24; // rcx
signed __int32 v25; // et0
wchar_t **v26; // rcx
signed __int32 v27; // et0
wchar_t **table; // [rsp+28h] [rbp-13F0h] BYREF
DWORD ptable[624]; // [rsp+30h] [rbp-13E8h] BYREF
DWORD v31[624]; // [rsp+30h] [rbp-13E8h] FORCED BYREF
__int64 v32; // [rsp+9F0h] [rbp-A28h]
float v33; // [rsp+A00h] [rbp-A18h] OVERLAPPED
float v34; // [rsp+A04h] [rbp-A14h] 0
DWORD randobj[640]; // [rsp+A08h] [rbp-A10h] BYREF

((void (__fastcall *)(void *, void *))sub_446270)(a1, a2);
v4 = v3;
v5 = v2;
if ( *(int *)(v2 + 32) <= 0 )
{
*v3 = (void *)sub_B21B40(byte_F58069, 0x1Bu);
return v4;
}
v6 = *(_BYTE *)(v2 + 36) == 0;
table = (wchar_t **)dword_11E9580; // 字母表
if ( !v6 )
str_append((void **)&table, (__int64 *)v2);
if ( *(_BYTE *)(v5 + 37) )
str_append((void **)&table, (__int64 *)(v5 + 8));
if ( *(_BYTE *)(v5 + 38) )
str_append((void **)&table, (__int64 *)(v5 + 16));
if ( *(_BYTE *)(v5 + 39) )
str_append((void **)&table, (__int64 *)(v5 + 24));
v7 = time64(0i64);
v8 = 1i64;
v9 = v7;
ptable[0] = v7;
while ( 1 )
{
ptable[v8] = v8 + 1812433253 * (v9 ^ (v9 >> 30));// 624次?
if ( ++v8 == 624 )
break;
v9 = ptable[v8 - 1];
}
v32 = 624i64;
*(_QWORD *)&v33 = 0x3F80000000000000i64; // float(1.0), 0
memcpy(randobj, ptable, 0x9C8ui64); // copy all randoms
while ( 1 )
{
v10 = *(_DWORD *)(v5 + 32);
*(_QWORD *)v31 = dword_11E9580;
if ( v10 > 0 )
{
v11 = 0;
while ( 1 )
{
v17 = 0;
rand = get_rand(randobj);
v19 = (float)(rand * (float)(v34 - v33)) + v33;// v19=1-rand
v20 = get_rand(randobj);
len = *((_DWORD *)table + 1);
pos = (int)(float)((float)((float)((float)(v20 * (float)(v34 - v33)) + v33) * v19) * (float)len);
if ( pos < len )
v17 = *(_WORD *)((char *)table + 2 * pos + (_QWORD)table[2]);
table_1 = *(_QWORD *)v31;
if ( **(_DWORD **)v31 > 1u )
break; // fail
idx = *(int *)(*(_QWORD *)v31 + 4i64);
v13 = idx + 2;
if ( (int)idx + 2 > (*(_DWORD *)(*(_QWORD *)v31 + 8i64) & 0x7FFFFFFFu) )
goto LABEL_17;
gen: // char generation loop
++v11;
*(_DWORD *)(table_1 + 4) = idx + 1;
v15 = *(_DWORD *)(v5 + 32) <= v11;
v16 = (wchar_t *)(*(_QWORD *)(table_1 + 16) + table_1 + 2 * idx);
*v16 = v17; // the charactor
v16[1] = 0;
if ( v15 )
goto LABEL_23;
}
v13 = *(_DWORD *)(*(_QWORD *)v31 + 4i64) + 2;
LABEL_17:
sub_B20AD0((void **)v31, v13, 1);
table_1 = *(_QWORD *)v31;
idx = *(int *)(*(_QWORD *)v31 + 4i64);
goto gen;
}
LABEL_23:
v23 = sub_BC91C0(v5, (__int64 *)v31);
v24 = *(void **)v31;
if ( v23 )
break;
if ( **(_DWORD **)v31 )
{
if ( **(_DWORD **)v31 == -1 )
continue;
v25 = _InterlockedSub(*(volatile signed __int32 **)v31, 1u);
v24 = *(void **)v31;
if ( v25 )
continue;
}
sub_64A030(v24);
} // gen end
*v4 = *(void **)v31;
*(_QWORD *)v31 = dword_11E9580;
v26 = table;
if ( !*(_DWORD *)table
|| *(_DWORD *)table != -1 && (v27 = _InterlockedSub((volatile signed __int32 *)table, 1u), v26 = table, !v27) )
{
sub_64A030(v26);
}
return v4;
}

个人感觉C++的程序比C的难读很多, 一开始没什么头绪, 然后就放了几天.
周四把这个函数理了一下, 抱着尝试的态度去网上找了下算法中出现的1812433253这个数字.
然后恍然大悟, 这是梅森旋转算法, (C库里那个rand是线性同余算法, 和这个一样都属于不安全的随机数生成算法)
程序中的大小为624的数组印证了我的猜想.
然后就硬看, 生成过程大概是:

  • 检查4个复选框, 把4个字母表拼成一个长字符串, 在这题中4种字符都要有.
  • 初始化梅森旋转算法
  • 生成密码(单个字符c = rand() * rand() * len(alphatable), 所以取到字母表的前面的字母概率较大)
  • 如果密码没有同时包含4种字符, 全部推倒重来

最后一条规则看代码没看出来. 运行程序把4种类型都选上, 长度改成3(构造不可能事件). 结果程序一直在get_rand, 大胆猜测推倒重来这个规则, 结果验证后发现这是对的. 我把它叫做假说-演绎法(
写解密算法就OK:

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
import time
import requests

lst = []
idx = 0

def srand(t):
global idx
idx = 0
lst.clear()
for i in range(624):
lst.append(t)
t = (1812433253 * (t ^ (t >> 30)) + i + 1) & 0xFFFFFFFF

def rand():
def step():
for i in range(624):
y = (lst[i] & 0x80000000) + (lst[(i + 1) % 624] & 0x7FFFFFFF)
lst[i] = lst[(i + 397) % 624] ^ (y >> 1)
if (y & 1) != 0: lst[i] = lst[i] ^ 2567483615
global idx
if idx == 0: step()
y = lst[idx]
y = y ^ (y >> 11)
y = y ^ ((y << 7) & 2636928640)
y = y ^ ((y << 15) & 4022730752)
y = y ^ (y >> 18)
idx = (idx + 1) % 624
return y / 0x100000000

def gen(time):
if time != -1: srand(time)
def truerand():
x = rand()
y = rand()
return int(x * y * 94)
alpha = '''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890~!@#$%^&*()_+`-=[]\\{}|;':\",./<>?'''
stat = [3 for i in range(94)]
for i in range(26):
stat[i] = 0
stat[i + 26] = 1
for i in range(10):
stat[i + 52] = 2
r, vt = '', [0, 0, 0, 0]
for i in range(16):
x = truerand()
y = stat[x]
vt[y] = vt[y] + 1
r = r + alpha[x]
if vt[0] == 0 or vt[1] == 0 or vt[2] == 0 or vt[3] == 0: r = gen(-1)
return r

csrf = '4uU4gCiPKItbRLPCOwNZcRhYzknHuWJHklxxQv5LohsRmjwpcyHGzGrhnQeDCSdd'
sid = 'eyJ0b2tlbiI6IjY4OTpNRVFDSUdhNkFIQmFqS1ZqZ0cyUTJpeWhtSkpUVi9hT0dPYzArV28zL1VReVpWbjFBaUFRd0k1b0R0dWdiSGQ4U1ZTTEFDeTgrTXEvZmZvS1AwMlZXQ0lDbkp1M3dBPT0ifQ:1mg5t5:2-_5RzMfjBfLBtAP1B21xcxzNuY3mka0XBvViLoB_hM'
head = '<input type="hidden" name="csrfmiddlewaretoken" value="'
def test(k):
cookie = {'csrftoken': csrf, 'sessionid': sid}
r = requests.get('http://202.38.93.111:15002/login', cookies=cookie)
p = r.text.find(head) + len(head)
q = r.text.find('"', p)
csrf_mid = r.text[p:q]
r = requests.post('http://202.38.93.111:15002/login', cookies=cookie, data={'csrfmiddlewaretoken': csrf_mid, 'username': 'admin', 'password': k})
return r.text.find('用户名或密码错误') == -1

t = int(time.mktime(time.strptime('2021-09-22 23:12', '%Y-%m-%d %H:%M')))
dt = 0
d = 8 * 3600
while True:
print(dt)
k = gen(t - dt + d)
print(str(t - dt + d) + ' -> ' + k + ' ... ', end='')
if test(k):
print('ok')
break
print('failed')
k = gen(t - dt)
print(str(t - dt) + ' -> ' + k + ' ... ', end='')
if test(k):
print('ok')
break
print('failed')
k = gen(t - dt - d)
print(str(t - dt - d) + ' -> ' + k + ' ... ', end='')
if test(k):
print('ok')
break
print('failed')
dt = dt + 1

可能还是技术不足, 之前不知道session这个东西, 就拿csrf去充当参数. 不过, 问题不大
脚本在dt为六十几的时候命中密码, 当时超级兴奋!!!

但是看了官方WP的做法后人傻了, 原来就我在逆向生成算法555

更多

星期八再说(