LLVM程序分析日记之getIntNTy()

LLVM IR中惯用的IntegerType主要是:

  • Int8Ty
  • Int16Ty
  • Int32Ty
  • Int64Ty

但是,今天注意到了一个有意思的API:

static IntegerType * 	getIntNTy (LLVMContext &C, unsigned N)

从而,我们可以定义任意长度的IntegerType,能够更加灵活地使用LLVM玩出花样。以下给出一个例子,在这个例子中,我们希望提取字符串的前N字节(N <= 8)。

Instrument Training

原始程序demo.c:

unsigned long long str2hex(char *buf) {
      return 0;
}
int main() {
      char buf[32];
      scanf("%s", buf);
      printf("Hex: %8llx\n", str2hex(buf));
      return 0;
}

我们的目标是插桩democ.c的str2hex()函数,使该函数返回char *buf的前N = 6个字节的Hex形式。

为了实现该目标,我们需要编写一个LLVM Pass,其中主要逻辑如下:

for (llvm::Function &F : M) {
      if (F.getName() != "str2hex") continue;
      IntegerType *IntNTy = IntegerType::getIntNTy(C, 48); // 6 * 8 = 48 bits
      IntegerType *Int64Ty = IntegerType::getInt64Ty(C);
      BasicBlock::iterator IP = F.getEntryBlock().getFirstInsertionPt();
      Value* arg = &(*F.arg_begin());
      IRBuilder<> IRB(&(*IP));
      Value *ptr = IRB.CreateBitCast(IRB.CreateGEP(arg, ConstantInt::get(Int64Ty, 0)), IntNTy->getPointerTo());
      Value *hex = IRB.CreateLoad(ptr);
      hex = IRB.CreateZExt(hex, Int64Ty);
      for (auto &I : *IP->getParent()) {
            if (dyn_cast<llvm::ReturnInst>(&I)) {
                  llvm::ReturnInst *new_inst = llvm::ReturnInst::Create(m->getContext(), hex);
                  ReplaceInstWithInst(&I, new_inst);
                  break;
            }
      }
}

插桩后的demo.ll如下:

define i64 @str2hex(i8*) #3 {
  %2 = getelementptr i8, i8* %0, i64 0
  %3 = bitcast i8* %2 to i48*
  %4 = load i48, i48* %3
  %5 = zext i48 %4 to i64
  %6 = alloca i8*, align 8
  store i8* %0, i8** %6, align 8
  ret i64 %5
}

运行lli demo_instrumented.bc,输入abcdefghijk,输出666564636261,成功!

IntNTy在x86上的实现方式

之所以Int8TyInt16TyInt32Ty更常用,是因为它们对应了ByteWordDword等类型,可以直接通过汇编指令表示。为了探究IntNTy,即任意长度的整型在x86汇编中的实现,我们将上一小节中的demo.c编译成可执行文件,并通过gdb反汇编来查看插桩代码的实现:

push   rbp
mov    rbp,rsp
mov    eax,DWORD PTR [rdi]
mov    ecx,eax
movzx  eax,WORD PTR [rdi+0x4]
mov    edx,eax
shl    rdx,0x20
or     rcx,rdx
mov    QWORD PTR [rbp-0x8],rdi
mov    rax,rcx
pop    rbp
ret

可以看到,对于6字节的IntNTy,汇编中首先读取一个4字节的Dword,接着读取一个2字节的Word,然后通过“移位”与“或”操作组合成一个6字节的值。

总结

显然,IntNty提供了更方便的方式,让我们可以在插桩时使用一个任意长度的整型值。但是,从汇编角度,当LLVM IR最终编译成汇编代码时,IntNTy仍然是基于ByteDword等类型实现,并且仍然需要引入额外的开销来进行组合。LLVM IR只不过是将基础类型“组合”的部分进行了封装,提供了便捷但并不会提升效率。

posted @ 2021-02-03 22:23  bjchan9an  阅读(392)  评论(0)    收藏  举报