Posts LLVM - Control Flow 난독화 (1/2)
Post
Cancel

LLVM - Control Flow 난독화 (1/2)

이번 주의 목표

  • Control Flow 조작하여 난독화해보기

이번 주에는 LLVM을 이용한 난독화를 해 보기로! 그 중에서도 Obfuscator-LLVM의 BogusControlFlow와 같이 “코드의 control flow를 조작하는 난독화”를 시도해 보았다.

우선 난독화에 쓸 간단한 입력 controlTest.c를 만들었다

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main()
{
  int num=0;
  if(num==0){
    num++;
  }
}

여기서 저번에 만든 출력 path를 이용하여 BasicBlock을 살펴보면

img

다음과 같이 Entry/if.then/if.end의 BasicBlock으로 나뉘는 것을 볼 수 있다.

이러한 코드에 opaque을 이용한 control flow 난독화를 적용하는 path를 만들어 볼 것이다.

1. 난독화하려는 방법

img

다음과 같이 항상 참인 결과가 나오는 간단한 Opaque Predicate Block을 만들고, 이 값이 true일때 원래의 BasicBlock으로 이동하게 한다.

값이 false일때는 원래의 BasicBlock에서 변형된(미정:연산자를 변형한/값을 1씩 더해준) instructions를 가진 의미 없는 가짜 BasicBlock을 수행하도록 한다.

+ 중간중간에 basicblock01->dump()과 같이 dump()를 통해 BasicBlock의 instruction들을 볼 수 있다! 이를 이용하면 디버깅처럼 쓸 수 있을 듯 하다.

2. 분기문 블럭 생성하여 연결하기

코드를 간단하게 보기 위해 if.then블럭에 대해서만 실행되도록 한정해 두었다.

1
2
3
4
5
6
7
8
9
10
if (BB.getName() == "if.then") {
  BasicBlock *ifthenBB = &BB;  // BasicBlock 클래스를 사용하기 위해 바꿔줌

  BasicBlock::iterator splitPoint = (BasicBlock::iterator)ifthenBB->getFirstNonPHIOrDbgOrLifetime();
  BasicBlock *trueBB = ifthenBB->splitBasicBlock(splitPoint, "trueBB");

  ifthenBB->dump();
  trueBB->dump();
  //출력
}

splitPoint에 (PHINode, debug intrinsic, lifetime intrinsic를 제외한) 첫 번째 instruction을 가져와서, 이 instruction의 위치를 저장해둔다. 여기서 첫 번째 instruction은 아래 사진에서의 “ %1 = load i32, i32* %num, align 4 “ 가 된다.

그 후, splitBasicBlock(splitPoint, “trueBB”)을 통해 이 해당 instruction 이후의 instruction들을 trueBB라는 이름의 새로운 BasicBlock으로 저장한다. (이때 splitPoint가 가리키는 instruction을 다른 것으로 바꾸고 싶다면, splitPoint++을 통해 증가시키며 다음 instruction을 가리킬 수 있다.)

img

코드를 실행시켜 결과를 보면, if.then BasicBlock이 다음과 같이 if.then과 trueBB 두 부분으로 쪼개지고, 두 블럭들이 무조건 분기를 통해 연결된 것을 볼 수 있다.

+ getFirstNonPHIOrDbgOrLifetime()함수를 첫 번째 instruction을 가져올때 사용했는데, 왜 이 함수를 사용하는지 궁금하다. 그냥 첫 번째를 가져오면 안 되는건지? PHINode 이런건 뭘까?

3. 생성한 블럭의 조건문 변경하기

Opaque 난독화를 위해서는 항상 참이 되는 조건문과, 이 조건문에 따라 true/false에 해당하는 BasicBlock으로 가게 하는 분기문이 필요하다. 이번 단계에서는 if.then블럭에 조건문과 분기문을 추가해 줄 것이다.

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
// 필요한 헤더
#include "llvm/IR/Constants.h"
#include "llvm/IR/Instructions.h"

if (BB.getName() == "if.then") { // if.then블럭에서만 난독화 진행
  BasicBlock *ifthenBB = &BB;  // BasicBlock 클래스를 사용하기 위해 바꿔줌

  BasicBlock::iterator splitPoint = (BasicBlock::iterator)ifthenBB->getFirstNonPHIOrDbgOrLifetime();
  BasicBlock *trueBB = ifthenBB->splitBasicBlock(splitPoint, "trueBB");

  errs() << "BEFORE\n";
  ifthenBB->dump();

  Value *LHS = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1);
  Value *RHS = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1);
  // 조건문의 양쪽 식을 만들어줌. 간단하게 양 변을 1로 설정

  ICmpInst *condInstruction = new ICmpInst(ifthenBB->getFirstNonPHIOrDbgOrLifetime(), ICmpInst::ICMP_EQ, LHS, RHS, "newcond");  // 만든 양 변이 EQUAL한지를 판단하는 조건문 생성
  BranchInst::Create(trueBB, trueBB, condInstruction, ifthenBB);  // 만든 조건문을 통해 분기문 생성하고, ifthenBB의 끝에 넣음

  BasicBlock::iterator toRemove = ifthenBB->begin();
  toRemove++;
  Instruction *instToRemove = &(*toRemove);
  instToRemove->dropAllReferences();
  instToRemove->eraseFromParent();
  // 기존의 무조건 분기를 삭제하고, 새로운 instruction들만 남김

  errs() << "AFTER\n";
  ifthenBB->dump();
}

ICmpInst을 통해 int형의 자료들을 비교하는 조건문을 만들 수 있다. Predicate자리에 ICmpInst::ICMP_EQ을 넣어 LHS와 RHS가 같은지 비교하도록 하고, BranchInst를 통해 만든 조건문으로 분기를 생성한다. 아직 만든 BasicBlock이 없어 true일때와 false일때 모두 trueBB BasicBlock으로 분기하도록 하였다.

(분기문을 넣는 과정에서 삽질을 많이 했는데, “Terminator found in the middle of a basic block!”라는 오류를 많이 봤었다. 지금 생각해보면 BasicBlock의 특징 중 하나가 분기가 하나씩만 존재한다는 것이었는데, 그래서 분기문이 BasicBlock의 Terminator라고 볼 수 있을 듯 하다. 코드를 짜 보면서 한 블럭에 분기를 두 개 이상 넣으면서 계속 이런 오류를 봤던 것 같다. 이런 오류가 뜬다면 BasicBlock에서 분기문을 살펴보면 좋을 듯 하다.)

아무튼, 결과는 다음과 같이 나온다.

img

if.then블럭이 제대로 된 분기문으로 변경된 것을 볼 수 있다.

이번 단계에서 너무 삽질을 많이 했다. 방법도 엄청 다양하고.. 타입 문제때문에 헤메기도 하고… Terminator오류도 계속 보니까 친근해짐…

+ 추가로, 저번 글에 쓴 반복과 출력을 다른 방법으로 할 수도 있었다.

1
2
3
4
for (BasicBlock::iterator IT = BB->begin(); IT != BB->end(); IT++) {
  Instruction* I = &(*IT);
  errs() << *I << "\n";
}

위의 코드는 BasicBlock BB를 돌면서 instruction들을 출력해주는 코드로, 이런 식으로 쓸 수도 있었다.. 이게 나중에 쓰다 보니 더 직관적인 느낌…? 뭔가 이렇게 쓰는게 편하다

4. 원본 BasicBlock을 변형하여 false에 연결하기

이 번에는 마지막 단계인…! 원본 BasicBlock인 trueBB를 살짝 변형하여 false에 연결하고 마지막 분기문을 다시 if.then으로 연결해주어서 무한루프를 돌게 해 볼 것이다.

일단 trueBB를 복제하기 위해 CloneBasicBlock을 사용할 것이다.

1
2
3
4
5
// 필요한 헤더
#include "llvm/Transforms/Utils/Cloning.h"

ValueToValueMapTy VMap;
BasicBlock * falseBB = llvm::CloneBasicBlock (trueBB, VMap, "clone");

CloneBasicBlock은 단순한 clone, 즉 shallow cloning이기 때문에 값들을 수정해줘야 하는 것 같지만, 우선 어떻게 되는 것인지 보고 싶어서 바로 원본 코드와 복사된 코드를 바로 출력해 보았다.

img

predecessor가 사라졌다. if.then으로 연결되어 있어야 하는데…!! 그리고 세 번째 파라미터로 준 이름 “clone”이 블럭의 이름과 변수들의 이름 뒤에 붙는 것을 볼 수 있다. 로 뜬 곳은 %1clone으로 되어서 옳지 않은 이름이라 그렇게 뜬 것 같다.

마무리 & 다음 주 계획

이번 주에는 이 난독화를 완벽하게 끝내고 싶어서 시간 투자를 많이 했는데도 부족할 만큼 오래 걸렸다. 다음 시간에 이번에 못한 부분을 마무리짓고, 코드를 짜면서 더 발전시킬 수 있는 방안들도 생각을 한 게 있어서 그 내용에 대해서도 포스팅할 것이다!

다음글 > LLVM - Control Flow 난독화 (2/2)

This post is licensed under CC BY 4.0 by the author.

Contents