ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • tcp 패킷 조작 in java (1) - tcp checksum
    네트워크 보안 2018. 11. 13. 11:44

      ARP Spoofing 같은 공격을 할 때 보통 패킷을 훔쳐보는 것뿐만 아니라 패킷조작까지 진행합니다. 패킷조작을 하기 위해서는 고치고 싶은 내용만 바꿀 뿐 아니라 TCP 헤더에 있는 checksum, length, window size, sequence 같은 필드의 값도 수정해주어야 합니다. 이번 글에서는 checksum에 대해서 알아보겠습니다.
      일단 checksum을 알아보기 앞서 tcp 패킷의 구조를 살펴보겠습니다.



    TCP packet 구조


      한 번 OSI 7계층이랑 비교해보았습니다. 


     Ethernet 은 데이터 링크 계층에 속하며 destination MAC, source MAC, type으로 총 14byte의 길이를 갖고 있습니다. 
      IP Header 은 네트워크 계층에 속하며 version, header length, TTL 등 총 20바이트의 길이를 갖고 있습니다.
      TCP Header 은 전송 계층에 속하며 source port, destination port 등 총 20바이트의 길이를 갖고 있습니다.
      TCP Data 는 세션 계층, 표현 계층, 응용 계층의 내용을 담고 있습니다. 만약 tcp로 http를 실현한다면 tcp data에는 http 내용이 들어갑니다.
      물리계층 같은 경우 tcp packet에 존재하긴 하지만 wireshark같은 도구로 분석하기 힘들기에 설명에서 뺐습니다.





    Checksum



      다시 checksum으로 돌아오겠습니다. TCP Header에서 checksum이란 2byte 길이의 필드입니다. 이 checksum의 목적은 패킷이 수정되었는지 확인하는 겁니다. 그렇기에 tcp packet을 변조할 때 무조건 넘어야 할 산입니다. checksum이 어떻게 패킷의 변조를 식별하는지 알아보겠습니다. 

      tcp의 checksum은 IP Header, Tcp Header, Tcp Data가 변했는지 확인합니다. 하지만 IP Header 전체를 다 확인하진 않고 pseudo-header 라는 IP Header에서 Source addr, Destination addr, Zeros, Protocol, TCP length 을 가져와 만든 가짜 헤더를 이용합니다.



    pseudo-header의 구조:




      checksum의 연산은 간단합니다. 처음에는 pseudo-header, tcp header, tcp data를 이어붙입니다. 다음에 2byte 단위로 ADD연산을 차례로 해주고 마지막에 NOT연산을 해주면 됩니다. 간단히 checksum 연산을 그림으로 나타내면 아래와 같습니다.






      이제 Java 코드로 직접 tcp checksum 계산 과정을 실현해보겠습니다. 
      먼저 Pseudo_header 클래스를 만들어줍시다.

    Pseudo_header.java:

    package model; public class Pseudo_header { public byte[] sourceIP = new byte[4]; public byte[] destinationIP = new byte[4]; public byte[] reserved = {0x00}; public byte[] protocol = {0x06}; public byte[] tcpLen = new byte[2]; }

      TCP_header 클래스도 만들어줍시다.

    TCP_header.java:

    package model; public class TCP_header { public byte[] sourcePort = new byte[2]; public byte[] destinationPort = new byte[2]; public byte[] sequence = new byte[4]; public byte[] ackSeq = new byte[4]; public byte[] dataOffsetAndReserv = {0x50}; // flag_res(3bit), flag_non(1bit) public byte[] flag = new byte[1]; // flag_con,flag_ecn,flag_urg,flag_ack,flag_psh,flag_rst,flag_syn,flag_fin 합쳐서 8bit. public byte[] windowSize = new byte[2]; public byte[] checksum = {0x00, 0x00}; public byte[] urgentPtr = {0x00, 0x00}; }

      그다음은 이어붙이는 작업을 해봅시다.

    Main.java:

    byte[] chk = new byte[12+20+tcpdata.length]; //pseudo_header, tcp_header, tcp_data를 붙인 결과 int tcpLen = 20+tcpdata.length; psh.tcpLen[0] = (byte)(tcpLen >> 8 & 0xFF); //pseudo_header의 tcpLen을 갱신해줍시다. psh.tcpLen[1] = (byte)(tcpLen & 0xFF); //밑에는 pseudo_header, tcp_header, tcp_data를 chk에 하나하나 붙여나가는 작업입니다. int index = 0; System.arraycopy(psh.sourceIP, 0, chk, index, 4); index += 4; System.arraycopy(psh.destinationIP, 0, chk, index, 4); index += 4; System.arraycopy(psh.reserved, 0, chk, index, 1); index += 1; System.arraycopy(psh.protocol, 0, chk, index, 1); index += 1; System.arraycopy(psh.tcpLen, 0, chk, index, 2); index += 2; System.arraycopy(tcph.sourcePort, 0, chk, index, 2); index += 2; System.arraycopy(tcph.destinationPort, 0, chk, index, 2); index += 2; System.arraycopy(tcph.sequence, 0, chk, index, 4); index += 4; System.arraycopy(tcph.ackSeq, 0, chk, index, 4); index += 4; System.arraycopy(tcph.dataOffsetAndReserv, 0, chk, index, 1); index += 1; System.arraycopy(tcph.flag, 0, chk, index, 1); index += 1; System.arraycopy(tcph.windowSize, 0, chk, index, 2); index += 2; System.arraycopy(tcph.checksum, 0, chk, index, 2); index += 2; System.arraycopy(tcph.urgentPtr, 0, chk, index, 2); index += 2; System.arraycopy(tcpdata, 0, chk, index, tcpdata.length);



      그다음은 checksum 계산을 해줍시다.

    Main.java:

    int tcp_checksum; tcp_checksum = Util.checksum(chk, chk.length, 0); byte[] tcp_checksumb = new byte[2]; tcp_checksumb[0] = (byte)(tcp_checksum >> 8 & 0xFF); tcp_checksumb[1] = (byte)(tcp_checksum & 0xFF);


    Util.java:

    public static short checksum( byte[] message , int length , int offset ) { // Sum consecutive 16-bit words. int sum = 0 ; while( offset < length - 1 ){ sum += (int) integralFromBytes( message , offset , 2 ); offset += 2 ; } if( offset == length - 1 ){ sum += ( message[offset] >= 0 ? message[offset] : message[offset] ^ 0xffffff00 ) << 8 ; } // Add upper 16 bits to lower 16 bits. sum = ( sum >>> 16 ) + ( sum & 0xffff ); // Add carry sum += sum >>> 16 ; // Ones complement and truncate. return (short) ~sum ; } private static long integralFromBytes( byte[] buffer , int offset , int length ){ long answer = 0 ; while( -- length >= 0 ) { answer = answer << 8 ; answer |= buffer[offset] >= 0 ? buffer[offset] : 0xffffff00 ^ buffer[offset] ; ++ offset ; } return answer ; }


      주의해야 할 점은 ADD연산을 할 때 16bits 단위로 하고 16bits가 넘은 값(carry)을 다시 ADD한다는 점입니다.



      checksum이라는 산을 넘었으니 이제 '길이가 변하지 않는 조건 하에서 패킷변조'가 가능하게 되었습니다. 길이가 변하지 않아야 하는 이유는 tcp packet에서 길이가 변한다면 checksum뿐 아니라  length, window size, sequence 같은 필드도 변하기 때문입니다. 



     시뮬레이션


      이번에 배운 내용만으로 한 번 시뮬레이션을 해보겠습니다.

      아래에서 쓰는 툴은 원래 전송되는 패킷을 가로채서 재전송하는 기능밖에 없는 툴입니다. 여기에 한 번 checksum 계산을 추가시켜 '같은 길이' 조건 하에 패킷변조 기능을 추가시켜보았습니다.

      먼저 공격자 컴퓨터에서 해킹툴을 작동시켜 ARP Spoofing을 성공시킵니다.



      그럼 피해자 컴퓨터에서는 ARP Spoofing 공격으로 자신의 패킷들이 감청되고 있다는 사실도 모른 채 인터넷을 쓰게 됩니다. 

      한 번 'data.txt'라는 파일에 들어가 보겠습니다. 출력 내용을 보니 'youhavebeenhacked'라는 글자가 있습니다.



      사실 'data.txt'는 a라는 문자열로만 가득 차 있는 문서입니다. 하지만 지금 공격자 컴퓨터에서 패킷변조를 진행했기에 길이는 변하지 않았지만 내용은 변한 걸 확인할 수 있습니다.





      다시 한 번 정리를 한다면 checksum 계산만을 이용해 패킷변조를 한다면 '같은 길이'라는 조건 하에서만 변조할 수 있습니다. 다음 시간에는 길이를 자유자재로 바꿀 수 있는 패킷변조를 다루겠습니다. 








    참고 사이트:
    tcp checksum: https://wordpress.youran.me/python-raw-socket-programming
    ARP Spoofing: https://www.youtube.com/watch?v=xHDihS87ND4


    댓글

Designed by Tistory.