I don't undarstand LLVM
about
なんかよくわからないが,LLVMという凄いコンパイラ基盤があるらしい.
あと,なんかよくわからないがPassというのが最適化とかしてるらしい.
そして,なんかよくわからないけど難読化すると嬉しい人がいるらしい.
さらに,なんかそのPassでも難読化ができそうな気がする.
なので,LLVMを完全に理解しないまま,難読化Passを実装してみる.
あくまで自分用の試行メモなので,雑な内容なのは勘弁してほしい.
リポジトリはここで,一応各step毎にcommitを分けている.
Passとその実装
書こうかと思ったが,いつものFPGA開発日記にめちゃくちゃいい記事が既にあったので,よくわからないのが嫌な人はこっちを読んでください.
今回はその記事で紹介されているものとは違って,ターゲットに依存したtarget passを書いていきます.(それについても良い記事があります.)
なぜならIRよりアセンブリに近い形式のほうが読み慣れているから.
(本当はアーキテクチャを選ばずに適用できるような上のレイヤーのPassを書くほうが良いが)
難読化
なんか色々難読化手法があるらしいが,その中でも定数や演算を置き換える手法があるらしい.
定数難読化
encode literals
難読化のコンテキストでの「定数」とは,何も数値に限った話ではなく,ソースコード中に現れる静的な文字列なども含まれる.
例えば,暗号化に用いる鍵がraw textでプログラムに含まれる時,解析への耐性が強いとは言えない.
そこで,難読化ツールを用いて直書きされた鍵を難読化する事で,耐解析性を向上させようというモチベーションがある.
tigressなどの難読化コンパイラにも実装されている.
しかし,ここでは簡単のため(めんどくさいので)数値だけを難読化する.
実装
「なにもしない」をする
とにかくまずは何もしないパスを実装してみる.
既存のTargetPass等を参考に,それっぽい形を作る.
実装していくのは関数ごとに適用されるMachineFunctionPass
.
実際のPassの実装は,X86OptimizeLEAPassを参考にする.
このPassは,名前の通りX86でのlea
を最適化するパスで,実装が比較的シンプルなので読みやすい.
適当にそれっぽい名前(llvm/lib/Target/X86/X86EncodeLiterals.cpp
)のファイルを作成し,テンプレっぽいコードを書く
#include "MCTargetDesc/X86BaseInfo.h"
#include "X86.h"
#include "X86InstrInfo.h"
#include "X86Subtarget.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetOpcodes.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/DebugLoc.h"
#include "llvm/IR/Function.h"
#include "llvm/MC/MCInstrDesc.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cstdint>
using namespace llvm;
#define DEBUG_TYPE "X86-encode-literals"
static cl::opt<bool> EnableEncodeLiterals("enable-encode-literals",
cl::desc("X86: Encode Literals"),
cl::init(false));
namespace {
class X86EncodeLiteralsPass : public MachineFunctionPass {
public:
X86EncodeLiteralsPass() : MachineFunctionPass(ID) {}
StringRef getPassName() const override { return "X86 Encode Literals"; }
/// Loop over all of the basic blocks, encoding literals.
bool runOnMachineFunction(MachineFunction &MF) override;
static char ID;
private:
MachineRegisterInfo *MRI = nullptr;
const X86InstrInfo *TII = nullptr;
const X86RegisterInfo *TRI = nullptr;
};
} // end anonymous namespace
char X86EncodeLiteralsPass::ID = 0;
FunctionPass *llvm::createX86EncodeLiterals() {
return new X86EncodeLiteralsPass();
}
INITIALIZE_PASS(X86EncodeLiteralsPass, DEBUG_TYPE, "X86 Encode Literals pass",
false, false)
bool X86EncodeLiteralsPass::runOnMachineFunction(MachineFunction &MF) {
bool Changed = false;
if (!EnableEncodeLiterals)
return false;
errs() << MF.getFunction().getName() << '\n';
return Changed;
}
ビルドの為にllvm/lib/Target/X86/CMakeLists.txt
にも追記
~~ snip ~~
X86InsertWait.cpp
X86EncodeLiterals.cpp
)
add_llvm_target(X86CodeGen ${sources}
~~ snip ~~
llvm/lib/Target/X86/X86.h
で必要な関数を定義
~~ snip ~~
FunctionPass *createX86SpeculativeLoadHardeningPass();
FunctionPass *createX86SpeculativeExecutionSideEffectSuppression();
/// Return a pass that encode literals
FunctionPass *createX86EncodeLiterals();
void initializeX86EncodeLiteralsPassPass(PassRegistry &);
void initializeEvexToVexInstPassPass(PassRegistry &);
void initializeFixupBWInstPassPass(PassRegistry &);
void initializeFixupLEAPassPass(PassRegistry &);
~~ snip ~~
実際のPassの登録はllvm/lib/Target/X86/X86TargetMachine.cpp
で行う.
段階毎にメソッドが別れているので,それっぽい部分で追加する.今回はaddPreEmitPass
void X86PassConfig::addPreEmitPass() {
if (getOptLevel() != CodeGenOpt::None) {
addPass(new X86ExecutionDomainFix());
addPass(createBreakFalseDeps());
}
addPass(createX86IndirectBranchTrackingPass());
addPass(createX86IssueVZeroUpperPass());
if (getOptLevel() != CodeGenOpt::None) {
addPass(createX86FixupBWInsts());
addPass(createX86PadShortFunctions());
addPass(createX86FixupLEAs());
}
addPass(createX86EvexToVexInsts());
addPass(createX86DiscriminateMemOpsPass());
addPass(createX86InsertPrefetchPass());
addPass(createX86InsertX87waitPass());
// Encode Literals
addPass(createX86EncodeLiterals());
}
これでPassの定義,登録,有効化ができた.
詳細な説明は先程の記事を参考にしてもらい,重要な部分だけ拾っていく.
runOnMachineFunction
は名前の通りMachineFunction
毎に適用される関数で,MachineFunction
を受取り,操作を行う.
今の所は関数の名前を出力するだけのコードになっている.
runOnMachineFunction
の戻り値は,関数に対して「変更を加えたかどうか」.
定数を取ってくる
さて,まずは書き換えるために定数を取得したい.
とりあえず受け取ったMachineFunction
の中の命令列を見たいので,dump
してみる.
for (auto &MBB : MF) {
for (auto &MI : MBB) {
errs() << MI;
}
}
こんな感じで実装して,以下のようなコードをclangに食わせてやると,
#include <stdio.h>
int main() {
char h = 'h';
char e = 'e';
char l = 'l';
char o = 'o';
char hello[] = {h, e, l, l, o, 0};
puts(hello);
}
$ ./clang -mllvm -enable-encode-literals=true test.c
frame-setup PUSH64r killed $rbp, implicit-def $rsp, implicit $rsp
CFI_INSTRUCTION def_cfa_offset 16
CFI_INSTRUCTION offset $rbp, -16
$rbp = frame-setup MOV64rr $rsp
CFI_INSTRUCTION def_cfa_register $rbp
$rsp = frame-setup SUB64ri8 $rsp(tied-def 0), 16, implicit-def dead $eflags
MOV8mi $rbp, 1, $noreg, -1, $noreg, 104 :: (store (s8) into %ir.1)
MOV8mi $rbp, 1, $noreg, -2, $noreg, 101 :: (store (s8) into %ir.2)
MOV8mi $rbp, 1, $noreg, -3, $noreg, 108 :: (store (s8) into %ir.3)
MOV8mi $rbp, 1, $noreg, -4, $noreg, 111 :: (store (s8) into %ir.4)
renamable $al = MOV8rm $rbp, 1, $noreg, -1, $noreg :: (load (s8) from %ir.1)
MOV8mr $rbp, 1, $noreg, -9, $noreg, killed renamable $al :: (store (s8) into %ir.6)
renamable $al = MOV8rm $rbp, 1, $noreg, -2, $noreg :: (load (s8) from %ir.2)
MOV8mr $rbp, 1, $noreg, -8, $noreg, killed renamable $al :: (store (s8) into %ir.8)
renamable $al = MOV8rm $rbp, 1, $noreg, -3, $noreg :: (load (s8) from %ir.3)
MOV8mr $rbp, 1, $noreg, -7, $noreg, killed renamable $al :: (store (s8) into %ir.10)
renamable $al = MOV8rm $rbp, 1, $noreg, -3, $noreg :: (load (s8) from %ir.3)
MOV8mr $rbp, 1, $noreg, -6, $noreg, killed renamable $al :: (store (s8) into %ir.12)
renamable $al = MOV8rm $rbp, 1, $noreg, -4, $noreg :: (load (s8) from %ir.4)
MOV8mr $rbp, 1, $noreg, -5, $noreg, killed renamable $al :: (store (s8) into %ir.14)
renamable $rdi = LEA64r $rbp, 1, $noreg, -9, $noreg
CALL64pcrel32 @puts, <regmask $bh $bl $bp $bph $bpl $bx $ebp $ebx $hbp $hbx $rbp $rbx $r12 $r13 $r14 $r15 $r12b $r13b $r14b $r15b $r12bh $r13bh $r14bh $r15bh $r12d $r13d $r14d $r15d $r12w $r13w $r14w $r15w $r12wh and 3 more...>, implicit $rsp, implicit $ssp, implicit killed $rdi, implicit-def $eax
renamable $eax = XOR32rr undef $eax(tied-def 0), undef $eax, implicit-def dead $eflags
$rsp = frame-destroy ADD64ri8 $rsp(tied-def 0), 16, implicit-def dead $eflags
$rbp = frame-destroy POP64r implicit-def $rsp, implicit $rsp
CFI_INSTRUCTION def_cfa $rsp, 8
RETQ implicit killed $eax
こんな感じで,なんとなく読めそうな感じの文字列が出力される.
addPreEmitPass
って名前なだけあって,emit
直前の形式なのでアセンブリに近いような気がする.
バイナリをradare2を使って読んでみると,
[0x00201050]> pdf @main
; DATA XREF from entry0 @ 0x201068
┌ 71: int main (int argc, char **argv, char **envp);
│ ; var char *s @ rbp-0x9
│ ; var int64_t var_8h @ rbp-0x8
│ ; var int64_t var_7h @ rbp-0x7
│ ; var int64_t var_6h @ rbp-0x6
│ ; var int64_t var_5h @ rbp-0x5
│ ; var int64_t var_4h @ rbp-0x4
│ ; var int64_t var_3h @ rbp-0x3
│ ; var int64_t var_2h @ rbp-0x2
│ ; var int64_t var_1h @ rbp-0x1
│ 0x00201140 55 push rbp
│ 0x00201141 4889e5 mov rbp, rsp
│ 0x00201144 4883ec10 sub rsp, 0x10
│ 0x00201148 c645ff68 mov byte [var_1h], 0x68 ; 'h' ; 104
│ 0x0020114c c645fe65 mov byte [var_2h], 0x65 ; 'e' ; 101
│ 0x00201150 c645fd6c mov byte [var_3h], 0x6c ; 'l' ; 108
│ 0x00201154 c645fc6f mov byte [var_4h], 0x6f ; 'o' ; 111
│ 0x00201158 8a45ff mov al, byte [var_1h]
│ 0x0020115b 8845f7 mov byte [s], al
│ 0x0020115e 8a45fe mov al, byte [var_2h]
│ 0x00201161 8845f8 mov byte [var_8h], al
│ 0x00201164 8a45fd mov al, byte [var_3h]
│ 0x00201167 8845f9 mov byte [var_7h], al
│ 0x0020116a 8a45fd mov al, byte [var_3h]
│ 0x0020116d 8845fa mov byte [var_6h], al
│ 0x00201170 8a45fc mov al, byte [var_4h]
│ 0x00201173 8845fb mov byte [var_5h], al
│ 0x00201176 488d7df7 lea rdi, [s] ; const char *s
│ 0x0020117a e891feffff call sym.imp.puts ; int puts(const char *s)
│ 0x0020117f 31c0 xor eax, eax
│ 0x00201181 4883c410 add rsp, 0x10
│ 0x00201185 5d pop rbp
└ 0x00201186 c3 ret
こんな感じで,変数に代入されている文字が普通にわかってしまう.
今回難読化すると嬉しそうなコードは
MOV8mi $rbp, 1, $noreg, -1, $noreg, 104 :: (store (s8) into %ir.1)
MOV8mi $rbp, 1, $noreg, -2, $noreg, 101 :: (store (s8) into %ir.2)
MOV8mi $rbp, 1, $noreg, -3, $noreg, 108 :: (store (s8) into %ir.3)
MOV8mi $rbp, 1, $noreg, -4, $noreg, 111 :: (store (s8) into %ir.4)
っぽい.MOV8mi
は「8bitのimmediateをmemoryにMOVする」のでは?と推測できるし,104は'h'
だし,offsetっぽい部分はrbp
とか-1
とかのローカル変数の領域を指していそうだし.
というわけで,まずはMOV(8|16|32|64)mi
を探してみる.
MachineInstr
の定義からそれっぽいメソッド(getOpcode
)を使ってオペコードを取得し,目的の物だった時だけ出力してみる.
オペコードは主にTableGenで定義されており,llvm/lib/Target/X86/X86InstrInfo.td
にある
unsigned Opcode = MI.getOpcode();
if (Opcode == X86::MOV8mi || Opcode == X86::MOV16mi || Opcode == X86::MOV32mi || Opcode == X86::MOV64mi32) {
errs() << MI;
}
こんな感じのコードを書くと,想定どおり取得できる
$ ./clang -mllvm -enable-encode-literals=true test.c
MOV8mi $rbp, 1, $noreg, -1, $noreg, 104 :: (store (s8) into %ir.1)
MOV8mi $rbp, 1, $noreg, -2, $noreg, 101 :: (store (s8) into %ir.2)
MOV8mi $rbp, 1, $noreg, -3, $noreg, 108 :: (store (s8) into %ir.3)
MOV8mi $rbp, 1, $noreg, -4, $noreg, 111 :: (store (s8) into %ir.4)
とりあえず関数に切り分けておく
private:
MachineRegisterInfo *MRI = nullptr;
const X86InstrInfo *TII = nullptr;
const X86RegisterInfo *TRI = nullptr;
bool isStoreLocalValue(MachineInstr &);
};
~~ snip ~~
bool X86EncodeLiteralsPass::isStoreLocalValue(MachineInstr &MI) {
unsigned Opcde = MI.getOpcode();
return Opcode == X86::MOV8mi || Opcode == X86::MOV16mi ||
Opcode == X86::MOV32mi || Opcode == X86::MOV64mi32;
}
さて,それっぽい命令を取ってくることに成功したので,次はそのオペランドである定数を取ってくる.
これまたそれっぽい名前のgetOperand
があるので,試してみる.
getNumOperands
でオペランドの数を取得して,とりあえず全部dumpしてみる.
for (auto &MBB : MF) {
for (auto &MI : MBB) {
if (isStoreLocalValue(MI)) {
errs() << "-----\n" << MI;
for (unsigned i = 0; i < MI.getNumOperands(); i++) {
errs() << MI.getOperand(i) << '\n';
}
}
}
}
$ ./clang -mllvm -enable-encode-literals=true test.c
-----
MOV8mi $rbp, 1, $noreg, -1, $noreg, 104 :: (store (s8) into %ir.1)
$rbp
1
$noreg
-1
$noreg
104
-----
MOV8mi $rbp, 1, $noreg, -2, $noreg, 101 :: (store (s8) into %ir.2)
$rbp
1
$noreg
-2
$noreg
101
-----
MOV8mi $rbp, 1, $noreg, -3, $noreg, 108 :: (store (s8) into %ir.3)
$rbp
1
$noreg
-3
$noreg
108
-----
MOV8mi $rbp, 1, $noreg, -4, $noreg, 111 :: (store (s8) into %ir.4)
$rbp
1
$noreg
-4
$noreg
111
いい感じに取れていそう.取得したい定数は最後にありそうなので,indexを指定して取ってくる.
for (auto &MBB : MF) {
for (auto &MI : MBB) {
if (isStoreLocalValue(MI)) {
MachineOperand &Lit = MI.getOperand(MI.getNumOperands()-1);
errs() << Lit << "\n";
}
}
}
$ ./clang -mllvm -enable-encode-literals=true test.c
104
101
108
111
これも関数に切り分けておく.
~~ snip ~~
bool isStoreLocalValue(MachineInstr &);
MachineOperand &getLiteralOperand(MachineInstr &);
};
} // end anonymous namespace
~~ snip ~~
MachineOperand &X86EncodeLiteralsPass::getLiteralOperand(MachineInstr &MI) {
MachineOperand &Lit = MI.getOperand(MI.getNumOperands() - 1);
assert(Lit.isImm());
return Lit;
}
MachineOperand
には,MachineOperandType
も定義されているので,Immediate
かどうかの確認もしておく(MOVmi
なのでimmの筈だが,念の為).
難読化する
本題の難読化.
定数を数式に変換することで難読化する.
Pythonで書くとこんな感じ
rnd = secrets.randbits(64)
tmp = Imm ^ rnd
Imm == rnd ^ tmp
こんな感じで,もとの定数をrandomな値(rnd, tmp)で表す.
とりあえずフラグレジスタの事は考えずに実装してみる.
ここでは,乱数の安全性は重要ではないのでstd::mt19937_64
を用いて疑似乱数を生成する.
手順としては,
-
即値mov命令のオペランドを書き換える
mov
のオペランドをrnd
とし(mov byte ptr [rbp - offset], rnd
),xor byte ptr [rbp - offset], tmp
とする事で直接アセンブリに定数が出てこなくなる. -
mov先の値をxorする
の作業を該当する命令毎に行う.
runOnMachineFunction
はこうなる
bool X86EncodeLiteralsPass::runOnMachineFunction(MachineFunction &MF) {
bool Changed = false;
MRI = &MF.getRegInfo();
TII = MF.getSubtarget<X86Subtarget>().getInstrInfo();
TRI = MF.getSubtarget<X86Subtarget>().getRegisterInfo();
if (!EnableEncodeLiterals)
return false;
for (auto &MBB : MF) {
unsigned Index = 0;
for (MachineBasicBlock::iterator MI = MBB.begin(), E = MBB.end(); MI != E;
++MI, ++Index) {
if (isStoreLocalValue(*MI)) {
obfuscateLiteralOperand(MF, *MI, Index);
Changed = true;
}
}
}
return Changed;
}
for文でIndex
を使用しているのは,必要な位置に命令XOR
を新しく挿入するため.
実際にリテラルを難読化する関数はこれ
void X86EncodeLiteralsPass::obfuscateLiteralOperand(MachineFunction &MF,
MachineInstr &MI,
unsigned Index) {
unsigned LitOffset = MI.getNumOperands() - 1;
MachineOperand &Lit = getLiteralOperand(MI, LitOffset);
std::random_device seed_gen;
std::mt19937_64 mt(seed_gen());
uint64_t Rnd = mt();
uint64_t Imm = Lit.getImm();
// replace literal operand by rnd
MI.RemoveOperand(LitOffset);
MachineOperand OpRnd = MachineOperand::CreateImm(Rnd);
MI.addOperand(OpRnd);
buildXor(MI, Index, Imm ^ Rnd);
}
まずは難読化に使用するランダムな値を作成
std::random_device seed_gen;
std::mt19937_64 mt(seed_gen());
uint64_t Rnd = mt();
uint64_t Imm = Lit.getImm();
し,オペランドを作成した値で置き換える
// replace literal operand by rnd
MI.RemoveOperand(LitOffset);
MachineOperand OpRnd = MachineOperand::CreateImm(Rnd);
MI.addOperand(OpRnd);
次に,新たにXORを挿入する(buildXor
).
inline void X86EncodeLiteralsPass::buildXor(MachineInstr &MI, unsigned Index,
uint64_t Operand) {
MachineBasicBlock *PMBB = MI.getParent();
MachineBasicBlock::instr_iterator Iter = skip(PMBB, Index);
int64_t Offset = MI.getOperand(3).getImm();
unsigned XOROpc = getXorOpc(MI);
addRegOffset(BuildMI(*PMBB, Iter, MI.getDebugLoc(), TII->get(XOROpc)),
X86::RBP, true, Offset)
.addImm(Operand);
}
mov
のオペランドのサイズ毎に適切なXORを選び(getXorOpc
),MBB
をIndex
の位置まで移動させたIterator
を使って命令を挿入する.
BuildMI
というのがinclude/llvm/CodeGen/MachineInstrBuilder.h
で定義されている命令を挿入する関数なんだが,これがかなり難解で,思った位置に挿入できたりできなかったりするので今回はIteratorを使用している.
実際にサンプルのコードをコンパイルしてみると
$ cat test.c
#include <stdio.h>
int main() {
char h = 'h';
char e = 'e';
char l = 'l';
char o = 'o';
char hello[] = {h, e, l, l, o, 0};
puts(hello);
}
$ ./clang -mllvm --enable-encode-literals test.c
$ ./a.out
hello
$ objdump --disassemble=main -j .text a.out
a.out: ファイル形式 elf64-x86-64
セクション .text の逆アセンブル:
0000000000201140 <main>:
201140: 55 push rbp
201141: 48 89 e5 mov rbp,rsp
201144: 48 83 ec 10 sub rsp,0x10
201148: c6 45 ff 4e mov BYTE PTR [rbp-0x1],0x4e
20114c: 80 75 ff 26 xor BYTE PTR [rbp-0x1],0x26
201150: c6 45 fe 4e mov BYTE PTR [rbp-0x2],0x4e
201154: 80 75 fe 2b xor BYTE PTR [rbp-0x2],0x2b
201158: c6 45 fd 4e mov BYTE PTR [rbp-0x3],0x4e
20115c: 80 75 fd 22 xor BYTE PTR [rbp-0x3],0x22
201160: c6 45 fc 4e mov BYTE PTR [rbp-0x4],0x4e
201164: 80 75 fc 21 xor BYTE PTR [rbp-0x4],0x21
201168: 8a 45 ff mov al,BYTE PTR [rbp-0x1]
20116b: 88 45 f6 mov BYTE PTR [rbp-0xa],al
20116e: 8a 45 fe mov al,BYTE PTR [rbp-0x2]
201171: 88 45 f7 mov BYTE PTR [rbp-0x9],al
201174: 8a 45 fd mov al,BYTE PTR [rbp-0x3]
201177: 88 45 f8 mov BYTE PTR [rbp-0x8],al
20117a: 8a 45 fd mov al,BYTE PTR [rbp-0x3]
20117d: 88 45 f9 mov BYTE PTR [rbp-0x7],al
201180: 8a 45 fc mov al,BYTE PTR [rbp-0x4]
201183: 88 45 fa mov BYTE PTR [rbp-0x6],al
201186: c6 45 fb 4e mov BYTE PTR [rbp-0x5],0x4e
20118a: 80 75 fb 4e xor BYTE PTR [rbp-0x5],0x4e
20118e: 48 8d 7d f6 lea rdi,[rbp-0xa]
201192: e8 79 fe ff ff call 201010 <puts@plt>
201197: 31 c0 xor eax,eax
201199: 48 83 c4 10 add rsp,0x10
20119d: 5d pop rbp
20119e: c3 ret
全体のコードはこうなる.
#include "MCTargetDesc/X86BaseInfo.h"
#include "X86.h"
#include "X86InstrBuilder.h"
#include "X86InstrInfo.h"
#include "X86Subtarget.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetOpcodes.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/DebugLoc.h"
#include "llvm/IR/Function.h"
#include "llvm/MC/MCInstrDesc.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
#include <bits/stdint-uintn.h>
#include <cassert>
#include <cstdint>
#include <random>
using namespace llvm;
#define DEBUG_TYPE "X86-encode-literals"
static cl::opt<bool> EnableEncodeLiterals("enable-encode-literals",
cl::desc("X86: Encode Literals"),
cl::init(false));
static inline bool isStoreLocalValue(MachineInstr &MI);
MachineBasicBlock::instr_iterator skip(MachineBasicBlock *MBB, unsigned Index);
static inline unsigned getXorOpc(MachineInstr &MI);
namespace {
class X86EncodeLiteralsPass : public MachineFunctionPass {
public:
X86EncodeLiteralsPass() : MachineFunctionPass(ID) {}
StringRef getPassName() const override { return "X86 Encode Literals"; }
/// Loop over all of the basic blocks, encoding literals.
bool runOnMachineFunction(MachineFunction &MF) override;
static char ID;
private:
MachineRegisterInfo *MRI = nullptr;
const X86InstrInfo *TII = nullptr;
const X86RegisterInfo *TRI = nullptr;
inline void buildXor(MachineInstr &MI, unsigned Index, uint64_t Operand);
MachineOperand &getLiteralOperand(MachineInstr &MI, unsigned Offset);
void obfuscateLiteralOperand(MachineFunction &MF, MachineInstr &MI,
unsigned Index);
};
} // end anonymous namespace
char X86EncodeLiteralsPass::ID = 0;
FunctionPass *llvm::createX86EncodeLiterals() {
return new X86EncodeLiteralsPass();
}
INITIALIZE_PASS(X86EncodeLiteralsPass, DEBUG_TYPE, "X86 Encode Literals pass",
false, false)
static inline bool isStoreLocalValue(MachineInstr &MI) {
unsigned Opcode = MI.getOpcode();
return Opcode == X86::MOV8mi || Opcode == X86::MOV16mi ||
Opcode == X86::MOV32mi || Opcode == X86::MOV64mi32;
}
MachineBasicBlock::instr_iterator skip(MachineBasicBlock *MBB, unsigned Index) {
auto I = MBB->instr_begin();
auto E = MBB->instr_end();
unsigned Cur = 0;
while (I != E && Cur != Index)
++I, ++Cur;
return ++I;
}
static inline unsigned getXorOpc(MachineInstr &MI) {
assert(isStoreLocalValue(MI));
switch (MI.getOpcode()) {
case X86::MOV8mi:
return X86::XOR8mi;
case X86::MOV16mi:
return X86::XOR16mi;
case X86::MOV32mi:
return X86::XOR32mi;
case X86::MOV64mi32:
return X86::XOR64mi32;
default:
assert(1 && "unreachable");
return 0;
}
}
inline void X86EncodeLiteralsPass::buildXor(MachineInstr &MI, unsigned Index,
uint64_t Operand) {
MachineBasicBlock *PMBB = MI.getParent();
MachineBasicBlock::instr_iterator Iter = skip(PMBB, Index);
int64_t Offset = MI.getOperand(3).getImm();
unsigned XOROpc = getXorOpc(MI);
addRegOffset(BuildMI(*PMBB, Iter, MI.getDebugLoc(), TII->get(XOROpc)),
X86::RBP, true, Offset)
.addImm(Operand);
}
bool X86EncodeLiteralsPass::runOnMachineFunction(MachineFunction &MF) {
bool Changed = false;
MRI = &MF.getRegInfo();
TII = MF.getSubtarget<X86Subtarget>().getInstrInfo();
TRI = MF.getSubtarget<X86Subtarget>().getRegisterInfo();
if (!EnableEncodeLiterals)
return false;
for (auto &MBB : MF) {
unsigned Index = 0;
for (MachineBasicBlock::iterator MI = MBB.begin(), E = MBB.end(); MI != E;
++MI, ++Index) {
if (isStoreLocalValue(*MI)) {
obfuscateLiteralOperand(MF, *MI, Index);
Changed = true;
}
}
}
return Changed;
}
MachineOperand &X86EncodeLiteralsPass::getLiteralOperand(MachineInstr &MI,
unsigned Offset) {
MachineOperand &Lit = MI.getOperand(MI.getNumOperands() - 1);
assert(Lit.isImm());
return Lit;
}
void X86EncodeLiteralsPass::obfuscateLiteralOperand(MachineFunction &MF,
MachineInstr &MI,
unsigned Index) {
unsigned LitOffset = MI.getNumOperands() - 1;
MachineOperand &Lit = getLiteralOperand(MI, LitOffset);
std::random_device seed_gen;
std::mt19937_64 mt(seed_gen());
uint64_t Rnd = mt();
uint64_t Imm = Lit.getImm();
// replace literal operand by rnd
MI.RemoveOperand(LitOffset);
MachineOperand OpRnd = MachineOperand::CreateImm(Rnd);
MI.addOperand(OpRnd);
buildXor(MI, Index, Imm ^ Rnd);
}
レジスタへの即値mov等,未対応の点もかなりあるが,パス本体以外を含めても200行くらいで簡単な難読化を実装できた.
この実装で少し難しい点はBuildMI
だが,先述の通りinstr_iterator
を使う事で無理矢理解決した.
ほぼコピペでobfuscateLiteralOperand
辺りを書き換えることでいろんな難読化手法を実装できると思うのでやってみてほしい.
Comments