国赛见到了一个LLVM PASS pwn,直接麻爪,所以借此机会来学习一下这类题目
基础知识
(LLVM环境自己在网上找找教程安装一下吧,这里就不占用篇幅了)
既然要学习LLVM PASS类pwn,首先要知道什么是LLVM(以下内容来自百度):
LLVM是构架编译器的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间、链接时间、运行时间以及空闲时间,对开发者保持开放,并兼容已有脚本。
然后要知道LLVM PASS是什么:
pass是一种编译器开发的结构化技术,用于完成编译对象(如IR)的转换、分析或优化等功能。pass的执行就是编译器对编译对象进行转换、分析和优化的过程,pass构建了这些过程所需要的分析结果
这里来看一个图:
首先我们的源代码会被clang编译器编译成一种中间代码——IR,这个叫IR的东西非常重要,它连接这编译器的前端和后端,IR的设计很大程度体现着LLVM插件化、模块化的设计哲学,LLVM的各种pass其实都是作用在LLVM IR上的。同时IR也是一个编译器组件接口。通常情况下,设计一门新的编程语言只需要完成能够生成LLVM IR的编译器前端即可,然后就可以轻松使用LLVM的各种编译优化、JIT支持、目标代码生成等功能。
LLVM的IR有三种表示形式:
- 内存格式,只保存在内存中,人无法看到
- 不可读的IR,被称作bitcode,文件后缀为bc
- 可读的IR,介于高级语言和汇编代码之间,文件后缀为ll
大概就是说,LLVM提供了一种中间语言形式,以及编译链接这种语言的后端能力,那么对于一个新语言,只要开发者能够实现新语言到IR的编译器前端设计,就可以享受到从IR到可执行文件这之间的LLVM提供的所有优化、分析或者代码插桩的能力。而LLVM PASS就是去处理IR文件,通过opt利用写好的so库优化已有的IR,形成新的IR。而LLVM PASS类的pwn就是利用这一过程中可能会出现的漏洞。
简单示例
接下来为了进一步感受上述过程,我们来用官方提供的demo实现一下,首先是随便写一段代码:
#include <stdio.h>
#include <unistd.h>
int function1()
{
printf("fun1\n");
return 0;
}
int function2()
{
printf("fun1\n");
return 0;
}
int function3()
{
printf("fun1\n");
return 0;
}
int Ayaka()
{
printf("fun1\n");
return 0;
}
int main() {
char name[0x10];
read(0,name,0x10);
write(1,name,0x10);
printf("bye\n");
}
然后执行如下命令,将c文件编译成ll后缀的文件:
clang -emit-llvm -S main.c -o main.ll
main.ll文件内容如下:
; ModuleID = 'main.c'
source_filename = "main.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
@.str = private unnamed_addr constant [6 x i8] c"fun1\0A\00", align 1
@.str.1 = private unnamed_addr constant [5 x i8] c"bye\0A\00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define i32 @function1() #0 {
%1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0))
ret i32 0
}
declare i32 @printf(i8*, ...) #1
; Function Attrs: noinline nounwind optnone uwtable
define i32 @function2() #0 {
%1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0))
ret i32 0
}
; Function Attrs: noinline nounwind optnone uwtable
define i32 @function3() #0 {
%1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0))
ret i32 0
}
; Function Attrs: noinline nounwind optnone uwtable
define i32 @Ayaka() #0 {
%1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0))
ret i32 0
}
; Function Attrs: noinline nounwind optnone uwtable
define i32 @main() #0 {
%1 = alloca [16 x i8], align 16
%2 = getelementptr inbounds [16 x i8], [16 x i8]* %1, i32 0, i32 0
%3 = call i64 @read(i32 0, i8* %2, i64 16)
%4 = getelementptr inbounds [16 x i8], [16 x i8]* %1, i32 0, i32 0
%5 = call i64 @write(i32 1, i8* %4, i64 16)
%6 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.str.1, i32 0, i32 0))
ret i32 0
}
declare i64 @read(i32, i8*, i64) #1
declare i64 @write(i32, i8*, i64) #1
attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "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"="+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" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "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"="+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 6.0.0-1ubuntu2 (tags/RELEASE_600/final)"}
接下来我们用官方给的小demo写一个LLVM PASS出来:
#include "llvm/Pass.h"//写Pass所必须的库
#include "llvm/IR/Function.h"//操作函数所必须的库
#include "llvm/Support/raw_ostream.h"//打印输出所必须的库
#include "llvm/IR/LegacyPassManager.h"
#include