Execute
문제로 주어진 파일을 실행하면 위와 같이 메뉴를 보여주고 특히 3번 option을 선택하면 특정 address를 준다.
Analyze
보호기법은 NXbit PIE Relro가 걸려있는 것을 확인 할 수 있다. 이제는 Ida를 통해 각각의 메뉴가 어떤 동작을 수행하는지 확인해보자.
Main
- Line 8 : gen_canary함수 호출
- Line 10 : v5변수에 loc_BA0의 address를 넣는다.
- Line 15 : v3변수에 read_int8함수의 return값 대입
- Line 18~20 : case2인경우 v5에 v3와 v5를 xor연산한 결과를 대입한다.
- Line 21~24 : environ 변수의 주소를 leak해준다.
- Line 25~28 : v4변수가 canary와 같을 경우 JUMPOUT(__CS__, v5); 명령을 수행한다.
JUMPOUT(__CS__, v5); 명령의 경우 IDA에서 확인하면 jmp rax를 의미하는 것을 확인 할 수 있다.
Loc_BA0의 경우 아래와 같은 동작을 수행한다.
프로그램을 실행하면 길게 뜨던 문자열 출력에 관련된 주소인 것을 확인 할 수 있다.
Read_int8
앞서 확인했던 v3 변수에 return 값을 넣던 함수이다. 이번문제에서 유일하게 입력을 받는 함수인데 buf의 크기가 0x20인데 read함수에서 0x21만큼 읽어 들이므로 1byte 만큼 buffer overflow를 발생 시킬 수 있다.
Gen_canary
Gen_canary함수에서는 단순히 /dev/urandom을 통해 난수를 생성해 canary에 넣어준 후 이를 반환해준다.
Win
Flag를 출력해주는 함수이다.
solve
Read_int8에서 1byte overflow가 발생하므로 sfp를 1byte 변경 할 수 있다. Read_int8함수에서 sfp는 main함수의 rbp값이 들어있을 것이다. 결과적으로 main의 rbp를 변경할 수 있는 것이다. Main의 변수들은 rbp를 기준으로 접근된다.
해당 내용은 case 1인경우 실행되는 명령이다. rbp-0x8가 v5인 것을 일단 확인 할 수 있고 jmp rax 통해 점프 할 수 있을 것이다. 여기서 rbp자체에 대한 조작이 발생하면 내가 원하는 address로 점프 할 수 있을 것이다. 그럼 이제 rbp를 어떻게 변경 시킬지 생각해보자.
내가 입력할 수 있는 유일한 함수인 read_int8함수를 중심으로 어셈블리를 읽어보던 중 아래와 같은 내용을 발견하였다.
만약 내가 rbp의 값을 rbp+9만큼 조작한다면 rbp-0x11명령은 rbp-0x8의 명령과 동일 해진다. 따라서 rbp가 rbp-0x8로 조작된 상태에서 프로그램의 초기상태로 돌아온다면(while문)
해당부분은 mov rax, QWORD PTR [rbp] 와 동일 할 것이다. 앞서 rbp에 대해 하위 1byte만큼 통제권을 얻었으므로 성공적으로 공격을 수행 할 수 있을 것이다.
win 함수의 주소역시 IDA에서 확인할 수 있다.
여기서 문제가 발생하는데 case1에서 v5에 저장된 address로 jmp하는 조건이 v4의 변수가 전역변수 canary와 동일한지 검사하는 if문을 통과 해야 하기 때문이다.
위 c코드에서 v4와 canary가 같은지 검사하는 어셈블리이다. 여기서 rbp을 참조하므로 앞서 rbp를 조작했기 때문에 해당 부분이 수행되기 전에 rbp를 원래 상태로 돌려놔야 한다.
이제 rbp를 어떻게 구해야 할지 생각해야한다. Case3에서 environ변수의 주소를 leak해주는데 해당 값을 기반으로 구할 수 있을 것이다. environ변수를 출력하는 부분에 break를 걸고 레지스터의 주소를 확인해 보았다.
environ변수는 rax에 저장됨으로 변수의 위치는 0x7fffffffdff8이고 rbp의 경우는 0x7fffffffdef0임을 확인 할 수 있다. 두 값을 빼서 거리를 구하면 실행시 마다 PIE에 의해 값들이 달라져도 거리를 기준으로 계산하기 때문에 RBP를 구할 수 있을 것이다.
성공적으로 RBP를 구하기 위한 거리를 구했다.
여기까지 결론을 정리해보면 먼저 read_int8함수에서 1byte overflow가 발생하므로 sfp의 하위 1바이트를 제어할 수 있다. read_int 함수의 sfp에는 앞선 함수인 main의 rbp가 들어있을 것이므로 main의 변수들은 rbp를 기반으로 접근하기 때문에 jmp rax명령이 수행되기전 rax에 들어가는 값을 win함수로 바꿔줄 수 있다면 win함수를 호출 해서 flag를 얻을 수 있을 것이다. 물론 case1에서 jmp를 위한 로직이 canary와 v4변수가 같아야하므로 rbp를 원래 상태로 돌려 놓는 작업 역시 필요하다.
Exploit의 전체적인 시나리오를 써보면 아래와 같다
- Case3를 선택해 environ변수의 위치를 leak해 해당 값을 기반으로 rbp를 구해준다.
- read_int8함수에서 1byte만큼 overflow를 발생시킬 수 있으므로 dummy(0x20byte)+rbp하위 1byte+9를 전달한다.
- 위동작을 통해 입력 받는 변수를 v4에서 v6로 변경한다.
- v6 변수에 win함수의 address를 넣어준다.
- rbp를 원상태로 복구시키기 위해 dummy(0x20byte)+rbp하위 1byte를 전달해준다.
- Case1를 선택해 jmp rax 명령을 실행 시킨다.
여기까지 결론을 바탕으로 exploit을 작성해보자.
여기서 rbp를 구할 때 env변수의 address에서 248을 빼는데 이유는 앞서 확인한 0x108이 아니라고 한다. 정확한 이유는 아직 모르겠으나 나중에 공부하다가 알게될거라고 믿고 풀이에서 확인한 248을 넣어 주었다.
성공적으로 flag를 획득 할 수 있었다.
'Wargmae > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz – Punch it] (0) | 2021.01.17 |
---|---|
[Pwnable.xyz - PvP] (0) | 2021.01.13 |
[Pwnable.xyz - TLSv00] (0) | 2021.01.02 |
[Pwnable.xyz - Welcome] (1) | 2021.01.02 |