Hacker's Corner: Complete Guide to Anti-Debugging in Linux - Part 3
Breakpoints
A breakpoint is intentional "pause" in normal execution of a program, generally used to inspect the internals of said process in more detail. This is the *most* used feature of any debugger.
(gdb) break main
Breakpoint 1 at 0x116d
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000001169 <+0>: push rbp
0x000000000000116a <+1>: mov rbp,rsp
0x000000000000116d <+4>: lea rsi,[rip+0xe91] # 0x2005
0x0000000000001174 <+11>: lea rdi,[rip+0x2f05] # 0x4080 <_ZSt4cout@@GLIBCXX_3.4>
0x000000000000117b <+18>: call 0x1040 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000001180 <+23>: mov rdx,rax
0x0000000000001183 <+26>: mov rax,QWORD PTR [rip+0x2e46] # 0x3fd0
0x000000000000118a <+33>: mov rsi,rax
0x000000000000118d <+36>: mov rdi,rdx
0x0000000000001190 <+39>: call 0x1050 <_ZNSolsEPFRSoS_E@plt>
0x0000000000001195 <+44>: mov eax,0x0
0x000000000000119a <+49>: pop rbp
0x000000000000119b <+50>: ret
End of assembler dump.
(gdb)
Detecting Software Breakpoint
#include
bool isBreakpointPresent(unsigned char *func)
{
bool result = *func == 0xCC;
return result;
}
void secret()
{
for (int i = 0; i < 10; ++i)
{
std::cout << "Try a breakpoint at secret()" << std::endl;
}
}
int main()
{
auto *ptr_secret = (unsigned char*)secret;
if (isBreakpointPresent(ptr_secret))
std::cerr << "Breakpoint detected" << std::endl;
else
secret();
return 0;
}
$ gdb -q ./detect-breakpoint1
Reading symbols from ./detect-breakpoint1...
(gdb) break secret
Breakpoint 1 at 0x118e: file detect-breakpoint1.cpp, line 15.
(gdb) run
Starting program: detect-breakpoint1
Breakpoint 1, secret () at detect-breakpoint1.cpp:15
15 for (int i = 0; i < 10; ++i)
(gdb)
(gdb) bt
#0 secret () at detect-breakpoint1.cpp:15
#1 0x000055555555521e in main () at detect-breakpoint1.cpp:27
(gdb) disassemble secret
Dump of assembler code for function secret():
0x0000555555555186 <+0>: push rbp
0x0000555555555187 <+1>: mov rbp,rsp
0x000055555555518a <+4>: sub rsp,0x10
=> 0x000055555555518e <+8>: mov DWORD PTR [rbp-0x4],0x0
0x0000555555555195 <+15>: cmp DWORD PTR [rbp-0x4],0x9
0x0000555555555199 <+19>: jg 0x5555555551c9
0x000055555555519b <+21>: lea rsi,[rip+0xe62] # 0x555555556004
0x00005555555551a2 <+28>: lea rdi,[rip+0x2ed7] # 0x555555558080 <_ZSt4cout@@GLIBCXX_3.4>
0x00005555555551a9 <+35>: call 0x555555555040 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x00005555555551ae <+40>: mov rdx,rax
0x00005555555551b1 <+43>: mov rax,QWORD PTR [rip+0x2e18] # 0x555555557fd0
0x00005555555551b8 <+50>: mov rsi,rax
0x00005555555551bb <+53>: mov rdi,rdx
0x00005555555551be <+56>: call 0x555555555050 <_ZNSolsEPFRSoS_E@plt>
0x00005555555551c3 <+61>: add DWORD PTR [rbp-0x4],0x1
0x00005555555551c7 <+65>: jmp 0x555555555195
0x00005555555551c9 <+67>: nop
0x00005555555551ca <+68>: leave
0x00005555555551cb <+69>: ret
End of assembler dump.
(gdb)
(gdb) disassemble secret
Dump of assembler code for function secret():
0x0000555555555186 <+0>: push rbp
0x0000555555555187 <+1>: mov rbp,rsp
0x000055555555518a <+4>: sub rsp,0x10
0x000055555555518e <+8>: mov DWORD PTR [rbp-0x4],0x0
0x0000555555555195 <+15>: cmp DWORD PTR [rbp-0x4],0x9
0x0000555555555199 <+19>: jg 0x5555555551c9
0x000055555555519b <+21>: lea rsi,[rip+0xe62] # 0x555555556004
0x00005555555551a2 <+28>: lea rdi,[rip+0x2ed7] # 0x555555558080 <_ZSt4cout@@GLIBCXX_3.4>
0x00005555555551a9 <+35>: call 0x555555555040 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x00005555555551ae <+40>: mov rdx,rax
0x00005555555551b1 <+43>: mov rax,QWORD PTR [rip+0x2e18] # 0x555555557fd0
0x00005555555551b8 <+50>: mov rsi,rax
0x00005555555551bb <+53>: mov rdi,rdx
0x00005555555551be <+56>: call 0x555555555050 <_ZNSolsEPFRSoS_E@plt>
0x00005555555551c3 <+61>: add DWORD PTR [rbp-0x4],0x1
0x00005555555551c7 <+65>: jmp 0x555555555195
0x00005555555551c9 <+67>: nop
0x00005555555551ca <+68>: leave
0x00005555555551cb <+69>: ret
End of assembler dump.
(gdb) break * 0x0000555555555186
Breakpoint 2 at 0x555555555186: file detect-breakpoint1.cpp, line 14.
(gdb) continue
Continuing.
Breakpoint detected
[Inferior 1 (process 52508) exited normally]
(gdb)
Improved Detection
Getting Offsets
(gdb) disassemble secret
Dump of assembler code for function secret():
0x000000000000128f <+0>: push rbp
0x0000000000001290 <+1>: mov rbp,rsp
0x0000000000001293 <+4>: sub rsp,0x10
0x0000000000001297 <+8>: mov DWORD PTR [rbp-0x4],0x0
0x000000000000129e <+15>: cmp DWORD PTR [rbp-0x4],0x9
0x00000000000012a2 <+19>: jg 0x12d2
0x00000000000012a4 <+21>: lea rsi,[rip+0xd5d] # 0x2008
0x00000000000012ab <+28>: lea rdi,[rip+0x2e0e] # 0x40c0 <_ZSt4cout@@GLIBCXX_3.4>
0x00000000000012b2 <+35>: call 0x1060 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x00000000000012b7 <+40>: mov rdx,rax
0x00000000000012ba <+43>: mov rax,QWORD PTR [rip+0x2d0f] # 0x3fd0
0x00000000000012c1 <+50>: mov rsi,rax
0x00000000000012c4 <+53>: mov rdi,rdx
0x00000000000012c7 <+56>: call 0x1090 <_ZNSolsEPFRSoS_E@plt>
0x00000000000012cc <+61>: add DWORD PTR [rbp-0x4],0x1
0x00000000000012d0 <+65>: jmp 0x129e
0x00000000000012d2 <+67>: nop
0x00000000000012d3 <+68>: leave
0x00000000000012d4 <+69>: ret
End of assembler dump.
(gdb)
$ gdb -batch -ex 'file ./detect-breakpoint2' -ex 'disassemble secret' 2>/dev/null | grep "0x" | grep "<+" | awk -F ' ' '{print $2}' | cut -c3- | rev | cut -c3- | rev | sed ':a;N;$!ba;s/\n/, /g'
0, 1, 4, 8, 15, 19, 21, 28, 35, 40, 43, 50, 53, 56, 61, 65, 67, 68, 69
#include
#include
#include
bool isBreakpointPresent(const unsigned char *func, const std::vector& offsets)
{
bool result = false;
for (auto &i : offsets) {
if (*(func + i) == 0xCC)
{
result = true;
break;
}
}
return result;
}
void secret()
{
for (int i = 0; i < 10; ++i)
{
std::cout << "Try a breakpoint at secret()" << std::endl;
}
}
int main()
{
auto *ptr_secret = (unsigned char*)secret;
std::vector offsets = {0, 1, 4, 8, 15, 19, 21, 28, 35, 40, 43, 50, 53, 56, 61, 65, 67, 68, 69};
if (isBreakpointPresent(ptr_secret, offsets))
std::cerr << "Breakpoint detected" << std::endl;
else
secret();
return 0;
}
$ gdb -q ./detect-breakpoint2
Reading symbols from ./detect-breakpoint2...
(gdb) break secret
Breakpoint 1 at 0x1297: file detect-breakpoint2.cpp, line 26.
(gdb) run
Starting program: detect-breakpoint2
Breakpoint detected
[Inferior 1 (process 70087) exited normally]
(gdb)
About the Author
Adhokshaj Mishra works as a security researcher (malware - Linux) at Uptycs. His interest lies in offensive and defensive side of Linux malware research. He has been working on attacks related to containers, kubernetes; and various techniques to write better malware targeting Linux platform. In his free time, he loves to dabble into applied cryptography, and present his work in various security meetups and conferences.