免杀之LLVM PASS入门: hello world

admin 2025年5月6日16:19:40评论1 views字数 3858阅读12分51秒阅读模式

前言

五一本来想出去玩的,但是人太多了,所以来研究一下llvm pass,不得不说,这玩意是真的方便,之前写壳花了大力气都在函数识别,全局变量识别,重定位 等等上了,而pass用来做各种代码层面的事情正好.所以我们来做一个pass.

免杀之LLVM PASS入门: hello world

准备环境

拉源码,准备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带。免杀之LLVM PASS入门: hello world

编写第一个pass

新建pass目录,写pass基本架构就不解释了,直接问GPT吧,三分钟的事情.我们直接进入正题,如何编写一个hello world:

我们的第一个pass,实现目的是 在任意编译出来的代码里面 弹出hello world。要实现这个目的,需要在bool runOnModule(Module& M) override这里面写

ModulePass特别适用于以下情况:需要同时分析或修改多个函数需要添加或删除模块中的函数需要修改或添加全局变量需要进行过程间(interprocedural)分析或优化需要处理模块级别的元数据或属性

为了实现第一个hello world,我们需要:

  1. 定义函数
  2. 找到入口点
  3. 插入代码

让我们一步一步来

定义函数

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免杀之LLVM PASS入门: hello world把自己编译好的覆盖到这个地方

C:Program FilesMicrosoft Visual Studio2022CommunityVCToolsLlvmx64bin不一定是C盘,安装到其他盘就是其他目录
免杀之LLVM PASS入门: hello world

安装完后,给你的项目设置为llvm编译:免杀之LLVM PASS入门: hello world

调试代码

但是在一切之前M我建议调试一下LLVM的IR因此,我们自己手动调用一下自己编译的clang生成一下ll文件看看啥情况

clang++ -S -emit-llvm test.cpp -o your_file.ll -mllvm

test.cpp 里面就是一个printf(“hello world”);

一切无误后,ll应该生成了,打开它:我们可以看到,我们声明的hello world已经在全局变量里面了免杀之LLVM PASS入门: hello world而且可以看到,在main这里已经有一个messagebox 弹hello world了:免杀之LLVM PASS入门: hello world现在,我们来编译一下,随便用vs切CLANG编译一个项目可以看到,我们的hello world就出来了免杀之LLVM PASS入门: hello world那么 我们第一步也就完成了。

未完待续

1000阅读后,我们将继续更新第二章,讲解更高级一点的内容。

原文始发于微信公众号(sec0nd安全):免杀之LLVM PASS入门: hello world

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月6日16:19:40
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   免杀之LLVM PASS入门: hello worldhttps://cn-sec.com/archives/4033854.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息