Linux Anti Debugging

Linuxにおけるアンチデバッグ(デバッグ検知等)について調べながらまとめる.

ptrace

原理

一般的(?),原始的(?)なデバッグ検知.

デバッガは一般的にデバッグ対象のプロセス(debuggee)に対し,ptraceシステムコールを発行してattachする.

ptraceは同じプロセスに対し2度目に呼び出された時,-1を返し失敗する.

これを利用し,debuggee自身がptraceを呼び出す事で,その戻り値で現在attachされているか確認できる

PoC

src.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>

void anti_debug() {
    if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0)
        fprintf(stderr, "debugger detected!!!!!!1\n"), exit(1);
}

void main() {
    anti_debug();
    puts("check passed!");
}
$ gcc src.c
$ ./a.out
check passed!
$ gdb -q ./a.out
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
(gdb) run
Starting program: /home/lilium/src/lab/anti_debug/a.out
debugger detected!!!!!!1
[Inferior 1 (process 1944964) exited with code 01]
(gdb)

anti anti debug

呼び出している箇所をNOPで潰す,呼び出し後にRAXを書き換える等の手法で検知を回避できる.

バイナリエディタでアンチデバッグ用関数を呼び出している命令をNOPに書き換える事で,簡単に回避できる.

また,以下の様にptrace呼び出した後に戻り値(RAX)を書き換える事でも回避できる.

$ gdb -q ./a.out
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
(gdb) i functions
All defined functions:

Non-debugging symbols:
0x0000000000001000  _init
0x0000000000001070  __cxa_finalize@plt
0x0000000000001080  puts@plt
0x0000000000001090  ptrace@plt
0x00000000000010a0  exit@plt
0x00000000000010b0  fwrite@plt
0x00000000000010c0  _start
0x00000000000010f0  deregister_tm_clones
0x0000000000001120  register_tm_clones
0x0000000000001160  __do_global_dtors_aux
0x00000000000011a0  frame_dummy
0x00000000000011a9  anti_debug
0x0000000000001201  main
0x0000000000001230  __libc_csu_init
0x00000000000012a0  __libc_csu_fini
0x00000000000012a8  _fini
(gdb) b anti_debug
Breakpoint 1 at 0x11a9
(gdb) run
Starting program: /home/lilium/src/lab/anti_debug/a.out

Breakpoint 1, 0x00005555555551a9 in anti_debug ()
(gdb) disass
Dump of assembler code for function anti_debug:
=> 0x00005555555551a9 <+0>:     endbr64
   0x00005555555551ad <+4>:     push   %rbp
   0x00005555555551ae <+5>:     mov    %rsp,%rbp
   0x00005555555551b1 <+8>:     mov    $0x0,%ecx
   0x00005555555551b6 <+13>:    mov    $0x1,%edx
   0x00005555555551bb <+18>:    mov    $0x0,%esi
   0x00005555555551c0 <+23>:    mov    $0x0,%edi
   0x00005555555551c5 <+28>:    mov    $0x0,%eax
   0x00005555555551ca <+33>:    callq  0x555555555090 <ptrace@plt>
   0x00005555555551cf <+38>:    test   %rax,%rax
   0x00005555555551d2 <+41>:    jns    0x5555555551fe <anti_debug+85>
   0x00005555555551d4 <+43>:    mov    0x2e45(%rip),%rax        # 0x555555558020 <stderr@@GLIBC_2.2.5>
   0x00005555555551db <+50>:    mov    %rax,%rcx
   0x00005555555551de <+53>:    mov    $0x19,%edx
   0x00005555555551e3 <+58>:    mov    $0x1,%esi
   0x00005555555551e8 <+63>:    lea    0xe15(%rip),%rdi        # 0x555555556004
   0x00005555555551ef <+70>:    callq  0x5555555550b0 <fwrite@plt>
   0x00005555555551f4 <+75>:    mov    $0x1,%edi
   0x00005555555551f9 <+80>:    callq  0x5555555550a0 <exit@plt>
   0x00005555555551fe <+85>:    nop
   0x00005555555551ff <+86>:    pop    %rbp
   0x0000555555555200 <+87>:    retq
End of assembler dump.
(gdb) b *0x00005555555551ca
Breakpoint 2 at 0x5555555551ca
(gdb) c
Continuing.

Breakpoint 2, 0x00005555555551ca in anti_debug ()
(gdb) i r
rax            0x0                 0
rbx            0x0                 0
rcx            0x0                 0
rdx            0x1                 1
rsi            0x0                 0
rdi            0x0                 0
rbp            0x7fffffffd2f0      0x7fffffffd2f0
rsp            0x7fffffffd2f0      0x7fffffffd2f0
r8             0x0                 0
r9             0x7ffff7fdffa0      140737354006432
r10            0xfffffffffffffa4a  -1462
r11            0x202               514
r12            0x5555555550c0      93824992235712
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
rip            0x5555555551ca      0x5555555551ca <anti_debug+33>
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
(gdb) ni
0x00005555555551cf in anti_debug ()
(gdb) i r
rax            0xffffffffffffffff  -1
rbx            0x0                 0
rcx            0x0                 0
rdx            0xffffffffffffff88  -120
rsi            0x0                 0
rdi            0x0                 0
rbp            0x7fffffffd2f0      0x7fffffffd2f0
rsp            0x7fffffffd2f0      0x7fffffffd2f0
r8             0xffffffff          4294967295
r9             0x7ffff7fdffa0      140737354006432
r10            0x0                 0
r11            0x286               646
r12            0x5555555550c0      93824992235712
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
rip            0x5555555551cf      0x5555555551cf <anti_debug+38>
eflags         0x206               [ PF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
(gdb) set $rax=0
(gdb) i r
rax            0x0                 0
rbx            0x0                 0
rcx            0x0                 0
rdx            0xffffffffffffff88  -120
rsi            0x0                 0
rdi            0x0                 0
rbp            0x7fffffffd2f0      0x7fffffffd2f0
rsp            0x7fffffffd2f0      0x7fffffffd2f0
r8             0xffffffff          4294967295
r9             0x7ffff7fdffa0      140737354006432
r10            0x0                 0
r11            0x286               646
r12            0x5555555550c0      93824992235712
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
rip            0x5555555551cf      0x5555555551cf <anti_debug+38>
eflags         0x206               [ PF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
(gdb) c
Continuing.
check passed!
[Inferior 1 (process 1959403) exited with code 016]
(gdb)

Detect Breakpoint

原理

gdb等のデバッガではbreakpointを設定する際,SIGTRAPを発生させるため,対象となるアドレスにint3(0xcc)を挿入する.

関数を呼び出す前にチェックを行う事で検知できる.

PoC

#include <stdio.h>
#include <stdlib.h>

void func() {
    puts("I'm function");
}

int main() {
    if ((*(unsigned long long*)((unsigned long long)func + 8) & 0xff) == 0xcc)
        fprintf(stderr, "breakpoing detected!!!!!!!\n"), exit(1);

    func();
}
$ gdb ./detect_bp
Reading symbols from ./detect_bp...
(No debugging symbols found in ./detect_bp)
gdb-peda$ b func
Breakpoint 1 at 0x1171
gdb-peda$ r
Starting program: /home/lilium/src/lab/anti_debug/anti_disas/detect_bp
breakpoing detected!!!!!!!
[Inferior 1 (process 3922595) exited with code 01]
Warning: not running

anti anti debug

普通に検知部分でレジスタを書き換えたり,関数自体ではなく関数呼び出しの部分にbreakpointを設定する等の方法で回避できる.

import gdb

functions = [l.split()[1] for l in filter(lambda s: s.startswith('0x'), gdb.execute('i func', to_string=True).splitlines())  ]

for f in functions:
    try:
        disas = gdb.execute('disas {}'.format(f), to_string=True).splitlines()
        for line in disas:
            if "call" in line and "func" in line:
                ofs = line.split()[1].lstrip('<').rstrip('>:')
                gdb.execute('b *{}{}'.format(f, ofs))
    except:
        continue

Fake Breakpoint

原理

上で述べたように,gdbではbreakpointにSIGTRAPを用いている.

これを利用して,コード内でSIGTRAPを発生させ,ダミーのbreakpointを挿入できる.

普通にデバッグする時には便利だが,過剰に発生させる事でかなり厄介になる.

本題からは逸れるが,Signalベースの難読化もあったりする(Binary Obfuscation Using Signals)

PoC

#include <signal.h>
#include <stdio.h>

void handler(int _) {}

void func() {
    puts("I'm function");
}

int main() {
    signal(SIGTRAP, handler);
    raise(SIGTRAP);
    func();
}
$ gdb -q ./dummpy_bp
Reading symbols from ./dummpy_bp...
(No debugging symbols found in ./dummpy_bp)
(gdb) r
Starting program: /host/dummpy_bp
warning: Error disabling address space randomization: Operation not permitted

Program received signal SIGTRAP, Trace/breakpoint trap.
__GI_raise (sig=<optimized out>) at ../sysdeps/unix/sysv/linux/raise.c:50
50      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb)

anti anti debug

とりあえずバイナリにパッチを当てる

#!/usr/bin/env python3
import r2pipe

r = r2pipe.open('dummpy_bp', flags=['-w'])

r.cmd('aaa')

refs = [ref['from'] for ref in r.cmdj('axtj @sym.imp.raise')]

for ref in refs:
    r.cmd(f's {ref}')
    r.cmd('wx 9090909090')
$ gdb -q ./dummpy_bp
Reading symbols from ./dummpy_bp...
(No debugging symbols found in ./dummpy_bp)
(gdb) r
Starting program: /host/dummpy_bp
warning: Error disabling address space randomization: Operation not permitted
I'm function
[Inferior 1 (process 762) exited normally]

detect container

原理

コンテナ内でプロセスが稼働している際,/proc/1/cgroupが通常と異なる.

/docker/lxc等のパスが含まれるので,これを元に検知が可能となる