목표 및 목적 : TCP Data 영역을 수정
netfilter를 이용하여 local process로 송수신 되는 IP packet을 모두 netfilter queue로 넘겨야 하는데 이는 iptables 라는 명령어로 가능하게 해준다.
[iptables 설정]
⚡ root@ubuntu ~ iptables -A OUTPUT -j NFQUEUE --queue-num 0
⚡ root@ubuntu ~ iptables -A INPUT -j NFQUEUE --queue-num 0
⚡ root@ubuntu ~ iptables -F
이제 tcp_data_change를 하기 위해서는 데이터가 변경된 후 tcp의 checksum 또한 바꿔줘야한다.
checksum이란?
중복 검사의 한 형태로, 오류 정저을 통해 송신된 자료의 무결성을 보호하는 단순한 방법이다.
기본적인 메시지 구성 요소를 추가하여 결과값을 저장함을써 동작한다.
나중에 누구나 데이터에 같은 작업을 수행할 수 있고, 무결성 검사에 대한 결과를 비교할 수 있으며, 메시지가 손상되지 않았다고 결론을 내릴 수도 있다. (Checksum이 맞아 떨어지는지 확인을 해봄으로서 가능)
ipv4의 경우 checksum이 있고, tcp 또한 checksum이 존재한다.
먼저 ipv4의 checksum을 구하는 방법은 아래와 같다.
[IPv4 header checksum 구하는 공식]
1. version 필드 값 ~ IP 필드 값까지 합한다. // 모든 필드의 값을 더해준다.(여기서 checksum 필드의 값은 0으로 초기화해준 뒤 더한다.)
2. 더한 값의 첫 번째 값을 뒤의 4자리 값과 합산한다.
3. 더한 갑을 2진수로 표기한 후, 1의 보수를 취한다. (0 -> 1 , 1 -> 0)
4. 이후 16진수로 표현하면 그것이 바로 체크섬의 값이 된다.
[code]
checksum = (sum>>16)+(sum&0xffff) -> 올림수 제거
checksum = (checksum^0xffff) -> 1의 보수
쉽게 말해 IP header의 필드 중 checksum을 0으로 초기화 한 뒤 2byte씩 쪼개서 더한다.
이때 2byte
ipv4의 경우 checksum값이 존재한다. (ipv6의 경우는 checksum을 계산하지 않는다고 한다.)
TCP의 경우도 마찮가지인데 더해주는 값이 IP header checksum을 구하는 공식과 다르다.
[TCP header checksum 구하는 공식]
ip header의[출발지 ip] + ip header의[도착지 ip] + NULL byte + ip header의 proto type + tcp header의 길이 + tcp 헤더의 모든 필드
이렇게 IP header에 존재하는 src_ip, dst_ip, proto type등 tcp header의 모든 필드의 값을 더할 뿐더러 더해주는 값이 더 많다.
이점만 빼면 checksum을 구하는 방식, 원리는 같다.
따라서 다음과 같은 pseudo_header를 이용하면 편리하게 구할 수 있다.
[pseudo_header]
#pragma pack(push,1)
struct pseudo_header{
uint32_t src_ip;
uint32_t dst_ip;
uint8_t reserved = 0;
uint8_t proto;
uint16_t tcp_len; // 전체 패킷의 크기에서 ip header의 크기를 뺀 값
};
#pragma pack(pop)
이제 이에 맞게 checksum을 구해주는 함수를 구현하면 다음과 같이 구현할 수 있다.
uint16_t calc_checksum(uint16_t *data, uint32_t len)
{
uint8_t oddbyte = 0;
uint32_t sum = 0;
cout << len << endl;
while(len > 1)
{
sum += ntohs(*data++);
len -= 2;
}
if(len == 1)
{
oddbyte = (uint8_t)*data;
sum += ntohs(oddbyte);
}
sum = (sum >> 16) + (sum & 0xffff);
return (uint16_t)sum;
}
데이터와 그 데이터에 대한 길이를 받고, checksum 값을 구하여 return 해주는 함수이다.
다음은 데이터를 변경하는 함수와 caller 함수의 모습이다.
[replaceString()]
string replaceString(string subject, const string &search, const string &replace)
{
size_t pos = 0;
while((pos = subject.find(search, pos)) != string::npos)
{
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
return subject;
}
[caller]
string tmp_data = (char *)packet;
string str1("hacking");
string str2("HOOKING");
tmp_data = replaceString(tmp_data, str1, str2);
// change data in copied original packet( change_packet )
memcpy((change_data + ip_tcp_len), tmp_data.c_str(), (ret - ip_tcp_len));
들어온 data를 string 변수에 넣어준 뒤 찾고 싶은 문자열과 변경할 문자열을 지정해주고 replaceString()를 호출하여 데이터를 변경해준다.
[결과물]
[ETC Ref, TIP]
[Tip] 데이터를 확인하고 싶을 때!
/* Ubuntu 에서 c++ string 을 출력하려고 printf("%s", str); 을 했을 때 문자가 깨져서 출력되는 경우가 있는데 이 경우 cout << str << endl; 로 대체 하면 된다. */
=> 간단히 말해 printf() 사용 x -> cout 사용 o
[Tip] libnet-headers.h에서 원하는 헤더 찾기
vi libnet-headers.h
:/[문자열]