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を用いて疑似乱数を生成する.

手順としては,

  1. 即値mov命令のオペランドを書き換える movのオペランドをrndとし(mov byte ptr [rbp - offset], rnd),xor byte ptr [rbp - offset], tmpとする事で直接アセンブリに定数が出てこなくなる.

  2. 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),MBBIndexの位置まで移動させた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辺りを書き換えることでいろんな難読化手法を実装できると思うのでやってみてほしい.