TCP-IPスタックとの連携
- QEMU + LLVM って感じ
-
User emulation でとりあえず →stack guard に関する記述がないので Linux を直接改造する
- TCP/IP スタックはどこに実装されているか?
- Canary 値はどうやって取得するか?
fs:0x28
glibc
sysdeps/x86_64/nptl/tls.h
typedef struct
{
...
uintptr_t stack_guard;
uintptr_t pointer_guard;
...
} tcbhead_t;
fs:0x28
としてアクセスできる
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
...
- /* The stack guard goes into the TCB, so initialize it early. */
- ARCH_SETUP_TLS ();
- ...
- /* Set up the stack checker's canary. */
- uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
# ifdef THREAD_SET_STACK_GUARD
- THREAD_SET_STACK_GUARD (stack_chk_guard);
# else
- `__stack_chk_guard = stack_chk_guard;`
# endif
...
static inline uintptr_t __attribute__ ((always_inline))
_dl_setup_stack_chk_guard (void *dl_random)
{
union
{
uintptr_t num;
unsigned char bytessizeof (uintptr_t);
} ret;
/* We need in the moment only 8 bytes on 32-bit platforms and 16
- bytes on 64-bit platforms. Therefore we can use the data
- directly and not use the kernel-provided data to seed a PRNG. */
- memcpy (ret.bytes, dl_random, sizeof (ret));
- #if BYTE_ORDER == LITTLE_ENDIAN
- ret.num &= ~(uintptr_t) 0xff;
#elif BYTE_ORDER == BIG_ENDIAN
ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));
#else
`#error "BYTE_ORDER unknown"`
#endif
return ret.num;
}
- `sysdeps/generic/dl-osinfo.h`にも同様の記述
- ↓が↑の`_dl_random`のことなのかはわからないが、kernelから渡されるらしい
- どうやって?
- code:sysdeps/generic/ldsodefs.h
- /* Random data provided by the kernel. */
- extern void *_dl_random attribute_hidden attribute_relro;
- code:elf/dl-sysdep.c
- ElfW(Addr)
- _dl_sysdep_start (void **start_argptr,
- ...
- case AT_RANDOM:
- _dl_random = (void *) av->a_un.a_val;
- ATフィールドから読み出すだけっぽい ELF固有の処理でもないしなあ
- gdbで確認すると同じ値だった
- ただしCanaryにセットされるのはAT_RANDOMの一部
- AT_RANDOM自身は128bit(16byte)ある←`$ man getauxval`参照
- `ElfW(auxv_t) *av`→マクロを経て`Elf32/64_auxv_t` 構造体
- カーネルから渡されるというのは多分これ経由で`_dl_random`に代入されるって意味だと思う
- `elf/elf.h`
- ...
- /* Auxiliary vector. */
- /* This vector is normally only used by the program interpreter. The
- usual definition in an ABI supplement uses the name auxv_t. The
- vector is not usually defined in a standard <elf.h> file, but it
- can't hurt. We rename it to avoid conflicts. The sizes of these
- types are an arrangement between the exec server and the program
- interpreter, so we don't fully specify them here. */
- typedef struct
- {
- uint32_t a_type; /* Entry type */
- union
- {
- uint32_t a_val; /* Integer value */
- /* We use to have pointer elements added here. We cannot do that,
- though, since it does not work when using 32-bit definitions
- on 64-bit platforms and vice versa. */
- } a_un;
- } Elf32_auxv_t;
- Linux
```c:arch/x86/include/asm/stackprotector.h
/*
* Initialize the stackprotector canary value.
*
* NOTE: this must only be called from functions that never return,
* and it must always be inlined.
*/
static __always_inline void boot_init_stack_canary(void)
{
u64 canary;
u64 tsc;
...
get_random_bytes(&canary, sizeof(canary));
tsc = rdtsc();
canary += tsc + (tsc << 32UL);
canary &= CANARY_MASK;
current->stack_canary = canary;
#ifdef CONFIG_X86_64
this_cpu_write(irq_stack_union.stack_canary, canary);
#else
this_cpu_write(stack_canary.canary, canary);
#endif
}
-
irq_stack_union.stack_canary
stack_canary.canary
はそれぞれgs:40
gs:20
を指す- union なり struct なり型を作ってから変数名と一緒に
DECLARE_PER_CPU_FIRST
マクロに渡すと extern で宣言してくれる- per-CPU variable
-
code:arch/x86/include/asm/processor.h
#ifdef CONFIG_X86_64 DECLARE_PER_CPU(struct orig_ist, orig_ist);
union irq_stack_union {- char irq_stackIRQ_STACK_SIZE;
- /*
- GCC hardcodes the stack canary as %gs:40. Since the
- irq_stack is the object at %gs:0, we reserve the bottom
- 48 bytes of the irq stack for the canary. */
- struct {
- char gs_base40;
- unsigned long stack_canary;
- };
-
};
- DECLARE_PER_CPU_FIRST(union irq_stack_union, irq_stack_union) __visible;
- DECLARE_INIT_PER_CPU(irq_stack_union);
…
#else /_ X86_64 _/ #ifdef CONFIG_STACKPROTECTOR /*
- Make sure stack canary segment base is cached-aligned:
- “For Intel Atom processors, avoid non zero segment base address
- that is not aligned to cache line boundary at all cost.”
-
(Optim Ref Manual Assembly/Compiler Coding Rule 15.) */
- struct stack_canary {
- char __pad20; /_ canary at %gs:20 _/
- unsigned long canary;
- };
- DECLARE_PER_CPU_ALIGNED(struct stack_canary, stack_canary);
- #endif
- union なり struct なり型を作ってから変数名と一緒に
- code:init/main.c
- asmlinkage **visible void **init start_kernel(void)
- …
- boot_init_stack_canary();
- asmlinkage **visible void **init start_kernel(void)
-
Linux サイドでも canary を設定する処理があるがこれはカーネル自身に対してのみ適用され、ユーザ空間のプログラムへの Canary の挿入を glibc に任せているっぽい
- AT フィールドは QEMU の User mode emulation でもセットできる
linux-user/elfload.c
#define NEW_AUX_ENT(id, val) do { \
put_user_ual(id, u_auxv); u_auxv += n; \
put_user_ual(val, u_auxv); u_auxv += n; \
} while(0)
...
NEW_AUX_ENT(AT_RANDOM, (abi_ulong) u_rand_bytes);
fs/binfmt_elf.c
#define NEW_AUX_ENT(id, val) \
do { \
elf_infoei_index++ = id; \
elf_infoei_index++ = val; \
} while (0)
...
NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes);
elf_addr_t
とあるようにランダムな値ではなくそれが入っているアドレスが入る- しかしそのアドレスもユーザー領域もスタック上にあるので(
__copy_to_user
)、読めてしまう- CTF テクになっているらしい: http://pwn.hatenadiary.jp/entry/2017/08/12/002518
- gdb で実験したら読めた
#include <stdio.h>
#include <sys/auxv.h>
int main() {
char buf32;
gets(buf);
printf("AT_RANDOM: %p\n", getauxval(AT_RANDOM));
return 0;
}
- ところでこれPRNG seeding以外にも使ってんじゃん
- code:fs/binfmt_elf.c
- /*
- * Generate 16 random bytes for userspace PRNG seeding.
- */
- get_random_bytes(k_rand_bytes, sizeof(k_rand_bytes));
- u_rand_bytes = (elf_addr_t __user *)
- STACK_ALLOC(p, sizeof(k_rand_bytes));
- if (__copy_to_user(u_rand_bytes, k_rand_bytes, sizeof(k_rand_bytes)))
- return -EFAULT;
- TCP/IPプロトコル・スタックにも渡す
- 親のプロセスとかはどうしよう
- とりあえず自分のプロセスのCanaryに対してのみ検査する
- socket()を追う
- libcの`socket()`から取ってきて覚えておく
- `send(), sendto(), sendmsg()`するたびにTLSのCanaryを見に行く
- インラインアセンブラでCanaryを取ってきて、bufに含まれていれば`__fortify_fail`
- THREAD_GETMEM使ってインラインアセンブラ書かずに取ってこられる
- ↓と同じ要領でやればいい
- code:sysdeps/x86_64/nptl/tls.h
- /* Set the stack guard field in TCB head. */
- # define THREAD_SET_STACK_GUARD(value) \
- THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)
- # define THREAD_COPY_STACK_GUARD(descr) \
- ((descr)->header.stack_guard \
- = THREAD_GETMEM (THREAD_SELF, header.stack_guard))
- code:nptl/descr.h
- /* Thread descriptor data structure. */
- struct pthread
- {
- union
- {
- #if !TLS_DTV_AT_TP
- /* This overlaps the TCB as used for TLS without threads (see tls.h). */
- tcbhead_t header;
- あれーなんかないぞ
- code:bash
- ../sysdeps/unix/sysv/linux/send.c: In function ‘__libc_send’:
- ../sysdeps/unix/sysv/linux/send.c:25:3: error: implicit declaration of function ‘__filter_canary’; did you mean ‘__builtin_carg’? -Werror=implicit-function-declaration
- __filter_canary(buf, len);
- ^~~~~~~~~~~~~~~
- __builtin_carg
- 結局よくわからんので`send()`関数に直接ねじ込んだ
- あとでこのカスタムビルドしたlibcを使ってやってみよう
- OSのレイヤーでやるならsend系システムコールをいじればよさそう
- send, sendto, sendmsg
- 余裕があったらsendmmsg, sendfile, sendfile64
- `linux-user/`で`__NR_***`みたいな感じで適当に探る
- code:linux-user/syscall.c
- // in do_syscall()
- #ifdef TARGET_NR_send
- case TARGET_NR_send:
- ret = do_sendto(arg1, arg2, arg3, arg4, 0, 0);
- break;
- #endif
- #ifdef TARGET_NR_sendmsg
- case TARGET_NR_sendmsg:
- ret = do_sendrecvmsg(arg1, arg2, arg3, 1);
- break;
- #endif
- #ifdef TARGET_NR_sendmmsg
- case TARGET_NR_sendmmsg:
- ret = do_sendrecvmmsg(arg1, arg2, arg3, arg4, 1);
- break;
- case TARGET_NR_recvmmsg:
- ret = do_sendrecvmmsg(arg1, arg2, arg3, arg4, 0);
- break;
- #endif
- #ifdef TARGET_NR_sendto
- case TARGET_NR_sendto:
- ret = do_sendto(arg1, arg2, arg3, arg4, arg5, arg6);
- break;
- #endif
- `safe_sendto`は内部でホスト側に`int 0x80`しているだけ
- code:linux-user/syscall.c
- // do_sendto()
- ....
- if (target_addr) {
- addr = alloca(addrlen+1);
- ret = target_to_host_sockaddr(fd, addr, target_addr, addrlen);
- if (ret) {
- goto fail;
- }
- ret = get_errno(safe_sendto(fd, host_msg, len, flags, addr, addrlen));
- } else {
- ret = get_errno(safe_sendto(fd, host_msg, len, flags, NULL, 0));
- }
- ...
- Canaryの値をバラバラにしたい
- https://stackoverflow.com/questions/14296852/recursive-diff-to-create-a-patch-and-apply-patch-recursively
- 難しいことはわからん
Backlinks
SecHack365
SecHack365は学生(と社会人)が、1年間指導を受けながらセキュリティに関連したり関連しなかったりするテーマで何かをつくる長期ハッカソン。無料で、交通費も全部出る。筆者は2018年度に、「ライブラリ・リンカ・ローダ・コンパイラetcを連携させたセキュリティ機能の開発」というテーマで参加した。