醉梦轩

醉中无日月,梦里有乾坤

0x00 前言

Docker作为现在常用的服务部署方式,日常工作中免不了要做些buildpush之类的操作。为了避免每次都要查文档,这里将一些常用的命令和技巧做一些总结。

以下以Ubuntu 16.04 x64系统作为演示环境。

阅读全文 »

最近在折腾Win10里的WSL的时候,不小心把WSL的目录删除了,然后无论如何再也装不上去。运行bash命令会报以下错误:

1
2
C:\>bash
指定的网络名不再可用。

百度、Google均无解,于是想到使用英文去搜索,终于找到了下面这篇帖子:

https://www.reddit.com/r/bashonubuntuonwindows/comments/aqeu24/i_accidentally_deleted_my_installed_wsl_folder/

以下是解决方法:

1
2
3
4
5
6
7
8
9
10
11
C:\>wslconfig /list
适用于 Linux 的 Windows 子系统:
Ubuntu-18.04 (默认)

C:\>wslconfig /u Ubuntu-18.04
正在注销...

C:\>wslconfig /list
适用于 Linux 的 Windows 子系统没有已安装的分发版。
可以通过访问 Microsoft Store 来安装分发版:
https://aka.ms/wslstore

此时,运行bash命令也正常了

1
2
3
4
C:\>bash
适用于 Linux 的 Windows 子系统没有已安装的分发版。
可以通过访问 Microsoft Store 来安装分发版:
https://aka.ms/wslstore

终于可以正常安装WSL了。

0x00 前言

重打包是一种将非产品代码静态插入到安装包中,从而实现注入测试代码的能力。这种技术可以用于非root手机上无法利用ptrace动态注入被测进程的场景。

除此之外,还可以修改安装包的属性,例如将release包改为debug包等。

重打包需要解决的问题主要有:

  • 如何修改AndroidManifest.xml文件
  • 如何将自己的代码插入到dex
  • 如何让自己的代码逻辑优先执行
  • 如何绕过应用的签名校验逻辑

只有完美解决这几个问题,才能真正实现重打包。

阅读全文 »

0x00 缘起

最近在使用一款Art Hook框架对应用进行Hook的时候发现,函数Hook之后却总是没有被触发,于是怀疑是被dex2oat做了Inline处理。

以下环境以Android 5.0 x86 为例。

阅读全文 »

0x00 前言

squid是一款高性能的代理缓存服务器,常用来部署HTTP(S)代理服务器。本文是在Ubuntu上使用squid部署HTTP(S)代理服务器的方法总结。

使用的Ubuntu版本是:Ubuntu 16.04 x64

0x01 安装和配置

使用如下命令安装squid

1
apt install squid -y

安装后,会在/etc/squid目录下生成默认的配置文件squid.conf,需要对其做一些自定义的修改.

修改默认端口

http_port 3128这行中的3128修改为期望的端口号,比如8080,或是非常用端口,这样可以避免服务被shodan之类的搜索引擎探测到。

允许外部访问

squid默认只能从本地访问,是因为它设置了http_access allow localhost

但正常情况下,我们都是需要从外部访问的,这就需要添加以下两行配置:

1
2
acl net src 0.0.0.0/0
http_access allow net

表示接收任意外部地址。

允许CONNECT所有端口

squid默认只可以CONNECT443端口,如果要开放所有端口,需要注释掉http_access deny CONNECT !SSL_ports这行。

修改安全端口

squid默认策略只允许代理访问以下端口:

1
2
3
4
5
6
7
8
9
10
acl Safe_ports port 80		# http
acl Safe_ports port 21 # ftp
acl Safe_ports port 443 # https
acl Safe_ports port 70 # gopher
acl Safe_ports port 210 # wais
acl Safe_ports port 1025-65535 # unregistered ports
acl Safe_ports port 280 # http-mgmt
acl Safe_ports port 488 # gss-http
acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http

因此,会有部分端口无法访问,直接返回403 Forbidden。如果需要访问这些端口,可以增加以下配置:

1
acl Safe_ports port 1-1024

不允许访问本地网络

squid默认允许访问本地(localhost)服务,但建议去掉#http_access deny to_localhost的注释

允许所有访问

如果觉得以上操作过于繁琐,在不考虑安全性的情况下,也可以修改http_access deny allhttp_access allow all

设置访问密码

为了安全,我们通常会给代理服务器设置密码。

先安装htpasswd工具,使用如下命令:

1
apt install apache2-utils -y

创建密码文件:

1
htpasswd -c /etc/squid/passwd proxy_username

squid.conf中添加以下内容:

1
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd

0x02 启动squid服务

1
systemctl start squid

0x00 前言

之前都是使用py2exe将Python程序打包成可执行文件,但是最近需要打包成Macos上的可执行程序。于是,选择了py2app,但是使用下来发现坑比较多,最终还是放弃了。

于是,将目光转向了PyInstaller,它可以同时支持Windows和Macos,并且使用方法差异也很小。

PyInstaller与py2exe的主要差异(Windows):

  • PyInstaller打包出来的是一个正常的exe;py2exe打包出来的既是exe,也是zip文件,可以进行解压

  • PyInstaller打包出来的程序在运行后会创建一个临时目录,把dll等文件解压到临时目录中;py2exe使用了内存加载dll的技术,可以在不解压dll的情况下直接加载,看上去更优雅一些

阅读全文 »

0x00 前言

在Python中,类也是作为一种对象存在的,因此可以在运行时动态创建类,这也是Python灵活性的一种体现。

本文介绍了如何使用type动态创建类,以及相关的一些使用方法与技巧。

0x01 类的本质

何为类?类是对现实生活中一类具有共同特征的事物的抽象,它描述了所创建的对象共同的属性和方法。在常见的编译型语言(如C++)中,类在编译的时候就已经确定了,运行时是无法动态创建的。那么Python是如何做到的呢?

来看下面这段代码:

1
2
3
4
5
6
class A(object):
pass

print(A)
print(A.__class__)

在Python2中执行结果如下:

1
2
<class '__main__.A'>
<type 'type'>

在Python3中执行结果如下:

1
2
<class '__main__.A'>
<class 'type'>

可以看出,类A的类型是type,也就是说:type实例化后是实例化后是对象

0x02 使用type动态创建类

type的参数定义如下:

type(name, bases, dict)

name: 生成的类名
bases: 生成的类基类列表,类型为tuple
dict: 生成的类中包含的属性或方法

例如:可以使用以下方法创建一个类A

1
2
3
4
cls = type('A', (object,), {'__doc__': 'class created by type'})

print(cls)
print(cls.__doc__)

输出结果如下:

1
2
<class '__main__.A'>
class created by type

可以看出,这样创建的类与静态定义的类基本没有什么差别,使用上还更灵活。

这种方法的使用场景之一是:

有些地方需要传入一个类作为参数,但是类中会用到某些受外界影响的变量;虽然使用全局变量可以解决这个问题,但是比较丑陋。此时,就可以使用这种方法动态创建一个类来使用。

以下是一个使用的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

import socket
try:
import SocketServer
except ImportError:
# python3
import socketserver as SocketServer

class PortForwardingRequestHandler(SocketServer.BaseRequestHandler):
'''处理端口转发请求
'''

def handle(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(self.server) # self.server是在动态创建类时传入的
# 连接目标服务器,并转发数据
# 以下代码省略...

def gen_cls(server):
'''动态创建子类
'''
return type('%s_%s' % (ProxyRequestHandler.__name__, server), (PortForwardingRequestHandler, object), {'server': server})


server = SocketServer.ThreadingTCPServer(('127.0.0.1', 8080), gen_cls(('www.qq.com', 80)))
server.serve_forever()

在上面的例子中,由于目标服务器地址是由用户传入的,而PortForwardingRequestHandler类的实例化是在ThreadingTCPServer里实现的,我们没法控制。因此,使用动态创建类的方法可以很好地解决这个问题。

0x03 使用元类(metaclass

类是实例的模版,而元类是类的模版。通过元类可以创建出类,类的默认元类是type,所有元类必须是type的子类。

下面是元类的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import struct

class MetaClass(type):
def __init__(cls, name, bases, attrd):
super(MetaClass, cls).__init__(name, bases, attrd)

def __mul__(self, num):
return type('%s_Array_%d' % (self.__name__, num), (ArrayTypeBase,), {'obj_type': self, 'array_size': num, 'size': self.size * num})

class IntTypeBase(object):
'''类型基类
'''
__metaclass__ = MetaClass
size = 0
format = '' # strcut格式

def __init__(self, val=0):
if isinstance(val, str): val = int(val)
if not isinstance(val, int):
raise TypeError('类型错误:%s' % type(val))
self._net_order = True # 默认存储的为网络序数据
self.value = val
self._num = 1

def __str__(self):
return '%d(%s)' % (self._val, self.__class__.__name__)

def __cmp__(self, val):
if isinstance(val, IntTypeBase):
return cmp(self.value, val.value)
elif isinstance(val, (int, long)):
return cmp(self.value, val)
elif isinstance(val, type(None)):
return cmp(int(self.value), None)
else:
raise TypeError('类型错误:%s' % type(val))

def __int__(self):
return int(self.value)

def __hex__(self):
return hex(self.value)

def __index__(self):
return self.value

def __add__(self, val):
return int(self.value + val)

def __radd__(self, val):
return int(val + self.value)

def __sub__(self, val):
return self.value - val

def __rsub__(self, val):
return val - self.value

def __mul__(self, val):
return self.value * val

def __div__(self, val):
return self.value / val

def __mod__(self, val):
return self.value % val

def __rshift__(self, val):
return self.value >> val

def __and__(self, val):
return self.value & val

@property
def net_order(self):
return self._net_order

@net_order.setter
def net_order(self, _net_order):
self._net_order = _net_order

@property
def value(self):
return self._val

@value.setter
def value(self, val):
if not isinstance(val, int):
raise TypeError('类型错误:%s' % type(val))
if val < 0: raise ValueError(val)
max_val = 256 ** (self.size) - 1
if val > max_val: raise ValueError('%d超过最大大小%d' % (val, max_val))
self._val = val

def unpack(self, buff, net_order=True):
'''从buffer中提取出数据
'''
if len(buff) < self.size: raise ValueError(repr(buff))
buff = buff[:self.size]
fmt = self.format
if not net_order: fmt = '<' + fmt[1]
self._val = struct.unpack(fmt, buff)[0]
return self._val

def pack(self, net_order=True):
'''返回内存数据
'''
fmt = self.format
if not net_order: fmt = '<' + fmt[1]
return struct.pack(fmt, self._val)

@staticmethod
def cls_from_size(size):
'''从整型大小返回对应的类
'''
if size == 1:
return c_uint8
elif size == 2:
return c_uint16
elif size == 4:
return c_uint32
elif size == 8:
return c_uint64
else:
raise RuntimeError('不支持的整型数据长度:%d' % size)

@classmethod
def unpack_from(cls, str, net_order=True):
obj = cls()
obj.unpack(str, net_order)
return int(obj)

class ArrayTypeBase(object):
'''数组类型基类
'''
def __init__(self, val=''):
init_val = 0
if isinstance(val, int):
init_val = val
else:
val = str(val)
self._obj_array = [self.obj_type(init_val) for _ in range(self.array_size)] # 初始化
self.value = val

def __str__(self):
return str(self.value)

def __repr__(self):
return repr(self.value)

def __getitem__(self, idx):
return self._obj_array[idx].value

def __setitem__(self, idx, val):
self._obj_array[idx].value = val

def __getslice__(self, i, j):
result = [obj.value for obj in self._obj_array[i:j]]
if self.obj_type == c_ubyte:
result = [chr(val) for val in result]
result = ''.join(result)
return result

def __add__(self, oval):
if not isinstance(oval, str):
raise NotImplementedError('%s还不支持%s类型' % (self.__class__.__name__, type(oval)))
return self.value + oval

def __radd__(self, oval):
return oval + self.value

def __iter__(self):
'''迭代器
'''
for i in range(self.length):
yield self[i]

@property
def value(self):
result = [obj.value for obj in self._obj_array]
if self.obj_type == c_ubyte:
result = [chr(val) for val in result]
result = ''.join(result)
return result

@value.setter
def value(self, val):
if isinstance(val, list):
raise NotImplementedError('ArrayType还不支持list')
elif isinstance(val, str):
self.unpack(val)

def unpack(self, buff, net_order=True):
'''
'''
if len(buff) == 0: return
if len(buff) < self.size: raise ValueError('unpack数据长度错误:%d %d' % (len(buff), self.size))
for i in range(self.array_size):
self._obj_array[i].unpack(buff[i * self.obj_type.size:], net_order)

def pack(self, net_order=True):
'''
'''
result = ''
for i in range(self.array_size):
result += self._obj_array[i].pack()
return result

class c_uint8(IntTypeBase):
'''unsigned char
'''
size = 1
format = '!B'

class c_uint16(IntTypeBase):
'''unsigned short
'''
size = 2
format = '!H'

class c_uint32(IntTypeBase):
'''unsigned int32
'''
size = 4
format = '!I'

class c_uint64(IntTypeBase):
'''unsigned int64
'''
size = 8
format = '!Q'


cls = c_ubyte * 5
print(cls)
val = cls(65)
print(val)

以上代码在Python2.7中输出结果如下:

1
2
3
4

<class '__main__.c_ubyte_Array_5'>
AAAAA

在Python3中,metaclass的定义方法做了修改,变成了:

1
2
3
4

class IntTypeBase(object, metaclass=MetaClass):
pass

为了兼容性。可以使用six库中的方法:

1
2
3
4
5
6
7

import six

@six.add_metaclass(MetaClass)
class IntTypeBase(object):
pass

使用元类的优点是可以使用更加优雅的方式创建类,如上面的c_ubyte * 5,提升了代码可读性和技巧性。

0x04 重写__new__方法

每个继承自object的类都有__new__方法,这是个在类实例化时优先调用的方法,时机早于__init__。它返回的类型决定了最终创建出来的对象的类型。

请看以下代码:

1
2
3
4
5
6
7
8
9
10
11

class A(object):
def __new__(self, *args, **kwargs):
return B()

class B(object):
pass

a = A()
print(a)

输出结果如下:

1
<__main__.B object at 0x023576D0>

可以看到,明明实例化的是A,但是返回的对象类型却是B,这里主要就是__new__在起作用。

下面的例子展示了在__new__中动态创建类的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class B(object):
def __init__(self, var):
self._var = var

def test(self):
print(self._var)

class A(object):
def __new__(self, *args, **kwargs):
if len(args) == 1 and isinstance(args[0], type):
return type('%s_%s' % (self.__name__, args[0].__name__), (self, args[0]), {})
else:
return object.__new__(self, *args, **kwargs)

def output(self):
print('output from new class %s' % self.__class__.__name__)

obj = A(B)('Hello World')
obj.test()
obj.output()

结果输出如下:

1
2
Hello World
output from new class A_B

这个例子实现了动态创建两个类的子类,比较适合存在很多类需要排列组合生成N多子类的场景,可以避免要写一堆子类代码的痛苦。

0x05 总结

动态创建类必须要使用type实现,但是,根据不同的使用场景,可以选择不同的使用方法。

这样做对静态分析工具其实是不友好的,因为在运行过程中类型发生了变化。而且,这也会降低代码的可读性,一般情况下也不推荐用户使用这样存在一定技巧性的代码。

0x00 前言

Python和JavaScript中都有生成器Generator)和协程coroutine)的概念。本文通过分析两者在这两种语言上的使用案例,来对比它们的差异。

0x01 Python中的生成器

Python中的生成器简介

使用过Python的同学对生成器的概念应该是很熟悉的,一个经典的例子是使用它生成斐波拉契数列。

1
2
3
4
5
6
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1

输出结果如下:

1
2
3
4
5
6
7
8
>>> for n in fab(5):
... print n
...
1
1
2
3
5

在Python中,使用了yield的函数不再是普通函数,而是一个生成器函数,执行它返回的是一个生成器对象,可以进行迭代,可以调用next函数获取下一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> fab
<function fab at 0x0225E4F0>
>>> fab(5)
<generator object fab at 0x0217BE18>
>>> x=fab(5)
>>> x.next()
1
>>> x.next()
1
>>> x.next()
2
>>> x.next()
3
>>> x.next()
5

yield也支持使用send方法进行参数传递。

1
2
3
4
5
6
7
8
9
10
def gen_test():
n = 1
while True:
n = yield n
print n

g = gen_test()
n = g.next()
for i in range(5):
n = g.send(n * 2)

输出结果为:

1
2
3
4
5
2
4
8
16
32

创建生成器函数后,需要先调用一次next函数,否则程序会报以下错误:

1
TypeError: can't send non-None value to a just-started generator

yield最大的特点是允许代码发生中断,并在调用nextsend时继续往下执行。

Python中使用生成器实现协程

协程是一种通过代码实现的模拟多线程并发的逻辑,其特点是使用一个线程实现了原本需要多个线程才能实现的功能;而且由于避免了多线程切换,提升了程序的性能,甚至去掉了多线程中必不可少的互斥锁。

协程最大的一个特点是用同步的方式写异步代码,提升了代码的可读性,并降低了维护成本。

协程与多线程的主要差别如下:

  1. 协程只有一个线程,多线程有多个线程
  2. 协程中任务(逻辑线程)的切换是在代码中主动进行的;线程的切换是操作系统进行的,时机不可预期
  3. 进程中可以创建的线程数量是有限的,数量多了之后产生的线程切换开销比较大;协程可以创建的任务数量主要受CPU占用率、文件句柄数量等限制

由于Python中GIL的存在,多线程实际上并无法利用到多核CPU的优势。这种情况下使用协程 + 多进程无疑是最优实现方案。

yield天生的特性,为实现协程提供了极大的便利。

Python中使用生成器实现协程的典型库是:tornado。即便是自己实现也不是很复杂,基本原理就是维护一个事件队列,保存生成器对象,不断取出队列前面的生成器对象,去调用send方法,进行参数传递,从而维护了函数调用链。

下面是使用tornado的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

import tornado.gen
import tornado.ioloop
import tornado.tcpclient

@tornado.gen.coroutine
def tcp_client_demo(addr, port):
tcp_client = tornado.tcpclient.TCPClient()
stream = yield tcp_client.connect(addr, port, timeout=30)
stream.write('send data')
buffer = yield stream.read_until('\r\n\r\n')
raise tornado.gen.Return(buffer)

tcp_client_demo('1.1.1.1', 1111)
tornado.ioloop.IOLoop.current().start()

不过tornado中还是有很多地方需要写回调函数的,个人觉得这些地方实现得不是很优雅。

Python从3.5开始支持asyncawait关键字,从而在语言层面支持了协程。但是使用生成器实现协程的兼容性会更好。

0x02 JavaScript中的生成器

JavaScript中的生成器简介

JavaScript中可以使用function*创建生成器函数,这是在ES6规范中提出来的,Chrome从版本39才开始支持这一特性。

使用JavaScript生成斐波拉契数列的代码如下:

1
2
3
4
5
6
7
8
function* fab(max) {
var [n, a, b] = [0, 0, 1];
while(n < max) {
yield b;
[a, b] = [b, a + b];
n++;
}
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
> x=fab(5)
fab {[[GeneratorStatus]]: "suspended"}
> x.next()
Object {value: 1, done: false}
> x.next()
Object {value: 1, done: false}
> x.next()
Object {value: 2, done: false}
> x.next()
Object {value: 3, done: false}
> x.next()
Object {value: 5, done: false}

可以看出,使用方法与Python中是基本一致的,不过,JavaScript中并没有send方法,但是next是可以传参的,相当于结合了Python中nextsend的功能。

JavaScript中使用生成器实现协程

JavaScript天生是一个单线程的环境,一般不能使用阻塞的操作,传统的实现多采用异步回调(callback)方式。但是,这种方式容易导致层层嵌套,变成回调地狱(Callback Hell),阅读和调试都不是很方便。

后来出现了Promise,可以用优雅一些的方法编写异步代码,但是仍然不够优雅。于是出现了基于生成器Promise实现的co库,这个库目前只有200多行代码,可以将生成器函数变成Promise对象,并自动执行。它支持yield一个Promise对象,其效果与asyncawait(Chrome 55开始支持)相似。

co代码链接为:https://github.com/tj/co/blob/master/index.js

关于co的具体介绍可以参考这篇文章

以下是使用co的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function* gen_sleep() {
console.log(new Date());
yield sleep(2000);
console.log(new Date());
}

co(gen_sleep);

执行结果如下:

1
2
3
Wed Jul 18 2018 14:39:44 GMT+0800 (中国标准时间)
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
VM514:8 Wed Jul 18 2018 14:39:47 GMT+0800 (中国标准时间)

这里两次打印时间差了3秒,怀疑是执行误差所致。使用asyncawait也是如此,尚未找到具体原因。

如果只是不断调用gen_sleepnext函数,是不会进行sleep操作的。

使用asyncawait实现以上的功能,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function async_sleep() {
console.log(new Date());
await sleep(2000);
console.log(new Date());
}

async_sleep();

可以看出,这两种方式都可以实现协程的效果,但是后者是语言官方支持,应该会成为主流。

0x03 总结

从上面的例子可以看出,两者对生成器和协程的使用有很多相似之处,可以说是大同小异。在理解了语言的这些特性之后,编写协程代码会更加地轻松。

总的来说就是:语言都是相通的

0x00 前言

MySQL是一款开源的关系型数据库管理系统,大量公司都在使用它,或是在它的基础上做二次开发。有时,我们会需要在Linux系统上部署MySQL服务用于测试。

本文是根据网上的经验分享以及亲自实践的过程总结而来。实验环境为:Ubuntu 16.04 x64。

阅读全文 »

0x00 前言

nginx是一款高性能的Web服务器,可以用作反向代理和负载均衡。随着HTTPS的不断推进,越来越多的网站都开始转到HTTPS方式,HTTP仅仅作为重定向到HTTPS的途径。

本文介绍了如何在Ubuntu 16.04服务器上搭建基于nginx的HTTPS服务器,并且支持SNI。

阅读全文 »