Detecting the Presence of a Debugger in Linux, (Tue, Nov 19th)
SANS Internet Storm Center, InfoCON: green 2024-11-19
Hello from Singapore where I'm with Johannes and Yee! This week, I'm teaching FOR710[1]. I spotted another Python script that looked interesting because, amongst the classic detection of virtualized environments, it also tries to detect the presence of a debugger. The script has been developed to target both environments: Windows & Linux.
On Windows, it's pretty easy to detect if a debugger has been attached to a process. The microsoft ecosystems has many ways to check this: A stealthy method is to check the PEB ("Process Environment Block")[2] that provides a "BeingDebugged" member in its structure. Another easy way is to use the Microsoft API call IsDebuggerPresent()[3]. Note that they are a lot of alternative techniques but I won't cover them here.
But how does it work in Linux? Because the malicious script is compatible with Linux, let's check the code:
def check_debugging(): if True: try: if CURRENT_OS == "Windows": if ctypes.windll.kernel32.IsDebuggerPresent(): return True else: import re with open('/proc/self/status') as f: status = f.read() if re.search(r'TracerPid:\s+(?!0\n)', status): return True except: pass return False
If the script is executed on Windows, IsDebuggerPresent() will be called otherwise, the script will search for an interesting string in /proc/self/status:
xavier@rog:/proc/self$ cat status
Name: bash
Umask: 0022
State: S (sleeping)
Tgid: 352
Ngid: 0
Pid: 352
PPid: 351
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 256
Groups: 4 20 24 25 27 29 30 44 46 116 1000
NStgid: 352
NSpid: 352
NSpgid: 352
NSsid: 352
VmPeak: 6216 kB
VmSize: 6216 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 5076 kB
VmRSS: 5076 kB
RssAnon: 1724 kB
RssFile: 3352 kB
RssShmem: 0 kB
VmData: 1736 kB
VmStk: 132 kB
VmExe: 892 kB
VmLib: 1864 kB
VmPTE: 48 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
CoreDumping: 0
THP_enabled: 1
Threads: 1
SigQ: 1/30158
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000010000
SigIgn: 0000000000384004
SigCgt: 000000004b813efb
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
NoNewPrivs: 0
Seccomp: 0
Seccomp_filters: 0
Speculation_Store_Bypass: thread vulnerable
SpeculationIndirectBranch: conditional enabled
Cpus_allowed: ffff
Cpus_allowed_list: 0-15
Mems_allowed: 1
Mems_allowed_list: 0
voluntary_ctxt_switches: 91
nonvoluntary_ctxt_switches: 1
The highlighted "TracerPid" line with the "0" indicates that no process is "tracing" this one. In Linux, a common technique used to analyze the behavious of a process is to use a tool like strace[4] to log all the activity performed at system calls level:
xavier@rog:/proc/self$ strace -f -p 352
Let's recheck the /proc/self/status now that it's being "traced":
xavier@rog:/proc/self$ cat status|grep TracerPid TracerPid: 9731
The script (SHA256a9ba5856b97327cc6c68d461e903569e30d5bd507405b5ecb34b0c513c42d50e)[5] remains undetected by most AV (VT score: 2/64) but its final purpose remains unknown because the bytecode passed to exec() does not seems to work! I'm still investigating it...
[1] https://www.sans.org/cyber-security-courses/reverse-engineering-malware-advanced-code-analysis/ [2] https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb [3] https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent [4] https://man7.org/linux/man-pages/man1/strace.1.html [5] https://www.virustotal.com/gui/file/a9ba5856b97327cc6c68d461e903569e30d5bd507405b5ecb34b0c513c42d50e/detection
Xavier Mertens (@xme) Xameco Senior ISC Handler - Freelance Cyber Security Consultant PGP Key
(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.