justCTF2020] debug_me_if_you_can ( LD_PRELOAD 후킹, ptrace )
후 기 : 이번 대회로 ptrace개념을 정확히 알게되었다.!!!!!!!!
기 본 개 념
ptrace란??
부모 프로세스가 다른 프로세스의 실행을 관찰하거나 제어하는 수단을 제공한다. (자식 프로세스)
아래의 표는
ptrace함수의 첫번째 인자로 들어가는 request마다의 행동을 요약해 둔 것입니다.
TRACE_TRACEME 자기 자신을 추적 가능하도록 만드며, 부모 프로세스에 의해 추적되는 것을 나타냅니다.
PTRACE_PEEKTEXT, PTRACE_PEEKDATA 자식 프로세스의 주어진 주소의 WORD를 읽어 리턴합니다.
PTRACE_POKETEXT, PTRACE_POKEDATA 부모 프로세스 메모리 주소에서 자식 프로세스 메모리 주소로 WORD를 복사합니다.
PTRACE_PEEKUSER 자식 프로세스의 USER 영역 주소의 WORD를 읽어 리턴합니다. PTRACE_GETREGS, PTRACE_GETFPREGS 자식 프로세스의 레지스터들을 부모 프로세스 data 위치로 복사합니다.
부모 프로세스 메모리 주소에서 자식 프로세스 주소의 USER 영역으로 WORD를 복사합니다.
PTRACE_CONT 자식 프로세스를 다시 시작합니다.
PTRACE_SETREGS, PTRACE_SETFPREGS 부모 프로세스의 data 위치에서 레지스터들을 자식 프로세스로 복사합니다.
PTRACE_SYSCALL, PTRACE_SINGLESTEP 자식 프로세스를 다시 시작한 후 인스트럭션 하나를 실행하고 다시 멈춥니다.
PTRACE_KILL 자식 프로세스에게 SIG_KILL을 보냅니다.
PTRACE_ATTACH 지정 프로세스의 부모 프로세스로 ATTACH 합니다.
PTRACE_DETACH ATTACH된 자식 프로세스를 DETACH 합니다. |
Shared Library Injection (3)
Shared Library Injection 세 번째 포스트입니다. 이전 글은 아래의 링크에서 확인하시기 바랍니다. 2016/07/18 - [Security] - Shared Library Injection (1) 2016/07/12 - [Security] - Shared Library Injectio..
it-diary.tistory.com
지문 : I bet you can't crack this binary protected with my custom bl33d1ng edg3 pr0t3c70r!!!111oneoneone

3개의 문제파일이 주어진다.
흐 름
"supervisor을 통해 자식프로세스가 생성되고, 자식 프로세스는 crackme.enc를 실행시킨다.
이후 flag.png.enc를 복호화 한다(Child). 여기서!!! 부모 프로세스인 supervisor은 자식프로세스를 컨트롤한다."
[참고] crackme.enc를 그냥 실행되지는 않는다. 뒤에 알게 되겠지만 crackme.enc가 패치 되어 실행되기 때문이다.

자식프로세스부터 보자.

[중 요]
자식프로세스가 생성되면, 빨간색박스로 인해 crackme.enc가 부모에 의해 attach된다.
즉, 부모 프로세스에 의해서 자식프로세스가 디버깅 된다.
이 말은 자식프로세스에 debuger가 붙어져 있기 때문에 또 다른 debuger가 붙을 수 없다.
그래서 이 문제의 이름처럼 자식프로세스에 디버거를 붙일 수 없다.
부모가 실행하는 코드

[중 요]
24 : 자식프로세스를 다시 실행시킨다.
30 : 자식프로세스의 레지스터를 모두 들고온다.
31 : 자식프로세스의 특정 주소에 값을 읽는다.
32 : 만약 그값이 0x1337BABE이 아니면 break; ( 그 값이 아래의 사진이다. )

이 부분의 헥스값을 읽어 들인다고 볼 수 있다.!

다음 위의 코드를 실행하는데 무슨 의도로 이 코드를 작성했는지는 분석하지 못했다..

[중 요]
이 부분이 핵심코드이다. 특정영역의 데이터를 읽어와서 다른 값으로 치환한다.
즉 crackme.enc의 코드를 복호화하는 작업이라고 볼 수 있다.
peektext로 데이터를 가지고 와서 poketext을 이용하여 특정 주소의 데이터를 바꾸어 준다.
우리는 이 부분을 후킹하여 값들을 가지고 올 수 도있고, 손으로 직접 뽑아 낼 수 도있다.

[중 요]
RDX가 주소이고, RCX가 데이터이다.
RDX라는 주소에 RCX값을 넣는다.
이것을 손으로 뽑는데 한계가 있어 LD_PRELOAD HOOKING을 이용하였다.
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <stdarg.h>
#include <sys/utsname.h>
#include <sys/stat.h>
long int ptrace(enum __ptrace_request __request, ...){
pid_t caller = getpid();
va_list list;
va_start(list, __request);
pid_t pid = va_arg(list, pid_t);
void* addr = va_arg(list, void*);
void* data = va_arg(list, void*);
long int (*orig_ptrace)(enum __ptrace_request __request, pid_t pid, void *addr, void *data);
orig_ptrace = dlsym(RTLD_NEXT, "ptrace");
long int result = orig_ptrace(__request, pid, addr, data);
if (__request == PTRACE_SETREGS){
unsigned long rip = *((unsigned long*)data + 16) - 0x555555554000;
printf("SETREGS: rip: 0x%lx\n", rip);
} else if (__request == PTRACE_POKETEXT){
printf("POKETEXT: (addr , data) = (0x%lx , 0x%lx)\n", (unsigned long)addr - 0x555555554000, (unsigned long)data);
}
return result;
}
__attribute__((constructor)) static void setup(void) {
fprintf(stderr, "called setup()\n");
}
gcc -shared -fPIC -ldl hook.c -o preload_test.so
LD_PRELOAD=$PWD/preload_test.so ./supervisor

이 결과를 바탕으로 어느 주소에서 어떤값으로 수정해주는지 알 수 있다.
좀 더 분석을 해보면 secret_key를 읽어서 password를 check한다는 것을 알 수 있었다.
secret_key를 만들어 준다음 후킹을 시도해 바뀌는 값들을 모두 찾아준다.
이제 우리는 해당주소에가서 데이터를 수정해야주어야 한다.
idaPython을 이용하여 패치를 해주면 복구가 될 것이다.(코드는 길어서 생략)
주의해야할점이 있다.
전체적인 패턴이 int3이후의 특정 주소에 rip가 설정된다.
그 int3~rip지점 까지 다 nop으로 처리 해주어야한다. 안그러면 코드가 깨져서보인다.
(코드를 복구하기 위해 아이다로 노가다를 좀 해본 결과로 알게 되었다.ㅠㅠ)

key를 검증하는 로직이다.
이를 역연산 해주면 되는데,,, 꽤나 오래걸렷다. 코드는 아래와 같다.
table = [27, 89, 41, 76, 61, 111, 34, 127, 38, 28, 44, 47, 7,
78, 23, 30, 97, 10, 83, 16, 52, 101, 74, 66, 88, 8,
29, 96, 51, 85, 55, 68, 82, 57, 46, 114, 15, 110, 126,
63, 50, 71, 90, 19, 25, 6, 122, 81, 24, 26, 99, 72,
2, 119, 62, 84, 53, 22, 4, 94, 79, 73, 48, 3, 21, 113,
77, 17, 56, 18, 5, 69, 39, 104, 58, 117, 9, 32, 1, 64,
105, 35, 106, 59, 65, 95, 123, 87, 60, 31, 102, 86,
92, 12, 54, 115, 45, 103, 67, 93, 75, 40, 118, 120,
125, 49, 109, 37, 20, 116, 91, 107, 13, 80, 112, 100,
14, 98, 43, 11, 70, 42, 124, 121, 108, 36, 33]
key =''
def func(x):
tmp=''
while x > 0:
if x&1 == 0:
tmp +='1'
x = (x-2) // 2
else:
tmp +='0'
x = (x-1 )// 2
#print tmp
return tmp[::-1]
for i in range(len(table)):
x = table.index(i+1)
key +=func(x)+'?'
print (key)
이렇게 뽑아낸 키를 secret_key에 저장하고, 실행하면

