일단 제목은 src의 ip를 속여서 target에 tcp connection을 맺을 수 있느냐에 관한 연구였는데,
결론부터 말하자면 거의 불가능 하다고 볼 수 있다.
그래도 이 내용을 읽고 누군가 신박한 해결책을 내 주길 바라면서 적어본다.
[+]python raw_socket 사용
python에서 raw_socket을 사용한 예제 소스를 수정해서, src ip를 조작할 수 있는 프로그램을 만들어 보았다.
'''
Raw sockets on Linux
Silver Moon (m00n.silv3r@gmail.com)
'''
# some imports
import socket, sys
from struct import *
from time import sleep
# checksum functions needed for calculation checksum
def checksum(msg):
s = 0
# loop taking 2 characters at a time
for i in range(0, len(msg), 2):
w = ord(msg[i]) + (ord(msg[i+1]) << 8 )
s = s + w
s = (s>>16) + (s & 0xffff);
s = s + (s >> 16);
#complement and mask to 4 byte short
s = ~s & 0xffff
return s
def send_tcp_packet(source_ip,dest_ip,tcp_source,tcp_dest,tcp_seq,tcp_ack_seq,tcp_syn,tcp_ack):
#create a raw socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
except socket.error , msg:
print 'Socket could not be created. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit()
# tell kernel not to put in headers, since we are providing it, when using IPPROTO_RAW this is not necessary
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# now start constructing the packet
packet = '';
#source_ip = '192.168.91.1'
#dest_ip = '192.168.91.143' # or socket.gethostbyname('www.google.com')
# ip header fields
ip_ihl = 5
ip_ver = 4
ip_tos = 0
ip_tot_len = 0 # kernel will fill the correct total length
ip_id = 54321 #Id of this packet
ip_frag_off = 0
ip_ttl = 255
ip_proto = socket.IPPROTO_TCP
ip_check = 0 # kernel will fill the correct checksum
ip_saddr = socket.inet_aton ( source_ip ) #Spoof the source ip address if you want to
ip_daddr = socket.inet_aton ( dest_ip )
ip_ihl_ver = (ip_ver << 4) + ip_ihl
# the ! in the pack format string means network order
ip_header = pack('!BBHHHBBH4s4s' , ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr)
# tcp header fields
#tcp_source = 12345 # source port
#tcp_dest = 8000 # destination port
#tcp_seq = 454
#tcp_ack_seq = 0
tcp_doff = 5 #4 bit field, size of tcp header, 5 * 4 = 20 bytes
#tcp flags
tcp_fin = 0
#tcp_syn = 1
tcp_rst = 0
tcp_psh = 0
#tcp_ack = 0
tcp_urg = 0
tcp_window = socket.htons (5840) # maximum allowed window size
tcp_check = 0
tcp_urg_ptr = 0
tcp_offset_res = (tcp_doff << 4) + 0
tcp_flags = tcp_fin + (tcp_syn << 1) + (tcp_rst << 2) + (tcp_psh <<3) + (tcp_ack << 4) + (tcp_urg << 5)
# the ! in the pack format string means network order
tcp_header = pack('!HHLLBBHHH' , tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags, tcp_window, tcp_check, tcp_urg_ptr)
user_data = 'Hello, how are you'
# pseudo header fields
source_address = socket.inet_aton( source_ip )
dest_address = socket.inet_aton(dest_ip)
placeholder = 0
protocol = socket.IPPROTO_TCP
tcp_length = len(tcp_header) + len(user_data)
psh = pack('!4s4sBBH' , source_address , dest_address , placeholder , protocol , tcp_length);
psh = psh + tcp_header + user_data;
tcp_check = checksum(psh)
#print tcp_checksum
# make the tcp header again and fill the correct checksum - remember checksum is NOT in network byte order
tcp_header = pack('!HHLLBBH' , tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags, tcp_window) + pack('H' , tcp_check) + pack('!H' , tcp_urg_ptr)
# final full packet - syn packets dont have any data
packet = ip_header + tcp_header + user_data
#Send the packet finally - the port specified has no effect
sleep(0.1)
s.sendto(packet, (dest_ip , 0 )) # put this in a loop if you want to flood the target
send_tcp_packet("255.255.255.255","192.168.91.143",12345,6969,0,0,1,0)
sleep(0.1)
send_tcp_packet("255.255.255.255","192.168.91.143",12345,6969,1,1,0,1)
해당 소스는 리눅스에서 가동시켜야 한다. 윈도우는 왜인지 작동을 안하더라.
당연히, 제대로 connection이 맺히지 않았다.
왜냐면, 3-way의 invaild를 체크하는 기준이 다음 그림과 같기 때문이다.
![](https://t1.daumcdn.net/cfile/tistory/2578B53F544B58A92B)
그러므로, syn패킷을 주었을 때, 되돌아오는 syn ack패킷의 ack 시퀀스를 알아야 보내야 하는 ack패킷을 만들 수 있기 때문이다. 그럼 과연 이 값은 예측 불가능 일까? [예측 불가능 이라고 나는 결론지었다.]
[+]scapy를 사용
그럼 src-ip를 변경하면 syn ack가 변경된 src ip로 가기 때문에, syn ack를 이용해 ack패킷을 만들수가 없다. 그러므로, 그냥 일단 spoofing없이 보내는 패킷만으로 세션이 맺히는지 알고 싶었다. 한번 해 보았다.
scapy는 python 으로 만들어진 강력한 패킷 조작 프로그램이다. 리눅스에서 간단하게
#apt-get install scapy
로 설치 할 수 있으며, 쓰기가 간편하다.
일단 쉘에 scapy라고 실행하면 interactive하게 실행할 수 있는 모드지만, syn과 ack사이의 긴시간이 생기면, target에서 RST가 날라오기 때문에, 스크립트를 통해서 빠르게 실행할 수 있도록 했다.
interactive한 예제는 아래의 동영상을 보고 따라하면 될 꺼 같다.
http://www.youtube.com/watch?v=TLB7GCjn__U
스크립트는 다음과 같이 작성했다.
직관적인 네이밍이라, 금방 이해가 갈 것이다.
#!/usr/bin/python
from scapy.all import *
ip=IP(src="192.168.91.143", dst="192.168.91.1")
TCP_SYN=TCP(sport=1500, dport=6969, flags="S", seq=100)
TCP_SYNACK=sr1(ip/TCP_SYN)
print TCP_SYNACK.seq
my_ack = TCP_SYNACK.seq + 1
TCP_ACK=TCP(sport=1500, dport=6969, flags="A", seq=101, ack=my_ack)
send(ip/TCP_ACK)
이 스크립트를 실행하기 전에 주의 해야 할 것이 있는데,
iptable설정을 통해 스크립트를 실행하는 컴퓨터의 TCP RST패킷을 드롭시켜 줘야한다.
왜냐하면, SYN ACK를 받으면, 열린 소켓이 없어 RST패킷을 target에 보내기 때문이다.
명령은 다음과 같다.
# iptables -A OUTPUT -p tcp --tcp-flags RST RST -j D
이렇게 하고 실행하면, 소켓생성 없이 tcp connection이 맺히는 것을 알 수 있다.