Linux (anti)+ debugging
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
等のパスが含まれるので,これを元に検知が可能となる
Comments