首页
社区
课程
招聘
[原创]llvm学习笔记——llvm基础
发表于: 2023-11-22 09:14 9196

[原创]llvm学习笔记——llvm基础

2023-11-22 09:14
9196

此部分主要涉及基本概念、环境搭建及LLVM IR的基本指令

llvm官网(点击)

定义:代码混淆是将计算机程序的代码,转换成一种功能上等价,但是难以阅读和理解的形式的行为。

image-20230505204155595

代码执行由顺序图转为分发器控制,使难以分清原来程序的逻辑。

对于软件开发者:一定程度上防止代码被逆向破解

对于逆向工程师:帮助我们研究反混淆技术

image-20230506145141902

首先应安装c++环境,安装cmake。
安装C++
安装cmake

https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.1

下载 LLVM-Core 和 Clang 源代码:

在 /home/llvm/Programs 文件夹内创建 llvm-project 文件夹,存放我们刚刚下载的源码压缩包。

将两个压缩包解压之后改名为 llvm 和 clang ,方便后续使用。

在同一文件夹内创建名为 build 的文件夹,存放编译后的LLVM

此时的目录结构如下(笔者的ubuntu为中文,用户文件夹即为home):

image-20230508005613030

在sh文件内写入如下命令

cmake 参数解释:

—G “Unix Makefiles”:生成Unix下的Makefile

—DLLVM_ENABLE_PROJECTS=“clang”:除了 LLVM Core 外,还需要编译的子项目。

—DLLVM_BUILD_TYPE=Release:在cmake里,有四种编译模式:Debug, Release,RelWithDebInfo和MinSizeRel。使用 Release 模式编译会节省很多空间。

—DLLVM_TARGETS_TO_BUILD=“X86”:默认是ALL,选择X86可节约很多编译时间。

—DBUILD_SHARED_LIBS=On:指定动态链接 LLVM 的库,可以节省空间。

make install 指令是将编译好的二进制文件和头文件等安装到本机的 /usr/local/bin和 /usr/local/include 目录,方便后续使用。

执行 build.sh 文件自动安装和编译,编译时长从十多分钟到数小时,具体时间由机器性能决定。

image-20230613002255183

输入 clang -v确认编译和安装是否完成

image-20230613120735663

image-20230506145141902

代码混淆是基于优化器进行的。

由前端Clang将源代码转化为LLVM IR 代码

LLVM IR主要有两种表现形式:

将源代码转化为以上两种LLVM IR代码的命令分别如下:

可阅读文本

二进制

以上两种后缀的文件后续均可以被优化和被编译成可执行文件

使用opt指令对LLVM IR进行优化,opt为optimizer即优化器的缩写

calng将如上两步整合为了一步,省去了链接器的过程(直接生成了可执行文件)

通过clang完成生成可执行文件的过程。

基于 LLVM Pass框架进行编写。

优化器 opt 和 LLVM Pass 框架是我们后续实现代码混淆的基础。

image-20230613135809578

LLVM Pass框架功能非常强大,一些代码优化工具和fuzz(AFL)工具都是基于llvm pass框架进行开发

LLVM Pass 框架是整个 LLVM 提供给用户用来干预代码优化过程的框架,也是我们编写代码混淆工具的基础。

编译后的 LLVM Pass 通过优化器 opt 进行加载,可以对 LLVM IR 中间代码进行分析和修改,生成新的中间代码。

image-20230613112852652

image-20230613135839402

存放了 LLVM 提供的一些公共头文件,这些头文件是开发过程中要用到的。

image-20230613114448635

存放了 LLVM 大部分源代码(.cpp 文件)和一些不公开的头文件,阅读这些源代码可以深入了解llvm的运行原理

image-20230613135432113

存放所有 LLVM Pass 的源代码。
也存放了一些 LLVM 自带的 Pass。

自己编写的Pass也可以放入该文件夹。

image-20230613140257080

以 "Hello world" 为例。
目标:编写一个 LLVM Pass,遍历程序中的所有函数,并输出 “Hello, ”+ 函数名。

image-20230613140320741

CMake是一种项目管理工具,可以对各种LLVM Pass进行编写

LLVM Pass 支持三种编译方式:

本文重点学习FunctionPass,基于函数的Pass

1).创建一个类(class),继承 FunctionPass 父类

2).在创建的类中实现 runOnFunction(Function &F) 函数。

3).向 LLVM 注册我们的 Pass 类。

选择基于CMake的编译,直接使用CMake进行编译即可。

编译好的.so文件在build文件夹内。

使用优化器 opt 将处理中间代码,生成新的中间代码:

-load 加载编译好的 LLVM Pass(.so文件)进行优化

首先在虚拟机的llvm文件夹创建OLLVM++文件夹

在此创建如下文件夹及文件

Build 文件夹:存放编译后 LLVM Pass

Test 文件夹:存放测试程序 TestProgram.cpp

Test/TestProgram.cpp:一个简单的 CTF 逆向题

Transforms/include 文件夹:存放整个 LLVM Pass 项目的头文件,暂时还没有用到

Transforms/src 文件夹:存放整个 LLVM Pass 项目的源代码

Transforms/src/HelloWorld.cpp:HelloWorld Pass 的源代码,一般来说一个 Pass 使用一个 cpp 文件

实现即可。

Transforms/CMakeLists.txt:整个 CMake 项目的配置文件。

image-20230613162450704

各文件内容分别如下:

TestProgram.cpp 将要被编译的源代码

CMakeLists.txt

具体操作在bool Demo::runOnFunction(Function &F){}中实现即可。

Hello.cpp为我们要编写的LLVM Pass代码

模板如下

关于此次实验代码 HelloWorld.cpp如下

test.sh:编译 LLVM Pass 并对 Test 文件夹中的代码进行测试(复制时把我的注释去掉)

在vscode控制台输入

然后运行

笔者在此处遇到一个问题,第一次编译并不会在Build文件夹下产生LLVMObfuscator.so文件,需要进入Transforms文件夹手动进行复制到Build文件夹进行第二次编译。

image-20230614214141654

实现了对样本程序中的每个函数进行扫描,并输出了"Hello,"+函数名

https://github.com/ttroy50/cmake-examples

LLVM编译过程回顾:

image-20230614220618223

image-20230614230726941

LLVM IR 共有两种表示方法:

通过LLVM自带的工具,该两种表示形式也可以互相转换

image-20230615112601095

image-20230615112651678

源代码被LLVM IR编译后,有如下结构

image-20230615112800526

image-20230615113027831

image-20230615113052721

image-20230615113148285

LLVM IR和IDA反汇编的结构比较相似。

image-20230615113931786

基于 LLVM 的混淆,通常是以函数或者比函数更小的单位为基本单位进行混淆的,我们通常更关心函数和基本块这两个结构。

image-20230615114034371

总共有六大类指令

基本块的最后一个指令,通常为跳转指令或返回指令。
跳转到下一个基本快进行执行或从当前执行的函数中返回。

return

函数的返回指令,对应 C/C++ 中的 return。

分为有返回值的ret指令和无返回值的ret指令。

image-20230615140616180

实例

image-20230615140846279

branch

跳转指令,分为条件分支非条件分支

image-20230615141016368

实例

image-20230615141030979

比较指令,通常与br指令一起出现

整数或指针的比较指令。

image-20230615142606230

实例

image-20230615142624401

浮点数的比较指令。

image-20230615142930178

实例

image-20230615142943973

分支指令,可看做是 br 指令的升级版

image-20230615143630522

实例

image-20230615144436670

整数加法指令

image-20230615144625767

实例

image-20230615144648673

整数减法指令

image-20230615144718653

实例

image-20230615144733080

整数乘法指令

image-20230615144855502

实例

image-20230615150722237

无符号整数除法指令

image-20230615152823994

加入exact时,需要精确保证op1是op2的倍数,指令才会正常执行

实例

image-20230615153305650

有符号整数除法指令

实例

image-20230615153506036

无符号整数取余指令

image-20230615153603497

实例

image-20230615153742092

有符号整数取余指令

image-20230615153948039

实例

image-20230615154003932

逻辑移位统一当成无符号数

算数右移按照原来符号数来决定正负

整数左移指令

image-20230615154732580

实例

image-20230615154800064

整数逻辑右移指令

image-20230615155111674

实例

image-20230615155143817

整数算术右移指令

image-20230615162337831

实例

image-20230615162351917

逻辑移位:无符号右移
算数移位:带符号右移

整数按位与运算指令

image-20230615162721472

实例

image-20230615162757274

整数按位或运算指令

image-20230615162821343

实例

image-20230615162837791

整数按位异或运算指令

image-20230615162925421

实例

image-20230615162951675

实例

image-20230615192133207

image-20230615192158006

引例:

image-20230615193015602

image-20230615193033070

内存分配指令

image-20230615201323778

实例

image-20230615201348653

内存存储指令

image-20230615202610517

实例

image-20230615202650713

内存读取指令

image-20230615202923884

image-20230615203041438

截断指令

image-20230615203341011

实例

image-20230615203353006

零拓展(Zero Extend)指令

将一种类型的变量拓展为另一种类型的变量,高位补0。对应 C/C++ 中小类型向大类型的强制转换(比如 int 强转 long)

image-20230615204346862

实例

image-20230615204359349

符号位拓展(Sign Extend)指令

通过复制符号位(最高位)将一种类型的变量拓展为另一种类型的变量。

实例

image-20230615204448143

不太好分类,又比较常见的指令

image-20230615204702573

通过引入Φ函数来解决这个问题,Φ函数的值由前驱块决定,这里的Φ函数对应 LLVM IR 中的 phi 指令

image-20230615204803023

phi 指令可以看做是为了解决 SSA 一个变量只能被赋值一次而引起的问题衍生出的指令。

phi 指令的计算结果由 phi 指令所在的基本块的 前驱块 确定

以下是一个用 phi 指令实现for循环的实例

image-20230615205106878

image-20230615205206571

实例

image-20230615205227447

image-20230615205250731

实例

image-20230615205303138

image-20230702113538997

image-20230702113732000

image-20230702113810314

for循环的实现方法

看雪 《LLVM与代码混淆技术》

sudo vim build.sh
sudo vim build.sh
cd build
cmake -G "Unix Makefiles" -DLLVM_ENABLE_PROJECTS="clang" \
-DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="X86" \
-DBUILD_SHARED_LIBS=On ../llvm
make
make install
cd build
cmake -G "Unix Makefiles" -DLLVM_ENABLE_PROJECTS="clang" \
-DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="X86" \
-DBUILD_SHARED_LIBS=On ../llvm
make
make install
sudo ./build.sh
sudo ./build.sh
clang -v
clang -v
clang -S -emit-llvm 文件名.cpp -o 文件名.ll
clang -S -emit-llvm 文件名.cpp -o 文件名.ll
clang -c -emit-llvm 文件名.cpp -o 文件名.bc
clang -c -emit-llvm 文件名.cpp -o 文件名.bc
opt -load LLVMObfuscator.so -hlw -S 文件名.ll -o 文件名_opt.ll
opt -load LLVMObfuscator.so -hlw -S 文件名.ll -o 文件名_opt.ll
clnag 文件名_opt.ll -o 文件名
clnag 文件名_opt.ll -o 文件名
优化器属于llvm-core的子项目
优化器属于llvm-core的子项目
opt -load ./LLVMObfuscator.so -hlw -S hello.ll -o hello_opt.ll
opt -load ./LLVMObfuscator.so -hlw -S hello.ll -o hello_opt.ll
#include <cstdio>
#include <cstring>
char input[100] = {0};
char enc[100] = "\x86\x8a\x7d\x87\x93\x8b\x4d\x81\x80\x8a\
\x43\x7f\x49\x49\x86\x71\x7f\x62\x53\x69\x28\x9d";
void encrypt(unsigned char *dest, char *src){
    int len = strlen(src);
    for(int i = 0;i < len;i ++){
        dest[i] = (src[i] + (32 - i)) ^ i;
    }
}
//flag{s1mpl3_11vm_d3m0}
int main(){
    printf("Please input your flag: ");
    scanf("%s", input);
    unsigned char dest[100] = {0};
    encrypt(dest, input);
    bool result = strlen(input) == 22 && !memcmp(dest, enc, 22);
    if(result){
        printf("Congratulations~\n");
    }else{
    printf("Sorry try again.\n");
    }
}
#include <cstdio>
#include <cstring>
char input[100] = {0};
char enc[100] = "\x86\x8a\x7d\x87\x93\x8b\x4d\x81\x80\x8a\
\x43\x7f\x49\x49\x86\x71\x7f\x62\x53\x69\x28\x9d";
void encrypt(unsigned char *dest, char *src){
    int len = strlen(src);
    for(int i = 0;i < len;i ++){
        dest[i] = (src[i] + (32 - i)) ^ i;
    }
}
//flag{s1mpl3_11vm_d3m0}
int main(){
    printf("Please input your flag: ");
    scanf("%s", input);
    unsigned char dest[100] = {0};
    encrypt(dest, input);
    bool result = strlen(input) == 22 && !memcmp(dest, enc, 22);
    if(result){
        printf("Congratulations~\n");
    }else{

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 8
支持
分享
最新回复 (4)
雪    币: 2340
活跃值: (10417)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-11-22 09:19
0
雪    币: 3059
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-11-22 09:30
1
雪    币: 185
活跃值: (2397)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
2023-11-22 09:48
0
雪    币: 129
活跃值: (2763)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2023-11-24 21:26
0
游客
登录 | 注册 方可回帖
返回
//