TOC
Open TOC
0x00 shadowsocks
shadowsocks
使用 socket 代理,拿到数据后将其加密传输到远程服务器,服务器发起数据的请求之后再将数据返回给客户端.想要对shadowsocks
的请求数据进行解析的话,在服务端和本地转发数据的时候都可以捕获解析.为了方便调试,这里选取了本地进行调试.
shadowsocks
的目录结构如下:
- shadowsocks
- - crypto #加密组件
- - asyncdns.py #异步dns查询
- - common.py
- - daemon.py
- - encrypt.py
- - local.py
- - eventloop.py
- - manager.py
- - shell.py
- - server.py
- - tcprelay.py #tcp转发
- - udprelay.py
0x01 trunked 和 gzip
本次要修改的地方就是tcprelay.py
里读取本地数据和远程数据的地方,对于http
请求头没有进行加密可以直接读取到.而远程传输回来的数据里有部分http
请求使用了gzip
压缩.例如某次返回的 header 信息如下:
HTTP/1.1 200 OK Server: nginx/1.4.6 (Ubuntu) Date: Sun, 22 Jan 2017 16:08:38 GMT
Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection:
keep-alive X-Powered-By: PHP/7.0.13 Content-Encoding: gzip
其中Transfer-Encoding: chunked
和Content-Encoding: gzip
指明了 html 的压缩格式为 gzip,并且分段传输.要解析其返回的数据就要先把分段的数据整合起来,再使用 gzip 解压.其中chunked的格式如下:
Chunked-Body = *chunk last-chunk trailer CRLF chunk = chunk-size [
chunk-extension ] CRLF chunk-data CRLF chunk-size = 1*HEX last-chunk = 1*("0") [
chunk-extension ] CRLF chunk-extension= *( ";" chunk-ext-name [ "="
chunk-ext-val ] ) chunk-ext-name = token chunk-ext-val = token | quoted-string
chunk-data = chunk-size(OCTET) trailer = *(entity-header CRLF)
chunked 编码使用若干个 Chunk 组成,由一个标明长度为 0 的 chunk 结束,每个 Chunk 有两部分组成,每个部分用回车换行隔开。在最后一个长度为 0 的 Chunk 中的内容是称为 footer 的内容,是一些没有写的头部内容。所以所谓的 chunked 编码是如下的格式:
如果一个 HTTP 消息(请求消息或应答消息)的 Transfer-Encoding 消息头的值为 chunked,那么,消息体由数量未定的块组成,并以最后一个大小为 0 的块为结束。
每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个 CRLF (回车及换行),然后是数据本身,最后块 CRLF 结束。在一些实现中,块大小和 CRLF 之间填充有白空格(0x20)。
最后一块是单行,由块大小(0),一些可选的填充白空格,以及 CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。消息最后以 CRLF 结尾。
第一个 chunk 数据的字节数+/r/n+第一块 chunk 的数据 +/r/n+第二个 chunk 的数据的字节数+/r/n+第二块 chunk 的数据+n 个 chunk+/r/n+0+/r/n。
0x02 解析数据
#...
if self._is_local:
data = self._encryptor.decrypt(data)
htmlData = data.split('\r\n\r\n')
htmlBody = data[1]
chunks = htmlBody.split('\r\n')
content = '\r\n'.join(chunks[1:3]) #对于内容不太长的一次会返回全部内容,所以去掉chunked的长度和结尾数据即可
# 对gzip格式的解压可以直接使用python的gzip扩展
# import gzip
# from cStringIO import StringIO
compressedstream = StringIO(content)
gzipper = gzip.GzipFile(fileobj=compressedstream)
realHtml = gzipper.read()
运行效果如下
2017-01-23 00:49:38 VERBOSE rec header: HTTP/1.1 200 OK Server: nginx/1.4.6
(Ubuntu) Date: Sun, 22 Jan 2017 16:49:37 GMT Content-Type: text/html;
charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive X-Powered-By:
PHP/7.0.13 Content-Encoding: gzip 2017-01-23 00:49:38 VERBOSE rec content:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PHP多进程|Dolinpa</title>
<meta
name="keywords"
content="php,多进程,进程通信,php扩展,dolinpa,vlean,php,IT"
/>
<meta name="description" content="php多进程的开启和调用。" />
<link rel="stylesheet" href="/theme/simple/css/main.css?ver=2.2" />
<link
rel="alternate"
type="application/rss+xml"
title="Dolinpa"
href="//feed.xml"
/>
</head>
....
</html>