From 2e3e6f11c5b41e3eb9d11442d8a9272af8c8fc14 Mon Sep 17 00:00:00 2001 From: firmianay Date: Sun, 15 Apr 2018 15:50:03 +0800 Subject: [PATCH] update 4.14_glibc_tcache --- doc/3.3.6_heap_exploit_1.md | 2 +- doc/4.14_glibc_tcache.md | 290 ++++++++++++++++++ .../6.1.10_pwn_0ctf2017_babyheap2017/exp.py | 2 + .../exp.py | 2 + .../6.1.14_pwn_32c3ctf2015_readme/exp.py | 2 + src/writeup/6.1.1_pwn_hctf2016_brop/exp.py | 2 + src/writeup/6.1.2_pwn_njctf2017_pingme/exp.py | 2 + .../exp_use_dynelf.py | 2 + .../exp_use_ret2dl-resolve.py | 2 + .../exp.py | 2 + .../exp.py | 2 + .../6.1.7_pwn_0ctf2015_freenote/exp.py | 2 + src/writeup/6.1.8_pwn_dctf2017_flex/exp.py | 2 + .../6.1.9_pwn_rhme3_exploitation/exp.py | 2 + .../6.2.1_re_xhpctf2017_dont_panic/exp_gdb.py | 2 + .../6.2.1_re_xhpctf2017_dont_panic/exp_pin.py | 2 + .../6.2.3_re_codegate2017_angrybird/exp.py | 2 + .../6.2.4_re_csawctf2015_wyvern/exp_pin.py | 2 + .../6.2.4_re_csawctf2015_wyvern/exp_re.py | 2 + .../6.2.5_re_picoctf2014_baleful/exp_pin.py | 2 + .../exp_pin_len.py | 2 + 21 files changed, 329 insertions(+), 1 deletion(-) diff --git a/doc/3.3.6_heap_exploit_1.md b/doc/3.3.6_heap_exploit_1.md index a70fad8..fcd1028 100644 --- a/doc/3.3.6_heap_exploit_1.md +++ b/doc/3.3.6_heap_exploit_1.md @@ -1120,7 +1120,7 @@ Freeing the overwritten pointer. Now the next malloc will return the region of our fake chunk at 0x7ffc782dae00, which will be 0x7ffc782dae10! malloc(0x10): 0x7ffc782dae10 ``` -house-of-spirit 是一种 fastbins 攻击方法,通过构造 fake chunk,然后将其 free 掉,就可以在下一次 malloc 时返回 fake chunk 的地址,即任意我们可控的区域。house-of-spirit 是一种通过堆的 fast bin 机制来辅助栈溢出的方法,一般的栈溢出漏洞的利用都希望能够覆盖函数的返回地址以控制 EIP 来劫持控制流,但如果栈溢出的长度无法覆盖返回地址,同时却可以覆盖栈上的一个即将被 free 的堆指针,此时可以将这个指针改写为栈上的地址并在相应位置构造一个 fast bin 块的元数据,接着在 free 操作时,这个栈上的堆块被放到 fast bin 中,下一次 malloc 对应的大小时,由于 fast bin 的先进后出机制,这个栈上的堆块被返回给用户,再次写入时就科恩给你造成返回地址的改写。所以利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个 fake chunk。所以 fake chunk 的伪造是关键。 +house-of-spirit 是一种 fastbins 攻击方法,通过构造 fake chunk,然后将其 free 掉,就可以在下一次 malloc 时返回 fake chunk 的地址,即任意我们可控的区域。house-of-spirit 是一种通过堆的 fast bin 机制来辅助栈溢出的方法,一般的栈溢出漏洞的利用都希望能够覆盖函数的返回地址以控制 EIP 来劫持控制流,但如果栈溢出的长度无法覆盖返回地址,同时却可以覆盖栈上的一个即将被 free 的堆指针,此时可以将这个指针改写为栈上的地址并在相应位置构造一个 fast bin 块的元数据,接着在 free 操作时,这个栈上的堆块被放到 fast bin 中,下一次 malloc 对应的大小时,由于 fast bin 的先进后出机制,这个栈上的堆块被返回给用户,再次写入时就可能造成返回地址的改写。所以利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个 fake chunk。所以 fake chunk 的伪造是关键。 首先 malloc(1) 用于初始化内存环境,然后在 fake chunk 区域伪造出两个 chunk。另外正如上面所说的,需要一个传递给 free 函数的可以被修改的指针,无论是通过栈溢出还是其它什么方式: ``` diff --git a/doc/4.14_glibc_tcache.md b/doc/4.14_glibc_tcache.md index 5a37531..c33d915 100644 --- a/doc/4.14_glibc_tcache.md +++ b/doc/4.14_glibc_tcache.md @@ -1,7 +1,297 @@ # 4.14 glibc tcache 机制 +- [tcache](#tcache) +- [安全性分析](#安全性分析) +- [CTF 实例](#ctf-实例) - [参考资料](#参考资料) +## tcache +tcache 全名 thread local caching,它为每个线程创建一个缓存(cache),从而实现无锁的分配算法,有不错的性能提升。libc-2.26 正式提供了该机制,并默认开启,具体可以查看这次 [commit](https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc)。 + +#### 数据结构 +glibc 在编译时使用 `USE_TCACHE` 条件来开启 tcache 机制,并定义了下面一些东西: +```c +#if USE_TCACHE +/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */ +# define TCACHE_MAX_BINS 64 +# define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1) + +/* Only used to pre-fill the tunables. */ +# define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ) + +/* When "x" is from chunksize(). */ +# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT) +/* When "x" is a user-provided size. */ +# define usize2tidx(x) csize2tidx (request2size (x)) + +/* With rounding and alignment, the bins are... + idx 0 bytes 0..24 (64-bit) or 0..12 (32-bit) + idx 1 bytes 25..40 or 13..20 + idx 2 bytes 41..56 or 21..28 + etc. */ + +/* This is another arbitrary limit, which tunables can change. Each + tcache bin will hold at most this number of chunks. */ +# define TCACHE_FILL_COUNT 7 +#endif +``` +值得注意的比如每个线程默认使用 64 个单链表结构的 bins,每个 bins 最多存放 7 个 chunk。chunk 的大小在 64 位机器上以 16 字节递增,从 24 到 1032 字节。32 位机器上则是以 8 字节递增,从 12 到 512 字节。所以 tcache bin 只用于存放 non-large 的 chunk。 + +然后引入了两个新的数据结构,`tcache_entry` 和 `tcache_perthread_struct`: +```c +/* We overlay this structure on the user-data portion of a chunk when + the chunk is stored in the per-thread cache. */ +typedef struct tcache_entry +{ + struct tcache_entry *next; +} tcache_entry; + +/* There is one of these for each thread, which contains the + per-thread cache (hence "tcache_perthread_struct"). Keeping + overall size low is mildly important. Note that COUNTS and ENTRIES + are redundant (we could have just counted the linked list each + time), this is for performance reasons. */ +typedef struct tcache_perthread_struct +{ + char counts[TCACHE_MAX_BINS]; + tcache_entry *entries[TCACHE_MAX_BINS]; +} tcache_perthread_struct; + +static __thread tcache_perthread_struct *tcache = NULL; +``` +tcache_perthread_struct 包含一个数组 entries,用于放置 64 个 bins,数组 counts 存放每个 bins 中的 chunk 数量。每个被放入相应 bins 中的 chunk 都会在其用户数据中包含一个 tcache_entry(FD指针),指向同 bins 中的下一个 chunk,构成单链表。 + +tcache 初始化操作如下: +```c +static void +tcache_init(void) +{ + mstate ar_ptr; + void *victim = 0; + const size_t bytes = sizeof (tcache_perthread_struct); + + if (tcache_shutting_down) + return; + + arena_get (ar_ptr, bytes); + victim = _int_malloc (ar_ptr, bytes); + if (!victim && ar_ptr != NULL) + { + ar_ptr = arena_get_retry (ar_ptr, bytes); + victim = _int_malloc (ar_ptr, bytes); + } + + + if (ar_ptr != NULL) + __libc_lock_unlock (ar_ptr->mutex); + + /* In a low memory situation, we may not be able to allocate memory + - in which case, we just keep trying later. However, we + typically do this very early, so either there is sufficient + memory, or there isn't enough memory to do non-trivial + allocations anyway. */ + if (victim) + { + tcache = (tcache_perthread_struct *) victim; + memset (tcache, 0, sizeof (tcache_perthread_struct)); + } + +} +``` + +#### 使用 +触发在 tcache 中放入 chunk 的操作: +- free 时:在 fastbin 的操作之前进行,如果 chunk size 符合要求,并且对应的 bins 还未装满,则将其放进去。 +```c +#if USE_TCACHE + { + size_t tc_idx = csize2tidx (size); + + if (tcache + && tc_idx < mp_.tcache_bins + && tcache->counts[tc_idx] < mp_.tcache_count) + { + tcache_put (p, tc_idx); + return; + } + } +#endif +``` +- malloc 时:有三个地方会触发。 + - 如果从 fastbin 中成功返回了一个需要的 chunk,那么对应 fastbin 中的其他 chunk 会被放进相应的 tcache bin 中,直到上限。需要注意的是 chunks 在 tcache bin 的顺序和在 fastbin 中的顺序是反过来的。 + + ```c + #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. */ + while (tcache->counts[tc_idx] < mp_.tcache_count + && (tc_victim = *fb) != NULL) + { + if (SINGLE_THREAD_P) + *fb = tc_victim->fd; + else + { + REMOVE_FB (fb, pp, tc_victim); + if (__glibc_unlikely (tc_victim == NULL)) + break; + } + tcache_put (tc_victim, tc_idx); + } + } + #endif + ``` + - smallbin 中的情况与 fastbin 相似,双链表中的剩余 chunk 会被填充到 tcache bin 中,直到上限。 + ```c + #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; + + tcache_put (tc_victim, tc_idx); + } + } + } + #endif + ``` + - binning code(chunk合并等其他情况)中,每一个符合要求的 chunk 都会优先被放入 tcache,而不是直接返回(除非tcache被装满)。寻找结束后,tcache 会返回其中一个。 + ```c + #if USE_TCACHE + /* Fill cache first, return to user only if cache fills. + We may return one of these chunks later. */ + if (tcache_nb + && tcache->counts[tc_idx] < mp_.tcache_count) + { + tcache_put (victim, tc_idx); + return_cached = 1; + continue; + } + else + { + #endif + ``` + +触发从 tcache 中取出 chunk 的操作: +- 在 `__libc_malloc()` 调用 `_int_malloc()` 之前,如果 tcache bin 中有符合要求的 chunk,则直接将它返回。 +```c +#if USE_TCACHE + /* int_free also calls request2size, be careful to not pad twice. */ + size_t tbytes; + checked_request2size (bytes, tbytes); + size_t tc_idx = csize2tidx (tbytes); + + MAYBE_INIT_TCACHE (); + + DIAG_PUSH_NEEDS_COMMENT; + if (tc_idx < mp_.tcache_bins + /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */ + && tcache + && tcache->entries[tc_idx] != NULL) + { + return tcache_get (tc_idx); + } + DIAG_POP_NEEDS_COMMENT; +#endif +``` +- bining code 中,如果在 tcache 中放入 chunk 达到上限,则会直接返回最后一个 chunk。 + ```c + #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 + ``` + 当然默认情况下没有限制,所以这段代码也不会执行: + ```c + .tcache_unsorted_limit = 0 /* No limit. */ + ``` +- binning code 结束后,如果没有直接返回(如上),那么如果有至少一个符合要求的 chunk 被找到,则返回最后一个。 +```c +#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 +``` + +另外还需要注意的是 tcache 中的 chunk 不会被合并,无论是相邻 chunk,还是 chunk 和 top chunk。因为这些 chunk 会被标记为 inuse。 + + +## 安全性分析 +`tcache_put()` 和 `tcache_get()` 分别用于从单链表中放入和取出 chunk: +```c +/* Caller must ensure that we know tc_idx is valid and there's room + for more chunks. */ +static __always_inline void +tcache_put (mchunkptr chunk, size_t tc_idx) +{ + tcache_entry *e = (tcache_entry *) chunk2mem (chunk); + assert (tc_idx < TCACHE_MAX_BINS); + e->next = tcache->entries[tc_idx]; + tcache->entries[tc_idx] = e; + ++(tcache->counts[tc_idx]); +} + +/* Caller must ensure that we know tc_idx is valid and there's + available chunks to remove. */ +static __always_inline void * +tcache_get (size_t tc_idx) +{ + tcache_entry *e = tcache->entries[tc_idx]; + assert (tc_idx < TCACHE_MAX_BINS); + assert (tcache->entries[tc_idx] > 0); + tcache->entries[tc_idx] = e->next; + --(tcache->counts[tc_idx]); + return (void *) e; +} +``` +可以看到注释部分,它假设调用者已经对参数进行了有效性检查,然而由于对 tcache 的操作在 free 和 malloc 中往往都处于很靠前的位置,导致原来的许多有效性检查都被无视了。这样做虽然有利于提升执行效率,但对安全性造成了负面影响。 + +#### tcache_house_of_spirit + +#### tcache_overlapping_chunks + +#### tcache_poisoning + +#### tcache_fastbin_dup + +这一节的代码可以在[这里](../src/Others/4.14_glibc_tcache)找到。其他的一些情况可以参考章节 3.3.6。 + + +## CTF 实例 +在最近的 CTF 中,已经开始尝试使用 libc-2.26,比如章节 6.1.15 中的例子。 + + ## 参考资料 - [thread local caching in glibc malloc](http://tukan.farm/2017/07/08/tcache/) +- [MallocInternals](https://sourceware.org/glibc/wiki/MallocInternals) diff --git a/src/writeup/6.1.10_pwn_0ctf2017_babyheap2017/exp.py b/src/writeup/6.1.10_pwn_0ctf2017_babyheap2017/exp.py index fc537d7..041f6c3 100644 --- a/src/writeup/6.1.10_pwn_0ctf2017_babyheap2017/exp.py +++ b/src/writeup/6.1.10_pwn_0ctf2017_babyheap2017/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * io = remote('127.0.0.1', 10001) diff --git a/src/writeup/6.1.13_pwn_34c3ctf2017_readme_revenge/exp.py b/src/writeup/6.1.13_pwn_34c3ctf2017_readme_revenge/exp.py index 217ac6c..9fcac06 100644 --- a/src/writeup/6.1.13_pwn_34c3ctf2017_readme_revenge/exp.py +++ b/src/writeup/6.1.13_pwn_34c3ctf2017_readme_revenge/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * io = process('./readme_revenge') diff --git a/src/writeup/6.1.14_pwn_32c3ctf2015_readme/exp.py b/src/writeup/6.1.14_pwn_32c3ctf2015_readme/exp.py index 3bba4bb..cb28204 100644 --- a/src/writeup/6.1.14_pwn_32c3ctf2015_readme/exp.py +++ b/src/writeup/6.1.14_pwn_32c3ctf2015_readme/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * io = remote("127.0.0.1", 10001) diff --git a/src/writeup/6.1.1_pwn_hctf2016_brop/exp.py b/src/writeup/6.1.1_pwn_hctf2016_brop/exp.py index 0f7842c..88ed989 100644 --- a/src/writeup/6.1.1_pwn_hctf2016_brop/exp.py +++ b/src/writeup/6.1.1_pwn_hctf2016_brop/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * #context.log_level = 'debug' diff --git a/src/writeup/6.1.2_pwn_njctf2017_pingme/exp.py b/src/writeup/6.1.2_pwn_njctf2017_pingme/exp.py index cd50570..84d06c2 100644 --- a/src/writeup/6.1.2_pwn_njctf2017_pingme/exp.py +++ b/src/writeup/6.1.2_pwn_njctf2017_pingme/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * # context.log_level = 'debug' diff --git a/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py b/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py index 7bfb640..387ad6c 100644 --- a/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py +++ b/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_dynelf.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * # context.log_level = 'debug' diff --git a/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_ret2dl-resolve.py b/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_ret2dl-resolve.py index 7dc32b2..baf17a8 100644 --- a/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_ret2dl-resolve.py +++ b/src/writeup/6.1.3_pwn_xdctf2015_pwn200/exp_use_ret2dl-resolve.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * # context.log_level = 'debug' diff --git a/src/writeup/6.1.4_pwn_backdoorctf2017_fun_signals/exp.py b/src/writeup/6.1.4_pwn_backdoorctf2017_fun_signals/exp.py index d0c817c..e6ef882 100644 --- a/src/writeup/6.1.4_pwn_backdoorctf2017_fun_signals/exp.py +++ b/src/writeup/6.1.4_pwn_backdoorctf2017_fun_signals/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * elf = ELF('./funsignals_player_bin') diff --git a/src/writeup/6.1.5_pwn_grehackctf2017_beerfighter/exp.py b/src/writeup/6.1.5_pwn_grehackctf2017_beerfighter/exp.py index 55f4068..b0761c4 100644 --- a/src/writeup/6.1.5_pwn_grehackctf2017_beerfighter/exp.py +++ b/src/writeup/6.1.5_pwn_grehackctf2017_beerfighter/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * elf = ELF('./game') diff --git a/src/writeup/6.1.7_pwn_0ctf2015_freenote/exp.py b/src/writeup/6.1.7_pwn_0ctf2015_freenote/exp.py index c4466bb..2553f72 100644 --- a/src/writeup/6.1.7_pwn_0ctf2015_freenote/exp.py +++ b/src/writeup/6.1.7_pwn_0ctf2015_freenote/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * io = process(['./freenote'], env={'LD_PRELOAD':'./libc.so.6_1'}) diff --git a/src/writeup/6.1.8_pwn_dctf2017_flex/exp.py b/src/writeup/6.1.8_pwn_dctf2017_flex/exp.py index 9354994..19b9cc4 100644 --- a/src/writeup/6.1.8_pwn_dctf2017_flex/exp.py +++ b/src/writeup/6.1.8_pwn_dctf2017_flex/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * io = remote('127.0.0.1', 10001) diff --git a/src/writeup/6.1.9_pwn_rhme3_exploitation/exp.py b/src/writeup/6.1.9_pwn_rhme3_exploitation/exp.py index bf0ef41..80f9ad0 100644 --- a/src/writeup/6.1.9_pwn_rhme3_exploitation/exp.py +++ b/src/writeup/6.1.9_pwn_rhme3_exploitation/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + from pwn import * # context.log_level = 'debug' diff --git a/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_gdb.py b/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_gdb.py index 151a51e..8e5cf95 100644 --- a/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_gdb.py +++ b/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_gdb.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + #HXP CTF 2017 - dont_panic 100 pts #Writeup link : https://rce4fun.blogspot.com/2017/11/hxp-ctf-2017-dontpanic-reversing-100.html #Souhail Hammou diff --git a/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_pin.py b/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_pin.py index e0ef12b..f01a494 100644 --- a/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_pin.py +++ b/src/writeup/6.2.1_re_xhpctf2017_dont_panic/exp_pin.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import os def get_count(flag): diff --git a/src/writeup/6.2.3_re_codegate2017_angrybird/exp.py b/src/writeup/6.2.3_re_codegate2017_angrybird/exp.py index b5a8968..c18ca94 100644 --- a/src/writeup/6.2.3_re_codegate2017_angrybird/exp.py +++ b/src/writeup/6.2.3_re_codegate2017_angrybird/exp.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import angr main = 0x004007da diff --git a/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_pin.py b/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_pin.py index b77e440..227a79b 100644 --- a/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_pin.py +++ b/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_pin.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import os def get_count(flag): diff --git a/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_re.py b/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_re.py index 38ed801..527dec4 100644 --- a/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_re.py +++ b/src/writeup/6.2.4_re_csawctf2015_wyvern/exp_re.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + array = [0x64, 0xd6, 0x10a, 0x171, 0x1a1, 0x20f, 0x26e, 0x2dd, 0x34f, 0x3ae, 0x41e, 0x452, 0x4c6, 0x538, 0x5a1, 0x604, 0x635, 0x696, 0x704, 0x763, 0x7cc, diff --git a/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin.py b/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin.py index 119d14a..43145aa 100644 --- a/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin.py +++ b/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import os def get_count(flag): diff --git a/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin_len.py b/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin_len.py index 44fa73c..8e02f6f 100644 --- a/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin_len.py +++ b/src/writeup/6.2.5_re_picoctf2014_baleful/exp_pin_len.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import os def get_count(flag):