前言
五一本来想出去玩的,但是人太多了,所以来研究一下llvm pass,不得不说,这玩意是真的方便,之前写壳花了大力气都在函数识别,全局变量识别,重定位 等等上了,而pass用来做各种代码层面的事情正好.所以我们来做一个pass.
准备环境
拉源码,准备cmake编译,这里我就不用ninja了,一般来说都推荐使用ninja,ninja确实不错.但是vs2019更适合我
git clone https://github.com/llvm/llvm-project.gitcd llvm-projectmkdir buildcd buildcmake -G "Visual Studio 16 2019" -A x64 ^ -DLLVM_ENABLE_PROJECTS="clang;lld" ^ -DCMAKE_BUILD_TYPE=Release ^ ../llvmcmake --build . --config Release --target clang
在build目录打开sln文件 直接编译即可.推荐使用debug+优化,debug版本不带优化会很卡(生成出来exe拿去编译东西非常占时间),release跟debug的区别是,release LLVM报错了,是不带错误符号的,只有debug带。
编写第一个pass
新建pass目录,写pass基本架构就不解释了,直接问GPT吧,三分钟的事情.我们直接进入正题,如何编写一个hello world:
我们的第一个pass,实现目的是 在任意编译出来的代码里面 弹出hello world。要实现这个目的,需要在bool runOnModule(Module& M) override这里面写
ModulePass特别适用于以下情况:需要同时分析或修改多个函数需要添加或删除模块中的函数需要修改或添加全局变量需要进行过程间(interprocedural)分析或优化需要处理模块级别的元数据或属性
为了实现第一个hello world,我们需要:
-
定义函数 -
找到入口点 -
插入代码
让我们一步一步来
定义函数
llvm中的定义函数非常无脑,只需要用FunctionCallee就行.如以下是定义一个messagebox的函数的代码:
FunctionCallee MsgBoxFunc = M.getOrInsertFunction( "MessageBoxA", FunctionType::get( IntegerType::getInt32Ty(Ctx), { Type::getInt8Ty(Ctx)->getPointerTo(), // hWnd Type::getInt8Ty(Ctx)->getPointerTo(), // lpText Type::getInt8Ty(Ctx)->getPointerTo(), // lpCaption IntegerType::getInt32Ty(Ctx) // uType }, false));
第一个是函数名字,第二个是函原型.hwnd iptext lpcaption都是指针.所以Type::getInt8Ty(Ctx)->getPointerTo() 这样就完成了一个函数定义。非常简单无脑,相比汇编手写
32位下,winapi需要设置stdcallmsgBoxFunc->setCallingConv(CallingConv::X86_StdCall);
对应的字符串,也只需要声明并且映射到全局变量里面即可
Constant* StrHello = ConstantDataArray::getString(Ctx, "Hello world!", true);Constant* StrTitle = ConstantDataArray::getString(Ctx, "Basic Error", true);GlobalVariable* HelloStr = new GlobalVariable( M, StrHello->getType(), true, GlobalValue::PrivateLinkage, StrHello, ".str.hello");GlobalVariable* TitleStr = new GlobalVariable( M, StrTitle->getType(), true, GlobalValue::PrivateLinkage, StrTitle, ".str.title");
是的,就这简单几步就完成了创建一个API和调用的全部过程。这就是LLVM的魅力
找到入口点
我们想让函数在入口点被执行,所以我们先找到函数入口点在哪,非常简单,llvm能遍历整个函数列表,找到名字为main或者wmain的,就是了
Function* Entry = nullptr; for (Function& F : M) { if (!F.isDeclaration() && (F.getName() == "main" || F.getName() == "wmain")) { Entry = &F; break; } } if (!Entry) { errs() << "No entry point (main) found.n"; return false; }
接着找到第一个block
BasicBlock& EntryBlock = Entry->getEntryBlock();
这部分就算完成了
BasicBlock(基本块)是LLVM IR中的一个基础构建单元,代表控制流图中的一个节点。它是编译器优化和代码生成的基本工作单位。具体可以参见我的IDA内部原理系列的第二篇文章
插入代码
llvm的强大之处是在于,能操控/创建任意的IR,以创建来说,只需要声明 IRBuilder 即可
IRBuilder<> IRB(&EntryBlock);
而”变量”在llvm里面是 value* 类型,我们刚刚声明的那些变量全部可以用CreatePointerCast表示:
// 得到 char* 类型指针到字符串Value* StrHelloPtr = IRB.CreatePointerCast( HelloStr, Type::getInt32Ty(Ctx)->getPointerTo());Value* StrTitlePtr = IRB.CreatePointerCast( TitleStr, Type::getInt32Ty(Ctx)->getPointerTo());Value* StrVmPtr = IRB.CreatePointerCast( GStrVmStr, Type::getInt32Ty(Ctx)->getPointerTo());// MessageBoxA 参数 (NULL, "Hello world!", "Info", MB_OK) => (i8* null,// i8* str1, i8* str2, i32 0)Type* Int8PtrTy = Type::getInt8Ty(Ctx)->getPointerTo();Value* NullPtr = ConstantPointerNull::get(cast<PointerType>(Int8PtrTy));Value* MB_OK = ConstantInt::get(Type::getInt32Ty(Ctx), 0); // MB_OK = 0
最后 构造参数,call 完事
// 构造参数std::vector<Value*> Args;Args.push_back(NullPtr);Args.push_back(StrHelloPtr);Args.push_back(StrTitlePtr);Args.push_back(MB_OK);IRB.CreateCall(MsgBoxFunc, Args);
是的 一个简单的hello world就完成了,非常的无脑.
安装自己编译的clang
我喜欢给VS的clang换成我的.具体步骤来说给vs安装clang把自己编译好的覆盖到这个地方
C:Program FilesMicrosoft Visual Studio2022CommunityVCToolsLlvmx64bin不一定是C盘,安装到其他盘就是其他目录
安装完后,给你的项目设置为llvm编译:
调试代码
但是在一切之前M我建议调试一下LLVM的IR因此,我们自己手动调用一下自己编译的clang生成一下ll文件看看啥情况
clang++ -S -emit-llvm test.cpp -o your_file.ll -mllvm
test.cpp 里面就是一个printf(“hello world”);
一切无误后,ll应该生成了,打开它:我们可以看到,我们声明的hello world已经在全局变量里面了而且可以看到,在main这里已经有一个messagebox 弹hello world了:
现在,我们来编译一下,随便用vs切CLANG编译一个项目可以看到,我们的hello world就出来了
那么 我们第一步也就完成了。
未完待续
1000阅读后,我们将继续更新第二章,讲解更高级一点的内容。
原文始发于微信公众号(sec0nd安全):免杀之LLVM PASS入门: hello world
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论