Обфусцирование целочисленных констант с проходом LLVM - PullRequest
0 голосов
/ 02 февраля 2019

Прочитав эту прекрасную статью из Quarkslab о запутывающих нолях , я подумал, что мне нужно немного изменить ее, чтобы запутать произвольные целочисленные константы.

Однако, похоже, что мойпроход игнорируется или не оказывает никакого влияния на результирующий битовый код LLVM (или даже двоичный исполняемый файл).

Простое запутывание работает следующим образом: генерируется случайное int, затем константа для скрытия XORed с этимключ.На результат применяется дополнение к двум.

Это дает целое число, которое затем вычисляется до его исходного значения путем выдачи требуемого битового кода LLVM.

Вот мой PoC (адаптировано из 1 ):

#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sstream>

using namespace llvm;

namespace {
class MyPass : public BasicBlockPass {
public:
  static char ID;

  MyPass() : BasicBlockPass(ID) {}

  bool runOnBasicBlock(BasicBlock &BB) override {

    bool modified = false;
    for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(),
                                       end = BB.end();
         I != end; ++I) {

      Instruction &Inst = *I;

      if (!isValidCandidateInstruction(Inst))
        continue;

      for (size_t i = 0; i < Inst.getNumOperands(); ++i) {

        if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) {
          std::stringstream stream;
          stream << std::hex << C->getUniqueInteger().getLimitedValue();
          std::string result(stream.str());
          errs() << "Found an integer: 0x" << result << "\n";

          if (C->getUniqueInteger().getLimitedValue() == 1337) {
            errs() << "Obfuscating constant 1337\n";
            if (Value *New_val = obfuscateInt(Inst, C)) {
              Inst.setOperand(i, New_val);
              modified = true;
              errs() << "Replaced with " << New_val << "\n";
            } else {
              errs() << "ObfuscateZero: could not rand pick a variable for "
                        "replacement\n";
            }
          }
        }
      }
    }
    return modified;
  }

  // replValue = ~(originalInt ^ key) -1
  Value *obfuscateInt(Instruction &Inst, Constant *C) {

    srand(time(NULL));
    int key = std::rand();
    int64_t replacedValue = ~(C->getUniqueInteger().getLimitedValue() ^ key);

    Constant *replValue = ConstantInt::get(C->getType(), replacedValue),
             *keyValue = ConstantInt::get(C->getType(), key);

    IRBuilder<> Builder(&Inst);
    Value *repl = Builder.CreateXor(replValue, keyValue);
    Value *finValue = Builder.CreateNeg(repl);

    return Builder.CreateSub(finValue, ConstantInt::get(C->getType(), 1));
  }

  // only interested in integer values  
  Constant *isValidCandidateOperand(Value *V) {
    Constant *C;
    if (!(C = dyn_cast<Constant>(V)))
      return nullptr;

    if (!C->getType()->isIntegerTy()) {
      return nullptr;
    }

    return C;
  }

  bool isValidCandidateInstruction(Instruction &Inst) {
    if (isa<GetElementPtrInst>(&Inst)) {
      errs() << "Ignoring GEP\n";
      return false;
    } else if (isa<SwitchInst>(&Inst)) {
      errs() << "Ignoring Switch\n";
      return false;
    } else if (isa<CallInst>(&Inst)) {
      errs() << "Ignoring Calls\n";
      return false;
    } else {
      return true;
    }
  }
};

} // namespace

char MyPass::ID = 0;
static RegisterPass<MyPass> X("MyPass", "Obfuscates 1337", true, false);

// register pass for clang use
static void registerMyPassPass(const PassManagerBuilder &,
                               llvm::legacy::PassManagerBase &PM) {
  PM.add(new MyPass());
}

static RegisterStandardPasses
    RegisterMBAPass(PassManagerBuilder::EP_OptimizerLast, registerMyPassPass);

И простая тестовая программа:

int main(void)
{
    volatile int a = 3;
    a += 1337;
    return a;
}

Я компилирую проход LLVM следующим образом:

clang -g3 -shared -fPIC MyPass.cpp -o pass/MyPass.so

Затем я запускаюпередача битового кода LLVM вышеупомянутого простого теста:

opt -S -load pass/MyPass.so -MyPass bin/simple_test.ll -o bin/out.ll

Содержимое bin / out.ll такое же, как bin / simple_test.ll, что, очевидно, противоположно тому, что я хочу:

; ModuleID = 'bin/simple_test.ll'
source_filename = "tests/simple_test.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store volatile i32 3, i32* %2, align 4
  %3 = load volatile i32, i32* %2, align 4
  %4 = add nsw i32 %3, 1337
  store volatile i32 %4, i32* %2, align 4
  %5 = load volatile i32, i32* %2, align 4
  ret i32 %5
}

attributes #0 = { noinline nounwind optnone sspstrong uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{!"clang version 7.0.1 (tags/RELEASE_701/final)"}

Конечно, я думал, что компилятор оптимизировал мою небольшую попытку запутывания, но после ручного применения небольшого преобразования к тестовой программе я мог видеть дополнительные операции XOR, NEG и SUB в результирующемдизассемблирование, которое заставляет меня думать, что оптимизатор здесь не виноват.

Меня интересует подтверждение концепции, в которой константа 1337 (немного) «скрыта», просто ради этого.Меньше интересуются комментариями, в которых говорится, что запутывание бесполезно, или указанием на то, что вам не нравится в коде, который не имеет отношения к вопросу.

Ответы [ 2 ]

0 голосов
/ 07 февраля 2019

IRBuilder выполняет базовое свертывание констант по умолчанию (как сообщил автор вопроса в своем ответе ).

Чтобы отключить свертывание констант в IRBuilder создать его следующим образом.

IRBuilder<NoFolder> Builder;
0 голосов
/ 03 февраля 2019

Проблема здесь в том, что IRBuilder выполняет по умолчанию Constant Folding при создании новых инструкций IR.

Чтобы решить эту проблему, мне пришлось создать новую volatile (volatile неОбязательная, но я мог бы так сделать) переменную в IR, выполнить «обфусцированные» арифметические операции над ней и заменить операнд инструкции, которая использует «1337», на результирующее значение.

Код - этоТо же, что и в вопросе, за исключением функции obfuscateInt (...), которая теперь выглядит следующим образом:

  // replValue = ~(originalInt ^ key) -1
  Value *obfuscateInt(BasicBlock &BB, Instruction &Inst, Constant *C) {

    srand(time(NULL));
    int key = std::rand();
    int32_t replacedValue = ~(C->getUniqueInteger().getLimitedValue() ^ key);

    Constant *replValue = ConstantInt::get(C->getType(), replacedValue),
             *keyValue = ConstantInt::get(C->getType(), key);

    IRBuilder<> Builder(&Inst);

    // allocate enough space on the stack to store a 32-bit value. Var name = "AA"
    AllocaInst *varAlloc = Builder.CreateAlloca(Builder.getInt32Ty(), nullptr, "AA");

    // Store the key in AA, set "volatile" to true
    Builder.CreateStore(keyValue, varAlloc, true);

    // read the variable "AA"
    LoadInst *loadVar = Builder.CreateLoad(varAlloc, true, "AA");

    // use it
    Value *repl = Builder.CreateXor(replValue, loadVar);
    Value *finValue = Builder.CreateNeg(repl);

    return Builder.CreateSub(finValue, Builder.getInt32(1));
  }

Сгенерированный IR теперь выглядит так:

  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store volatile i32 3, i32* %2, align 4
  %3 = load volatile i32, i32* %2, align 4
  %4 = alloca i32
  store volatile i32 525933950, i32* %4
  %5 = load volatile i32, i32* %4
  %6 = xor i32 -525932616, %5
  %7 = sub i32 0, %6
  %8 = sub i32 %7, 1
  %9 = add nsw i32 %3, %8
  store volatile i32 %9, i32* %2, align 4
  %10 = load volatile i32, i32* %2, align 4
  %11 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([41 x i8], [41 x i8]* @.str, i32 0, i32 0), i32 %10)
  %12 = load volatile i32, i32* %2, align 4
  ret i32 %12

ИРазборка показывает, что 1337 нигде не появляется, но поведение программы сохраняется:

0000000000001140 <main>:
    1140:       55                      push   rbp
    1141:       48 89 e5                mov    rbp,rsp
    1144:       48 83 ec 10             sub    rsp,0x10
    1148:       31 c0                   xor    eax,eax
    114a:       c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
    1151:       c7 45 f8 03 00 00 00    mov    DWORD PTR [rbp-0x8],0x3
    1158:       8b 4d f8                mov    ecx,DWORD PTR [rbp-0x8]
    115b:       c7 45 f4 02 77 c4 31    mov    DWORD PTR [rbp-0xc],0x31c47702
    1162:       8b 55 f4                mov    edx,DWORD PTR [rbp-0xc]
    1165:       81 f2 c4 8d 3b ce       xor    edx,0xce3b8dc4
    116b:       29 d0                   sub    eax,edx
    116d:       83 e8 01                sub    eax,0x1
    1170:       01 c1                   add    ecx,eax
    1172:       89 4d f8                mov    DWORD PTR [rbp-0x8],ecx
    ....
...