Network

[ Linux ] Raw socket & Scapy 실습 - kakao 실무진 특강 (3)

jogaknabi_1023 2024. 3. 4. 23:25

그럼 저번 Kernel 게시물 이후 Socket 관련 내용을 정리해보겠다. 그 이후에는 Scapy를 사용하여 간단하게 패킷을 생성하고 보냈던 실습 과정에 대해 작성하였다.

 

리눅스 네트워크 TCP/IP 계층

 

세부적인 Kernel space 네트워크 구조

 

  • BSD Sockets 계층 : 일반적인 소켓 관리 소프트웨어가 BSD 소켓만 처리
  • INET Sockets 계층 : 소켓 관리 소프트웨어를 지원하는데, IP 기반 프로토콜인 TCP/UDP의 통신 종점을 관리
  • TCP(Transmission Control Protocol) : 연결 지향의 신뢰할 수 있는 일대일 프로토콜 (TCP 패킷들에 번호를 매겨 종점 호스트는 데이터를 수신할 때 패킷 순서 및 손실을 확인)
  • UDP(User Datagram Protocol) : 비연결지향 방식의 프로토콜 (패킷이 전송 시 제대로 도착했는지 확인 불가)
  • IP 계층 : 인터넷 프로토콜을 구현한 계층으로, 전송하는 데이터 앞에 IP 헤더를 붙이고, 들어오는 IP 패킷의 헤더를 제거하여 TCP나 UDP로 전달
  • IP 계층 아래의 PPP 또는 이더넷 같은 네트워크 장치들이 리눅스의 모든 네트워킹을 지원하며, 네트워크 장치는 항상 물리 장치를 가리키는 것이 아니라 루프백 장치 같은 몇몇 순수 소프트웨어로 작성된 것도 존재한다.

 

BSD Socket Interface

우리가 흔히 말하는 socket이다.

 

일반적으로 응용 프로그램이 실행되면 해당 프로그램이 메모리에 로드되고, 운영체제에 의해 실행되는 프로세스가 생성된다. 그리고 이 프로세스는 네트워크 통신을 위해 필요한 소켓을 생성하기 위해 커널로 시스템 호출을 수행한다. 이 소켓을 사용하여 응용 프로그램은 네트워크를 통해 데이터를 전송하고 수신할 수 있다. 또한 파이프와 달리 소켓은 저장가능한 데이터 용량이 제한되지 않는다.

 

socket은 양방향 통신 방법으로 socket을 사용하여 통신하는 프로세스는 클라이언트 서버 모델을 따르며, 서버는 서비스를 제공하고, 클라이언트는 이 서비스를 이용한다. 여기서 클라이언트 서버 모델(Client-Server model)은 하나의 Server process와 하나 이상의 client process들로 여러 명의 클라이언트들이 하나의 서버에게 정보를 요청하는 형태이다.

 

그럼 socket의 구성(주소 패밀리명, 포트 번호, IP주소)를 알아보기 전, 잠깐 옆으로 빠져서 Client-Server model 통신에 대해 찍먹 정보만 적어보고 가겠다. 물론 내 이해를 위해 ㅋㅋㅋ

 

먼저 Client-Server model에서 client는 IP 주소와 port 번호를 통해 server를 찾는다. 그리고 이 IP 주소와 port 번호로 TCP/IP 프로토콜로 통신할 수 있다.

    1) Server Socket Addreess에 있는 IP 주소를 통해 Host를 확인

    2) Server Socket에 있는 port를 통해 Service를 확인하고, 그 Service를 수행하기 위한 Server Process를 암묵적으로 확인한다.

IP 주소와 port 번호를 이용

 

여기서 Server컴퓨터가 켜질 때부터 꺼질 때까지 돌아가야 하기 때문에 daemon이라 할 수 있으며, 그렇기 때문에 init process에 의해 생성되고 관리된다. 각 Server들은 request들을 기다린다.

 

Socket 통신 방법

TCP Server (Server Socket)

  • socket() : socket 하나를 생성
  • bind() : 생성한 socket을 server socket으로 등록
  • listen() : server socket을 통해 클라이언트의 접속 요청을 확인하도록 설정
  • accept() : 클라이언트 접속 요청 대기 및 허락. 클라이언트와 통신을 위해 새 socket 생성. 새로 생성한 socket을 client socket이라고 하자
  • read()/write() : client socket으로 데이터 송수신
  • close() : client socket을 소멸

Client Server (Client Socket)

  • socket() : socket 하나 생성
  • connect() : server로 연결 시도
  • write()/read() : socket으로 데이터 송수신
  • close() : client socket을 소멸

 

Socket 종류

  • Stream (=TCP Socket) 이 소켓은 데이터가 전송 중 분실, 오염, 또는 중복되지 않는다는 것을 보장하는 신뢰성 있는 양방향 순차 데이터 스트림
  • Datagram (=UDP Socket) 이 소켓은 양방향 데이터 전송을 제공하나, 메시지 도착 유무, 순서 보장, 중복 제거, 오염 유무 등을 보장하지 않음
  • Raw 프로세스가 하부 프로토콜에 직접 접근 가능. 이더넷 장치에 이 소켓을 열어 가공되지 않은 IP 데이터 흐름을 볼 수 있음
  • Reliable Delivered Messages 데이터그램 소켓과 유사하지만, 데이터가 목적지에 도착한다는 것을 보장
  • Sequenced Packets 스트림 소켓과 유사하며, 데이터 패킷 크기가 고정됨
  • Packet 표준 BSD 소켓 타입이 아니며, 장치 수준에서 프로세스가 직접 패킷에 접근할 수 있는 확장 패킷 유형

 

개인적으로 socket을 공부하면서 그럼 socket interface를 사용하지 않고도 TCP/IP 프로토콜 사용하는 네트워크 통신이 가능한지 궁금해졌다. socket을 사용하는 것처럼 클라이언트와 서버 간의 직접적인 연결을 제공하며, 데이터의 전송과 수신을 더 세밀하게 제어할 수 있는 다른 방법이 있는지 말이다.

 

일단 결과로 봤을 땐 가능은 하다. 하지만 개발자가 네트워크적인 측면까지 고려하며 프로그램을 개발해야하기 때문에 프로그래밍이 아주 복잡해질 뿐만 아니라 관련 프로토콜의 내부 구조를 잘 알고 있는 개발자여야 한다는 것이다.

 

이런 복잡한 작업을 간편하게 만들어주는 것이 Socket Interface 이다. 소켓 인터페이스는 응용 계층에서 전송 계층의 기능을 사용할 수 있도록 제공하는 응용 프로그래밍 인터페이스 (API, Application Programming Interface)이다. 즉 응용 프로그램과 TCP 계층을 연결하는 역할을 하는 것이다. 소켓 인터페이스를 이용하면 TCP/IP 프로토콜의 전송 계층이나 네트워크 계층의 복잡한 구조를 몰라도 쉽게 네트워크 프로그램을 작성할 수 있다.

 

그럼 RAW Socket이란?

특정 프로토콜 용의 전송 계층 포맷팅 없이 인터넷 프로토콜 패킷을 직접적으로 주고 받게 해주는 소켓

 

위에서 다뤘던 일반적인 socket은 네트워크를 통해 데이터를 주고 받을 때 헤더정보의 추가 및 분리와 같은 작업을 운영체제의 프로토콜 스택 내에서 자동으로 처리되어 신경 쓸 필요가 없었다. 그래서 개발자는 헤더를 접할 기회가 없습니다. 하지만 새로운 프로토콜을 사용하거나, 헤더의 정보를 이용하여 보안 프로그램과 같은 것을 구현하는 경우에는 헤더를 직접 제어해주어야 한다. 이러한 상황에서 RAW소켓은 헤더 정보들에 대해 프로그래머가 직접 제어할 수 있게 해준다.

즉, RAW소켓을 사용하면 IP헤더와 TCP헤더를 직접 제어할 수 있는데, 이를 모두 사용자 데이터로 취급하므로 네트워크 계층에 어떤 종류의 헤더가 와도 상관이 없게 된다. 그렇기에 RAW 소켓으로 TCP데이터를 전송하고 싶은 경우에 IP헤더와 TCP헤더를 보내고자 하는 데이터의 앞부분에 직접 만들어주어야한다.

 

Socket vs RAW Socket

Socket vs RAW Socket

Socket: 일반적으로 네트워크 계층에서 수신된 패킷을 응용 프로그램에 전달할 때, TCP/IP 스택은 해당 패킷을 프로토콜 스택을 통해 처리한 후 응용 프로그램에 전달한다.

RAW Socket: 네트워크 계층에서 바로 패킷(네트워크 계층에서 종단 장비 사이에서 전달되는 데이터의 묶음)을 응용 프로그램으로 전달할 수 있다.

 

Scapy를 통해 RAW Socket Programming 실습

Scapy: 패킷 생성 도구. HTTP, DNS, TCP, IP, Ethernet 프로토콜의 메시지를 생성시키는 Python Library 이다.

EC2 인스턴스(Amazon Linux 2) 생성 후 실습 시작. 파이썬 인터랙티브 쉘이 나오면 됨. 인스턴스는 패킷의 송수신 때문에 2개 만들었다.

# python 설치
apt-get update
apt install python3-pip
pip3 install scapy

 

 

[ 실습 1 ] myPacket 만들어서 패킷 저장 후 확인

wireshark는 로컬PC에 미리 설치.

# myPacket에 저장
>>> myPacket = IP(dst='172.31.6.175')/ICMP()
>>> myPacket
<IP  frag=0 proto=icmp dst=172.31.6.175 |<ICMP  |>>
>>> id(myPacket)
140627013942592

# 동일한 소스IP와 대상IP를 가진 패킷이라도 각각의 패킷은 고유한 ID 값을 가져야 한다.

# 다른 세션 열어서 패킷전송 결과값 실시간으로 확인
root@ip-172-31-0-180:~# tcpdump -neli eth0 icmp

Sent 1 packets.
>>> send (myPacket)
.
Sent 1 packets.
>>> send (myPacket)
.
Sent 1 packets.

root@ip-172-31-0-180:~# tcpdump -neli eth0 icmp -w icmp.pcap

root@ip-172-31-0-180:~# cp icmp.pcap /home/ubuntu

# pem 키 사용하여 인스턴스 -> 로컬로 파일 이동 후
C:\Users\user\Desktop\키페어>scp -i "test1.pem" ubuntu@ec2-3-38-99-233.ap-northeast-2.compute.amazonaws.com:/home/ubuntu/icmp.pcap ./

# wireshark 접속해서 icmp.pcap 파일 확인하기

 

 

[ 실습2 ] 3가지 목적지의 패킷을 덤프해보기

여기서 ICMP()의 그냥 기본값으로 보냈다. 함수 디폴트값으로 채워준 것.

ICMP Ping 보내기 + wireshark 결과 확인 + list와 for loop 이용해서 print, send 실행

내 두번째 인스턴스: 172.31.6.175
myPacket1 = IP(dst='172.31.6.175')/ICMP()
myPacket2 = IP(dst='google.co.kr')/ICMP()
myPacket3 = IP(dst='kakaocorp.com')/ICMP()

root@ip-172-31-0-180:~# tcpdump -neli eth0 icmp -w icmp2.pcap
send(myPacket1)
send(myPacket2)
send(myPacket3)

cp icmp2.pcap /home/ubuntu

# list&loop 사용했을 때
>>> packetlist = [myPacket1,myPacket2,myPacket3]
>>> for x in packetlist:
...     print(x)
...     send(x)
... 
IP / ICMP 172.31.0.180 > 172.31.6.175 echo-request 0
.
Sent 1 packets.
IP / ICMP 172.31.0.180 > Net("google.co.kr/32") echo-request 0
.
Sent 1 packets.
IP / ICMP 172.31.0.180 > Net("kakaocorp.com/32") echo-request 0
.
Sent 1 packets.

다른 세션 한개 더 열여서 패킷 결과값 넣을 파일 만들기
tcpdump -neli eth0 icmp -w icmp3.pcap

>>> for x in packetlist:
...     print(x)
...     send(x)

cp icmp3.pcap /home/ubuntu

# 파일 로컬로 옮기기
C:\Users\user\Desktop\키페어>scp -i "test1.pem" ubuntu@ec2-3-38-99-233.ap-northeast-2.compute.amazonaws.com:/home/ubuntu/icmp3.pcap ./

# wireshack로 확인

 

WireShark로 icmp3.pcap 파일 결과

패킷의 결과는 보낸 순서대로 오지 않는다.

위와 같은 경우 인스턴스2에서의 요청-응답-google에 대한 요청-kakaocrop에 대한 요청-google에 대한 응답

=> kakaocrop 은 응답이 오지 않았다.

 

kakaocrop.com에 대한 응답 : No response seen

 

반면 google은 그들의 정책에 따른 응답을 보내준 것을 확인

 

이렇게 배웠던 RAW Socket 을 통해 ICMP 패킷을 생성하여 보내는 방식을 실습해보았다. 추가적으로 매번 파일로 옮기지 않아도 원격으로 볼 수 있는 Remote Wireshark도 사용하여 실시간 패킷을 확인하는 실습도 있었는데 그 부분은 다른 게시물에서 따로 다뤄보겠다.

 

 

참고했던 블로그 (네트워크 신들...)

https://www.opensourceforu.com/2015/03/a-guide-to-using-raw-sockets/

https://movefast.tistory.com/350
https://mangkyu.tistory.com/16

https://asidefine.tistory.com/109