본문 바로가기

etc/Technical Documents

Spoof src-ip TCP 3-way handshaking


일단 제목은 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를 체크하는 기준이 다음 그림과 같기 때문이다.


그러므로, 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이 맺히는 것을 알 수 있다.




'etc > Technical Documents' 카테고리의 다른 글

[Metasploit] android meterpreter 분석  (0) 2014.10.13
peda 유용한 명령어 정리  (0) 2014.09.14
ret to libc  (0) 2014.06.07
.dtors overwrite  (0) 2014.05.23
Fake ebp  (2) 2014.04.24