System Hacking/Dreamhack 풀이

Return to Shellcode(Stack Canary)

박연준 2023. 7. 2. 00:28

문제 정보

Exploit Tech: Return to Shellcode에서 실습하는 문제입니다.

풀이

문제 파일인 r2s 와 r2s.c 파일을 다운받아서 r2s.c 소스 코드를 먼저 살펴본 결과 buf의 크기는 0x50의 크기인데 첫 번째 입력에 read 함수에 0x100만큼의 입력을 받고 두 번째 입력에 개행문자가 나오기 전에 입력을 받는 gets 함수를 사용하고 있기 때문에 스택 버퍼오버플로우 취약점을 확인할 수 있었다.

 

 

다음으로 소스 코드에서init() 함수로 setvbuf로 입력과 출력이 즉시 처리되도록 처리하였는데 인터넷으로 찾아본 결과 이를 통해 CTF나 exploit 개발 시에 버퍼링이 원인이 되는 예상치 못한 상황을 최소화하기 위해 넣은 것이라고 한다.

 

 

다음으로는 컴파일 시 -zexecstack 옵션을 주면서 NX 옵션을 비활성화 한 것을 볼 수 있다.

 

checksec툴로도 확인한 결과 NX 옵션을 비활성화한 것을 볼 수 있다.

 

r2s 파일을 실행한 결과 buf의 주소인 0x7ffc3fe0f550와 buf와 rbp와의 거리인 96을 알려주었다. 또한 두 번째 입력에서 A를 많이 입력해 본 결과 core 파일이 생성되었다.

 

두 번째로 실행해서 첫 번째 값에는 버퍼 오버플로우를 발생시키지 않았고 두 번째 입력 값에 버퍼 오버플로우를 발생하니까 Stack smashing detected가 나왔고 이것은 카나리가 변조되어서 나오는 오류이다. 따라서 익스플로잇 시나리오를 짜보면 첫 번째 값에서 카나리 값을 알아낸 후 두 번째 입력 값에서 카나리를 우회한 후 buf값에 셸코드를 주입한 후 반환 주소를 buf로 설정하면 셸코드가 실행될 것이다.

 

익스플로잇 코드를 작성하기에 r2s 파일을 한 번 더 실행한 결과 buf의 주소가 계속 동적으로 변하는 것을 확인할 수 있었다. 따라서 buf의 주소는 익스플로잇 코드에서 동적으로 짜주어야 할 것 같다.

 

다음으로 rbp와의 거리가 96이라고 주어졌기 때문에 96을 16진수로 바꾸면 0x60이다. 또한 canary는 rbp와의 거리에서 10진수 8만큼 뺀 거리에 있기 때문에 96 -8 = 88 을 16진수로 바꾸면 0x58이다 . 따라서 58보다 1만큼 증가해서 입력하면 canary값이 나올 것이다.

 

다음과 같이 익스플로잇을 작성하는데 pwntools를 먼저 import 해준 후 slog() 함수를 선언해준 후 “: “ 문자열을 구분자로 사용하여 n과 m을 결합하고 m인 10진수를 받아 hex값인 16진수로 변환해준 후 반환한다.

 

다음으로 remote 함수와 context.arch 를 이용하여 익스플로잇 할 서버를 설정하고 아키텍처를 설정해준다.

 

그 후 recvuntil 함수를 이용해 buf: 문자열을 만날 때까지 기다리고 그 이후에 전송된 값을 슬라이싱을 이용하여 16진수로 파싱한 후 buf 변수에 저장한다. 다음에도 rbp 문자열을 만날 때까지 기다리고, 그 이후에 전송된 값을 파싱하여 buf2sfp 변수에 할당한다. 카나리의 위치는 sfp의 위치에서 10진수의 8만큼 위에 존재하기 때문에 -8을 빼주고 slog() 함수를 이용해 주소를 출력한다.

 

이렇게 익스플로잇 코드를 짠 후 스택 프레임의 정보들을 수집했다. 따라서 sfp의 위치는 0x60, cannary의 위치는 0x58 그리고 buf의 주소까지 알아낼 수 있었다.

 

이제 스택 프레임에 대한 정보를 수집했으므로, 이를 활용하여 카나리를 구해야한다. buf와 카나리 사이를 임의의 값으로 채우면 프로그램에서 buf를 출력할 때 카나리가 같이 출력될 것이다.

 

익스플로잇 코드를 다음과 같이 추가했다. 먼저 buf2cnry에서 buf와 카나리 사이의 위치를 알아냈으므로 buf2cnry에 1을 더한 위치에 쓰레기 값을 주면 카나리 값이 출력될 것이다. 다음으로 Input : 문자열을 찾을 때까지 기다린 후 payload 값을 전송한다. 그 후 recvuntil() 함수를 이용해 payload의 데이터가 수신될 때까지 데이터를 수신한다. 수신받은 후 recvn(7)를 이용하여 7문자를 받고 앞의 1바이트는 널 문자인 \x00을 더해 u64함수를 사용해서 64비트 unsigned integer로 변환한 후 canry 값을 출력하면 아래에 카나리 값이 잘 나오는 것을 확인할 수 있다.

 

카나리를 구했으므로, 이제 buf에 셸코드를 주입하고, 카나리를 구한 값으로 덮은 뒤, 반환 주소를 buf로 덮으면 셸코드가 실행되게할 수 있다. shellcraft, asm을 이용하면 스크립트를 쉽게 추가할 수 있다.

 

다음과 같이 익스플로잇을 추가한다. 먼저 shellcraft모듈을 사용하여 쉘 실행을 위한 어셈블리 코드를 sh 변수에 저장한다. 다음으로 payload를 전송하는데 카나리와의 위치만큼 A문자로 덮어쓰기 위해 buf2cnry를 사용하고 그 후에 아까 저장해둔 카나리 값인 cnry를 p64를 통해 바이트로 변환한 후 붙여넣어준다. 또 반환 주소는 sfp가 있어 8만큼 더 아래에 있기 때문에 8만큼 B로 붙여넣어주고 buf의 주소를 넣어준다.

 

다 붙여 넣어 주면 sendlineafter함수를 이용해 Input: 인 두 번째 입력을 payload에 넣어 전송한다. 마지막으로 interactive()를 이용하여 쉘을 실행할 수 있게 하였다.