2021 redhat simpleVM
GitHub - fghcvjk/2021redhat: 第四届“红帽杯”网络安全大赛 - 初赛
对于llvm pwn
需要三个组件 一个是opt 用于将一个对ir操作的规则so加载到一个lr or bc文件里。从而完成ir层面的处理。
而这里已经给了opt与so 我们要写的就是bc去利用这个opt。
1 | [*] '/home/squ/prac/pwn/opt-8' |
pass练习
假设我要找到一个函数的所有调用
- 拿到一个function
- 遍历所有BasicBlock
- 遍历BB中的所有instruction
- 根据instruction构造一个CallBase 也就是这个instruction是存在调用的(如果dyn_cast不出来就跳过 说明不是call)
- getNumOperands可以得到所有操作数 记住 这里和ghidra一样 第一个Operand是函数地址
1
%9 = call i32 (ptr, ...) @printf(ptr noundef @.str)
- 然后通过getArgOperand cast到ConstantInt再getZExtValue就能得到操作数的值 同样的 会有错误情况,比如是一个字符串不是数字 需要continue
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
using namespace llvm;
using std::cout;
using std::endl;
using std::string;
/* anonymous namespace. visible only to the current file. */
namespace {
struct Squ : public FunctionPass {
static char ID;
Squ() : FunctionPass(ID) {}
/* which overrides an abstract virtual method inherited from FunctionPass. */
bool runOnFunction(Function &F) override {
/* print the function name */
errs().write_escaped(F.getName()) << '\n';
/* BasicBlock iterator */
for(auto &BB_i : F){
/* Istruction iterator */
for(auto &I_i : BB_i){
Value *CalledFunction;
if(auto *CB = dyn_cast<CallBase>(&I_i)){
CalledFunction = CB->getCalledFunction();
StringRef Name = CalledFunction->getName();
unsigned int num = CB->getNumOperands();
errs() << "\tCall : " << Name << " with " \
<< num << " argus" << "\n";
for(auto i = 0; i < num - 1; i++){
ConstantInt *ci = dyn_cast<ConstantInt>(CB->getArgOperand(i));
// assert(ci != NULL);
if(ci == NULL) continue; // not a digital
errs() << "\t\t" << ci->getZExtValue();
}
errs() << "\n";
}
}
}
return false;
}
}; // end of struct Squ
} // end of anonymous namespace
/* LLVM uses ID’s address to identify a pass */
char Squ::ID = 0;
/* important here for cmd-line use */
static RegisterPass<Squ> X("squ", "squ Pass",
false /* Only looks at CFG */,
false /* Analysis Pass */);
static RegisterStandardPasses Y(
PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &Builder,
legacy::PassManagerBase &PM) { PM.add(new Squ()); });
分析
不过首先先分析他的IR规则 找到start 这就是ir对象的注册点
1 | int start() |
找到虚表(Pass类)
这个6830就是runOnFunction
1 | __int64 __fastcall sub_6830(__int64 a1, llvm::Value *a2) |
断点在Name后可以看到 llvm::Value::getName
获得的是函数名
1 | x/s $rax |
也就是我们函数名要为o0o0o0o0
才能进入
然后跟入 此处有明显的迭代器痕迹
1 | unsigned __int64 __fastcall trigger(__int64 a1, llvm::Function *a2) |
- llvm::CallBase::getCalledFunction : Returns the function called
对每个函数遍历后会在sub_6B80中对基本块遍历
从基本块中获取每一个指令 然后getOpcode (比如call store)
取出调用的函数名 比如add(1,2)
这里就取出add
1 | while ( 1 ) |
然后就是虚拟机程序了 根据调用的函数来进行虚拟机执行
- REG_x 分别是LOAD段上的两个地址
- 这里会先用 llvm::CallBase::getNumOperands 判断有几个参数
- llvm::CallBase::getArgOperand(v35, 0) 然后获得第一个参数(是类方法,所以rdi是自己的地址,第二个是参数的idx)
- llvm::dyn_cast对象调用llvm::ConstantInt::getZExtValue 就是是获得这个值的0拓展 GDB验证了一下
- 然后根据第一个参数是0是1对REG_x中存放的地址处写入别的REG中的内容剩下的分析也都大同小异
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25else if ( !strcmp(s1, "store") )
{
if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 )
{
v25 = llvm::CallBase::getArgOperand(v35, 0);
v24 = 0LL;
v23 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v25);
if ( v23 )
{
v22 = llvm::ConstantInt::getZExtValue(v23);
if ( v22 == 1 )
v24 = REG_1;
if ( v22 == 2 )
v24 = REG_2;
}
if ( v24 == REG_1 )
{
**(_QWORD **)REG_1 = *(_QWORD *)REG_2;
}
else if ( v24 == REG_2 )
{
**(_QWORD **)REG_2 = *(_QWORD *)REG_1;
}
}
} - reg1中给到free_got的值 (free就在后面结束被调用)
- free_got里的内容写给reg2
- reg2加上偏移得到one_gadget
- one_gadget store回free_got
由于我懒得换到小版本的libc了,所以就最后验证了一下写回
exp
1 | void store(int a); |
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Comment
ValineDisqus