[WHS 네트워크기초] PCAP을 이용한 패킷 분석

#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
#include <arpa/inet.h>

const int data_length = 100; //표현할 데이터 길이입니다.

/* Ethernet header */
struct ethheader {
  u_char  ether_dhost[6]; /* destination host address */
  u_char  ether_shost[6]; /* source host address */
  u_short ether_type;     /* protocol type (IP, ARP, RARP, etc) */
};

/* IP Header */
struct ipheader {
  unsigned char      iph_ihl:4, //IP version
                     iph_ver:4; //IP header length
  unsigned char      iph_tos; //Type of service
  unsigned short int iph_len; //IP Packet length (data + header)
  unsigned short int iph_ident; //Identification
  unsigned short int iph_flag:3, //Fragmentation flags
                     iph_offset:13; //Flags offset
  unsigned char      iph_ttl; //Time to Live
  unsigned char      iph_protocol; //Protocol type
  unsigned short int iph_chksum; //IP datagram checksum
  struct  in_addr    iph_sourceip; //Source IP address
  struct  in_addr    iph_destip;   //Destination IP address
};

/* TCP Header */
struct tcpheader{
  unsigned short     tcp_sport; //TCP source port
  unsigned short     tcp_dport; //TCP destination port
  unsigned int       tcp_seqnum; //TCP sequence number
  unsigned int       tcp_acknum; //TCP acknowledgement number
  unsigned char      tcp_flagc:1, //TCP flag bit
                     tcp_reserved:3, // TCP reserved
                     tcp_dataoffset:4; //TCP data offset
  unsigned char      flags; //TCP flags
  unsigned short     tcp_winsize; //TCP window size
  unsigned short     tcp_chksum; //TCP checksum
  unsigned short     tcp_urgpointer; //TCP urgent pointer
};

struct httpheader{
  u_char data[data_length];
};

void printMacString(u_char* u){ //u_char 형식의 배열을 mac주소 형태의 string으로 반환합니다. (버퍼 필요)
  printf("%02X:%02X:%02X:%02X:%02X:%02X", u[0],u[1],u[2],u[3],u[4],u[5]);
}

void printDataHex(u_char* data){
  int line = 20;
  for(int i = 0;i<data_length;i+=line){
    for(int j = 0; j<line; j++){
      printf("%02X ", data[i+j]);
    }
    printf("\n");
  }
}

void printDataString(u_char* data){
  for(int i = 0;i<data_length;i++){
    printf("%c", data[i]);
  }
  printf("\n");
}


void got_packet(u_char *args, const struct pcap_pkthdr *header,
                              const u_char *packet)
{
  char buffer[20];
  struct ethheader *eth = (struct ethheader *)packet;

  if (ntohs(eth->ether_type) == 0x0800) {

    printf("=====================got tcp packet======================\n");

    printf("Ethernet Header : ");
    printMacString(eth->ether_dhost);
    printf(" / ");
    printMacString(eth->ether_shost);
    printf("\n");

    struct ipheader * ip = (struct ipheader *) (packet + sizeof(struct ethheader));
    
    printf("IP Header : %s / ", inet_ntoa(ip->iph_sourceip));
    printf("%s\n", inet_ntoa(ip->iph_destip));
    unsigned short int iplen = (ip->iph_ihl) * 4; //ip header의 값에 4를 곱하여 바이트로 표현합니다.

    struct tcpheader * tcp = (struct tcpheader *) ((char*)ip + iplen);

    unsigned short int tcplen = (tcp->tcp_dataoffset)*4; //tcp header의 값에 4를 곱하여 바이트로 표현합니다.

    printf("TCP Header : %d / %d\n" , ntohs(tcp->tcp_sport), ntohs(tcp->tcp_dport));

    printf("IP Header len : %d\n", iplen);

    printf("TCP Header len : %d\n", tcplen);

    struct httpheader * http = (struct httpheader *) (packet + sizeof(struct ethheader) + iplen + tcplen);

    if(ntohs(ip->iph_len)-(iplen)-(tcplen) > 0){ //데이터그램의 길이에서 ip헤더의 길이와 tcp헤더의 길이를 뺐을 때 0 이상이라면 데이터가 있음을 알 수 있습니다.
      printf("Data Found : \n");
      printf("hex data (%dbyte) : \n",data_length);
      printDataHex(http->data);
      printf("ASCII string data (%dbyte) : \n",data_length);
      printDataString(http->data);
    }
    else {
      printf("Data Not Found\n");
    }
    printf("=========================================================\n");
  }
}

int main()
{
  pcap_t *handle;
  char errbuf[PCAP_ERRBUF_SIZE];
  struct bpf_program fp;
  char filter_exp[] = "tcp";
  bpf_u_int32 net;

  handle = pcap_open_live("en0", BUFSIZ, 1, 1000, errbuf);

  pcap_compile(handle, &fp, filter_exp, 0, net);
  if (pcap_setfilter(handle, &fp) !=0) {
      pcap_perror(handle, "Error:");
      exit(EXIT_FAILURE);
  }

  pcap_loop(handle, -1, got_packet, NULL);

  pcap_close(handle);
  return 0;
}

실행하면 이렇게 된다.

어쩌다 데이터가 있는 패킷을 잡으면 이렇게 뜬다. 아 참고로 데이터가 있는 패킷인지 아닌지는 ip헤더의 ip_len(데이터그램 크기)과 ip헤더 길이와 tcp헤더 길이로 판별한다.
ip_len-ip_headerlen-tcp_headerlen > 0이면 데이터가 있는 것이다.

다 좋은데 데이터를 읽을 수가 없다. 443번 포트니까 tsl/ssl 암호화에 의해 그런 것이다.
그래서 실제로 취약한 서버를 하나 열어보았다. ESP32라는 아두이노 모듈로 할 수 있는데, ESP32가 내장되어있는 LOLIN S2 MINI라는 제품을 사용했다. 사실 이거는 혼자 홈 IoT 만들면서 사용하던 부품이다. 사이즈도 작은게 꽤 많은 기능을 수행할 수 있다. 지금은 LCD가 연결되어있다. 이것도 부품을 사서 3d프린터로 케이스를 만들었다.


요거

그래서 아두이노 코드도 뚝딱 짜면, 이런 식이다.

#include <Adafruit_GFX.h>     // Core graphics library
#include <Adafruit_ST7735.h>  // Hardware-specific library for ST7735
#include <Adafruit_ST7789.h>  // Hardware-specific library for ST7789
#include <SPI.h>
#include <WiFi.h>
#include <string.h>
#include <ESP32_Servo.h>

#define TFT_CS 7
#define TFT_RST 16
#define TFT_DC 5
#define TFT_MOSI 11  // Data out
#define TFT_SCLK 12  // Clock out

static const int servoPin = 39;
const char* ssid = "bmc";
const char* password = "********";
char isLed[10] = "OFF";
char isPc[10] = "OFF";
char isCurtain[10] = "OFF";
int visitCnt = 0;
int uvisitCnt = 0;  //for unexpected visit
WiFiServer server(80);
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
Servo servo;
void drawtextzero(char* text, uint16_t color) {
  tft.setCursor(0, 0);
  tft.setTextColor(color);
  tft.setTextWrap(true);
  tft.print(text);
}

void drawtextcontinue(char* text, uint16_t color) {
  tft.setTextColor(color);
  tft.setTextWrap(true);
  tft.print(text);
}

void setup() {
  char setupbuffer[500];
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);
  Serial.begin(115200);
  servo.attach(39);
  servo.write(value);
  delay(10);
  tft.initR(INITR_BLACKTAB);
  // We start by connecting to a WiFi network
  Serial.begin(115200);
  servo.attach(servoPin); // D27에 서보모터가 연결되었습니다.

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  IPAddress local_IP(192, 168, 35, 212);  // ESP32가 사용할 IP address
  IPAddress gateway(192, 168, 35, 1);     // Gateway IP address (공유기 IP주소)
  IPAddress subnet(255, 255, 255, 0);     // subnet mask
  if (!WiFi.config(local_IP, gateway, subnet)) {
    Serial.println("STA failed to configure");
  }
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    strcpy(setupbuffer, "connecting...");
    tft.fillScreen(ST77XX_BLACK);
    drawtextzero(setupbuffer, ST77XX_WHITE);
    delay(500);
    strcpy(setupbuffer, "connecting. . .");
    tft.fillScreen(ST77XX_BLACK);
    drawtextzero(setupbuffer, ST77XX_WHITE);
    delay(500);
  }
  updateStr();
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  server.begin();
}

void updateStr() {
  char wifiIp[100];
  char buffer[500];
  strcpy(wifiIp, WiFi.localIP().toString().c_str());
  strcpy(buffer, "WIFI CONNECTED.\nWIFI SSID : ");

  tft.fillScreen(ST77XX_BLACK);

  drawtextzero(buffer, ST77XX_WHITE);
  strcpy(buffer, ssid);
  drawtextcontinue(buffer, ST77XX_YELLOW);
  strcpy(buffer, "\nIP : ");
  drawtextcontinue(buffer, ST77XX_WHITE);
  strcpy(buffer, "");
  sprintf(buffer, "%s\n", wifiIp);
  drawtextcontinue(buffer, ST77XX_YELLOW);

  strcpy(buffer, "\nHOME LED : ");
  drawtextcontinue(buffer, ST77XX_WHITE);
  strcpy(buffer, "");
  sprintf(buffer, "%s\n", isLed);
  drawtextcontinue(buffer, strcmp(isLed, "OFF") == 0 ? ST77XX_RED : ST77XX_BLUE);

  strcpy(buffer, "HOME PC : ");
  drawtextcontinue(buffer, ST77XX_WHITE);
  strcpy(buffer, "");
  sprintf(buffer, "%s\n", isPc);
  drawtextcontinue(buffer, strcmp(isPc, "OFF") == 0 ? ST77XX_RED : ST77XX_BLUE);

  strcpy(buffer, "HOME Curtain : ");
  drawtextcontinue(buffer, ST77XX_WHITE);
  strcpy(buffer, "");
  sprintf(buffer, "%s\n", isCurtain);
  drawtextcontinue(buffer, strcmp(isCurtain, "OFF") == 0 ? ST77XX_RED : ST77XX_BLUE);


  strcpy(buffer, "\nSecure Visit : ");
  drawtextcontinue(buffer, ST77XX_WHITE);
  strcpy(buffer, "");
  sprintf(buffer, "%d\n", visitCnt);
  drawtextcontinue(buffer, ST77XX_GREEN);


  strcpy(buffer, "\nNormal Visit : ");
  drawtextcontinue(buffer, ST77XX_WHITE);
  strcpy(buffer, "");
  sprintf(buffer, "%d\n", uvisitCnt);
  drawtextcontinue(buffer, ST77XX_RED);

  //sprintf(buffer,"IP : ");IP : %s\n\nHOME LED : %s\nHOME PC : %s\nHOME Curtain : %s\n\nsecurevisit : %d\n",ssid,wifiIp,isLed,isPc,isCurtain,visitCnt)
}

void loop() {
  WiFiClient client = server.available();  // listen for incoming clients
  char buffer[10];
  if (client) {                     // if you get a client,
    Serial.println("New Client.");  // print a message out the serial port
    String currentLine = "";        // make a String to hold incoming data from the client
    while (client.connected()) {    // loop while the client's connected
      if (client.available()) {     // if there's bytes to read from the client,
        char c = client.read();     // read a byte, then
        Serial.write(c);            // print it out the serial monitor
        if (c == '\n') {            // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println();
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            client.print("Hello.");
            client.println("this is http page from esp32");
            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {  // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        if (currentLine.endsWith("GET /vuln")) { //for test
          visitCnt++;
          client.println("this is vuln page");
          updateStr();
        }
      }
    }

    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}

대단한 건 아니고, 들어가면 문자열을 html 형식으로 출력한다. 외에는 lcd 출력한다고 삽질한 흔적이다.

아무튼 그래서 브라우저로 들어가면,

이런 느낌으로 잘 된다.

이때 패킷이 어떻게 잡혔나 확인해보면,

이렇게 잘 잡힌다. 아아 신기하여라