Welcome to Day 17 of the Advent of Radare!
Today, we’ll focus on examining and changing the register state at specific breakpoints, a fundamental step in debugging and understanding binary behavior when performing dynamic instrumentation.
Radare2 provides commands like db
and dbc
to manage breakpoints and display register values with dr
or drr
. In this guide, we’ll explore some common practices
and techniques for manipulating program execution to bypass protections
and security checks at runtime.
We have already covered the topic of breakpoints in the 2nd day of the #aor24, so we won’t go into much extra details in here, but instead focus on the practical applications and inconvenients.
Protected software uses different techniques to detect when the application is being debugged and uses that as a way to misbehave, crash, or fail in different ways to confuse the analyst or avoid tampering to make it harder to change or inspect its internal functionalities.
This technique is commonly used not just for Anti-debugging mechanisms, but also for Malware detection evasion, Software protection schemes and Security-sensitive applications. In this section we will learn about some of them:
IsDebuggerPresent, this w32 api returns true when the current process have a debugger attached, so the program itself can determine if it’s being debugged.
The IsDebuggerPresent API on Windows can be replaced by directly checking the Process Environment Block (PEB) structure, which contains a byte flag indicating if a debugger is present. This information is accessible through the Thread Local Storage (TLS) via the Thread Environment Block (TEB), which points to the PEB.
IsDebuggerPresentPEB:
#if _WIN64mov rax, gs:[0x60]
#elsemov eax, fs:[0x30]
#endif
Some little understandings on which structures are inside those pointers:
() {
BOOL IsDebuggerPresentPEBtypedef struct _PEB {
[2];
BYTE Reserved1;
BYTE BeingDebugged[1];
BYTE Reserved2// ... rest of PEB structure
} PEB;
typedef struct _TEB {
;
NT_TIB NtTib;
PVOID EnvironmentPointer;
CLIENT_ID ClientId;
PVOID ActiveRpcHandle;
PVOID ThreadLocalStoragePointer;
PPEB ProcessEnvironmentBlock// ... rest of TEB structure
} TEB;
= NtCurrentTeb();
PTEB pTEB return pTEB->ProcessEnvironmentBlock->BeingDebugged;
}
On Linux systems (as well as in BSD), the
PTRACE_ME
request for the ptrace syscall that will fail
(return -1) when the process is being traced. The reason for that is
because this call can be only made once by the child process.
On Linux we can check the stat
and status
files from the /proc/self/
directory to determine if we are
being traced:
/proc/[pid]/stat
/proc/self/status
Malware authors and software protection systems often implement various techniques to detect and prevent debugging. Understanding them will make us understand and find ways to bypass or workaround them.
Spend some time reading the following items and think (or google around) about how to beat them!
Let’s go back to the radare2 shell to learn how to display and manipulate the registers.
Register profiles are tab-separated text files that define how registers are mapped in memory. Each analysis engine and debugger backend maintains its own register profile, which specifies the exact memory layout of registers.
We can inspect them with the arp
or drp
commands. These files contain the following fields:
Here’s an excerpt from the reg profile used by esil on x64 binaries:
0x00000000]> drp | head -n20
=PC rip
=SP rsp
=BP rbp
=R0 rax
=A0 rdi
=A1 rsi
=A2 rdx
=A3 rcx
=A4 r8
=A5 r9
=A6 r10
=A7 r11
=SN rax
gpr rax .64 80 0
gpr eax .32 80 0
gpr ax .16 80 0
gpr al .8 80 0
gpr ah .8 81 0
gpr rbx .64 40 0
gpr ebx .32 40 0
..
The register profile information is also available in XML format
within GDB, and Radare2 includes functionality to convert between both
formats. By default, the dr
command displays registers
corresponding to the current architecture’s bit width (as specified by
asm.bits
).
To pick the value of a register:
[0x100425074]> dr=
Show registers of size 64
[0x100425074]> dr 64
As long as all r2 command can export the information in scripts by
using the *
suffix, we can use this characteristic to save
the register values in a file and then restore it back by executing the
script.
Let’s see how that works:
[0x100425074]> dr* > regs.r2
[0x100425074]> . regs.r2
But what’s in the regs.r2 now? is this exactly what we were looking for?
[0x100425074]> cat regs.r2
fs+registers
f rax 8 0x00000000
f rbx 8 0x00000000
f rcx 8 0x00000000
f rdx 8 0x00000000
f rsi 8 0x00000000
f rdi 8 0x00000000
...
fs-
[0x100425074]>
Well, it seems like it’s not. this script is just setting some flags with a given name, size and address. So, if we run the script what we will get is just the flags loaded, but flags are not tied to the debugger backend or the register state, why’s that useful for then?
Turns out that math operations, and many other commands end up using
the flags as a way to find out references to named things like the
register periscoping listing in drr
.
dr?rax
- get value of ‘rax’ registerfd @ rax
- assume rax
is taken as a flag
(see .dr*
)fd @r:rax
- show flag dr*rax
- get value
of ‘rax’ registerSo, to achieve the same behaviour we can just run it in one line:
[0x100425074]> .dr*
The dot tells r2 to take the output of the command that is written right after it and interpret it as an r2 script.
But then, which command should we use to save and restore registers?
it’s dr.
!
[0x100425074]> dr. > regs.r2
[0x100425074]> dr rax=33
[0x100425074]> . regs.r2
[0x100425074]> dr?rax
0x00000033
And the script now looks like expected:
[0x100425074]> dr.
dr rax=0x00000000
dr rbx=0x00000000
dr rcx=0x00000000
dr rdx=0x00000000
dr rsi=0x00000000
dr rdi=0x00000000
dr r8=0x00000000
...
We learned about the advantages and inconveniences of different protections and anti-debugging techniques, so we should now decide which are the best choices we can take to circumvent them.
dbh
)db
)rarun2 ldpreload=..
)wx/wa
)Let’s put the knowledge into practice with today’s challenge, we may try to solve it in all the possible ways we have.
Start by disassembling the main function of the binary attached (b64+xz), then load it into your favourite linux-x64 system and run it:
$ ./a.out
Hello, World!
$ r2 -qc 'dc' -d ./a.out
Debugger detected!
(1223217) Process exited with status=0x100
$
I’ll give you over some of these options:
Set a breakpoint in the ptrace import. step over the libc call and check for the condition that happens in the main function and the values of the registers.
We can now use the ‘dr’ command right before the cmp to set the right value like if ptrace returned 0 instead of -1.
Open the binary with -w
to be able to write changes in
the file, analyze with agfv the condition that makes the program fail
when ptrace returns -1 and patch the code using wa
or
wao
.
If we use Frida or r2frida as backend for debugging or tracing the target we will notice that the program runs without any issue:
$ frida ./a.out
Spawned `./a.out`. Resuming main thread!
Hello, World!
[Local::a.out ]-> Process terminated
Thank you for using Frida!
Same happens in r2frida:
$ r2 frida://./a.out
INFO: Mounted io on /r2f at 0x0
-- Press 'C' in visual mode to toggle colors
[0x5caf81643169]> :dc
Hello, World!
INFO: resumed spawned process
[0x5caf81643169]>
INFO: DetachReason: FRIDA_SESSION_DETACH_REASON_PROCESS_TERMINATED
The reason for that is because frida uses ptrace only to spawn the
proces and inject the frida agent inside the process, then it’s
detaching it and resuming, we can use commands like
:dtf ptrace
to trace when the ptrace call is happening. But
more interestingly, we can just fake the return value of
ptrace
to make the program think that is running
$ r2 frida://./a.out
INFO: Mounted io on /r2f at 0x0
[0x61a5cf02f169]> :dif-1 ptrace
[0x61a5cf02f169]> :dc
INFO: resumed spawned process
Debugger detected!
Target: ptrace was called, intercepting return value.
INFO: DetachReason: FRIDA_SESSION_DETACH_REASON_PROCESS_TERMINATED
[0x61a5cf02f169]>
The dif-1
command intercepts the function return value
and replace it with 0, 1, -1, any random inmediate a string, etc..
The difference with di
is that one replaces the function
and the other keeps the code being executed and changes the return value
after that.
:di[0,1,-1,i,s,v] [addr] Intercepts and replace return value of address without calling the function
:dif[0,1,-1,i,s] [addr] Intercepts return value of address after calling the function
/Td6WFoAAATm1rRGAgAhARwAAAAQz1jM4D6HB4tdAD+RRYRoPYmm2orhgzJO2QgDEQzOIJnhOIC5by
/ltn+ezABBUQWSImIw3m2GytJRiNQkZPCV3kjmM7n2HnITlJRsuZmFI1UE10XNxW+VChhvRHl5fGj2
J+xHkuneiFvGZVjLRXsLH+ejwCtW4fycAq+zrC3pCtl6OjIfWSGu4VRquhW0qbY4kgFGnU0pPvGR56
J9SbiL7LRA9MEo7XZSgG5KgZv8w4JMhmh/71xh6r5JEPOEUv73jNfcXf0o2saxza4WhR1nhG0rQ/M0
zJ3GU2x7ZRABu/1NCjPnUl/mIjhjKYrvuInbBUuDDZGnO52WnBL/NXNxyCFmW3hifYY+ZsvvNgYEj0
BmJeGkTBBpCYakJniaTPxkoamERCyPhnhY3Hs6FP/xqu0ryTPmbWp7KVqVzZdM/zINB+uDDdEfypx9
DgtIyl8gKNK+jdqZfrl2qzNP9mTm/LlnfiZ5L5xNmKcXSsJ+ScvYqCnsrdZFRfjBNu9+/BGOwZiISl
BuYy1EqSz3L4ghcWOvKdQCNpRAuM/3mpP7TznBu+/4ntUAb79JzSkaZLoQOpx/uKMB9rmp6fr9T2RC
nu4UyAd4+w4M9D8eJulFCbkvH6agg8EF3j+69saEbsikSyYK9RUgWeDExhT4IUxOsU5qse9+RfKM2K
8TkR88l/tPyZMu87zCgOkFan4EQksXJaO6kuLeyNWXTPI4tFDKIpiAONRmDvcvmlbkpgRDLlPUBJ6M
wIPX1ZRrB5QSz7XnScen3OuQdMpWU3g3Yz8trLeDyU5yH/i+MRgTHIrM8s/zjdpt573ox1tIGwozFZ
mu8gfgOJ/BYZcZdNP73csQVNBf1dQZNb2LnYTVkeE2vK2ReKUjDyEPkfE2fx8j1DiYqpwExK2xeWKz
8SFwyr4p2QdlV6EYk5w5UcDkJyrmihVVkelX7JjI5zsvtZHreRVgDnXqTXlncKBDvWAbspzCrmlPjO
qc+2NKzeqS1JSHS7CLU6/YQ6CnZyWeOc97ECKsQAN51j0ATD+8J7GOKV2DoqxICUNtWCsRO1ZeQEe/
FrjN/NuhJVPVSuRvc6fBivY4zBOdjwwfeofUYF9T0hn/UjTEPRojCbtDxT/Zj6Tooita478RnFpJu/
LHnBFMyg5lgy/+s5+H8+7DyqQbUOrN+qSdpmeCqY8M9rzNeBOG0h0Ri6gxTLbMQaAY5rj1+IZRFOx4
zJDBA+NC3qX2ZIpy2mcQhx0Q0KHLegUdfW5HIU2+MPsC9ZPWDkx6m8SDxvylcpZHibPMmIYCEHocU5
SMekVhC3ZNzwAAPQsCmMzRkZl5JpquJlm45QQsxNXmpJ6yWng+dnleIzkmxYV4D1kIsX9WfLJ394eo
ycWhLXPbHCThQP0AcSQBnFGD8LC/aGkL6x7wVlqAoFu/9vtB9FJhQOfIudj5WxFUceebxHNsgwho/K
xSvAKGT1uFkDs/gISwEYcG0QfT8TWLuVLnpTzJZjX1UU9RK2lGMydg65FftwecdO6BvFWBb+qT2TM2
p+TsqFWOW9bOEvPrpSQoMpUNfNIqe9mGBhdJ96VNqCatxGeBT0HEOaDItNfHlu6SWz9o7OZHCpdH4K
2USnveWWdbMFn8iyOwGEq5ZSZAt+BEVlIKSSsy1kI1KeZWtGzAIrJM5Ek5cHxk3fPN+uHqKbR/JeHP
BrIjetINZ/kmxNIXb64TP4N8ktG3Ds8y3Qfmtq7UJrFuKrZpLaAs/NFM4mLpVnmi5WSTEK8h16RG9Q
4/chp4JXJ26hZxvRCZ6Dxyl2EOmclJapKB/QzYwZ/dzJlDUyvZXEZulgX07COskprImttfjRSH+v4t
t/Qyz8suqkaoilKpcLmDbcdnljXdd1sK36axUCHNFJTAG2jsoB4Vj9sJIwbo/+e6OfTmO82lFIJKu3
dwLdGt40aGJwr4qwFO0wNlnZDnDvky68mj9qCv88Go2lVEVty+LCmQ5iZUY5gUtnYwJVyl/l6cbzuz
zT9b0cqRawcQjH6XrxSONzZ3V9WF0x8PAaf+4pLw3BQoy+IKznNNFb96FRfYetqeTH/+0FPRIsA8T1
HpSmXuRPK/zzpA4uotzYIdGl5mGe+BMXW01+nOLW9BGgzWSaEoA6KHTicp4d8eBTznktYIqyK++LtZ
sP8N+46wiM0jWFWj3175oyyn//iDkN5bV2kYPW96FPtySNw1kmOhvjnf0jX60+DPNAIMJlshWNCi9b
02+RCCz5ieq6taFRU2nuwzcpcPpTCkNlczPo4dd0Z6LTHQONZBn2MnWwJZkDtkGrtnh/VcPAgvB7dp
htyibCTLZGKza/4nkv+SQqxNKy1zavya4xmrFew9Yzvi86+3V/DqRi64zT4UPpXw456RSFVGjUhWQw
wJOunqOQy4lsm2V1O+e3XQA5aEyrkm0xEx/HAIUELeBopLm/7RpL38EZETucZNJEOkQ5SBm3RAjFzC
zbAWehvi+4LWJz3MC6SjXNMUnRAK3Z8UrjnXbfUvn8NBY5z4NMaoI2ziCgzS/siraiU8nneuP0aqBu
A40hRIfZ/4N6Sz2fuh/RAgFRfbEw04QIPaOHK46Yf5AABUe8K8ViBtbQABpw+IfQAAjQfYMbHEZ/sC
AAAAAARZWg==
Stay tuned for tomorrow’s Radare2 challenge as we explore more powerful analysis tools!