Challenge Debugme
This challenge consists of analyzing a Windows PE32 executable from HackTheBox that actively attempts to thwart debugging efforts through memory and time checks and the solution consists of using static analysis tools like Cutter to identify debugger evasion techniques, and utilizing winedbg to dynamically analyze, patch the binary, and successfully extract the hidden flag from memory.
Basic Reconnaissance
The analysis begins by determining the file type using the file command, which reveals a PE32 executable for Windows with an unusual 14 sections.
$ file debugme.exe
debugme.exe: PE32 executable for MS Windows 4.00 (console), Intel i386, 14 sections
Running the executable under Wine immediately displays a taunting message, confirming that the goal is to capture the flag while actively debugging the binary.
$ wine debugme.exe
I heard you like bugs so I put bugs in your debugger so you can have bugs while you debug!!!
Seriously though try and find the flag, you will find it in your debugger!!!
Extracting the strings from the binary uncovers additional messages related to debugger detection.
$ strings debugme.exe
!This program cannot be run in DOS mode.
.text
.data
.rdata
.bss
.idata
.CRT
.tls
@B/19
B/31
B/45
B/57
0B/70
B/81
f=MZt
l\\\
\\\\m
l\\\
`\)nSm
...
Looks like your doing something naughty. Stop it!!!
I heard you like bugs so I put bugs in your debugger so you can have bugs while you debug!!!
Seriously though try and find the flag, you will find it in your debugger!!!
...
__imp____setusermatherr
__tls_used
_WideCharToMultiByte@32
___crt_xt_end__
__imp__EnterCriticalSection@4
Opening the binary in Cutter reveals two suspicious imports commonly associated with debugging traps: GetTickCount and GetSystemTimeAsFileTime.
...
0x0040c170 FUNC KERNEL32.dll GetSystemTimeAsFileTime
0x0040c174 FUNC KERNEL32.dll GetTickCount
...
Tracing the cross-references for GetTickCount indicates that it is being called by the Stack Canary Protection mechanism (___security_init_cookie), suggesting that purely static analysis might not be the most effective approach for this challenge.
void ___security_init_cookie(void)
{
uint32_t uVar1;
int32_t var_24h;
int32_t var_20h;
int32_t var_1ch;
int32_t var_18h;
long unsigned var_10h;
var_1ch = 0;
var_18h = 0;
if (_data.00409130 == 0xbb40e64e) {
(*_GetSystemTimeAsFileTime)(&var_1ch);
var_10h = var_1ch ^ var_18h;
uVar1 = (*_GetCurrentProcessId)();
var_10h = var_10h ^ uVar1;
uVar1 = (*_GetCurrentThreadId)();
var_10h = var_10h ^ uVar1;
uVar1 = (*_GetTickCount)();
var_10h = var_10h ^ uVar1;
(*_QueryPerformanceCounter)(&var_24h);
var_10h = var_10h ^ var_24h ^ var_20h;
if (var_10h == 0xbb40e64e) {
var_10h = 0xbb40e64f;
}
_data.00409130 = var_10h;
_data.00409134 = ~var_10h;
} else {
_data.00409134 = ~_data.00409130;
}
return;
}
Given the limitations of static analysis, the focus shifts to dynamic analysis using winedbg.
$ winedbg debugme.exe
WineDbg starting on pid 01a8
01a4:fixme:dbghelp:elf_search_auxv can't find symbol in module
01a4:fixme:dbghelp:elf_search_auxv can't find symbol in module
0x0000007bcefcb6 ntdll+0x6fcb6: movl -0x58(%ebp), %eax
Continuing the execution results in a page fault. The binary intentionally accesses invalid memory, which triggers a crash that the debugger catches.
Wine-dbg> c
Unhandled exception: page fault on write access to 0xfffba004 in wow64 32-bit code (0x00000000330007).
01a4:fixme:dbghelp:elf_search_auxv can't find symbol in module
Register dump:
CS:0023 SS:002b DS:002b ES:002b FS:0063 GS:002b
EIP:00330007 ESP:0032ff70 EBP:0032ff58 EFLAGS:00010606( R- -- DI - -P- )
EAX:7ffdd002 EBX:7ffdd000 ECX:00000000 EDX:00401001
ESI:00000000 EDI:00000000
Stack dump:
0x0000000032ff70: 7bccfedd 7bacfb7c 004010f9 7ffdd000
0x0000000032ff80: 7ffdd000 004010f9 ffffffff 7bcc8d00
0x0000000032ff90: 7bcd0090 00000000 0032ffe8 7ffdd000
0x0000000032ffa0: 00000000 00000000 0032ff70 7bccfe88
0x0000000032ffb0: 00000000 00000000 00000000 00000000
0x0000000032ffc0: 00000000 00000000 00000000 00000000
Backtrace:
=>0 0x00000000330007 (0x0000000032ff58)
1 0x0000007bc8e437 in ntdll (+0xe437) (0x0000000032ff6c)
2 0x0000007bccfedd in ntdll (+0x4fedd) (0x0000000032ffe8)
0x00000000330007: addb %al, (%eax, %eax)
By examining the entry0 function in Cutter, it becomes clear that the original entry point has been hijacked with a direct jump to 0x408904.
;-- _mainCRTStartup:
entry0();
; var int32_t var_10h @ stack - 0x10
0x004010f9 jmp 0x408904
0x004010fe nop
0x004010ff mov dword [var_10h], 0xff ; 255
0x00401106 mov dword [0x40b020], 0
0x00401110 call ___security_init_cookie ; sym.___security_init_cookie
0x00401115 call ___tmainCRTStartup ; sym.___tmainCRTStartup
0x0040111a mov dword [var_10h], eax
0x0040111d mov eax, dword [var_10h]
0x00401120 leave
0x00401121 ret
Following the jump to 0x408904 reveals three anti-debugging traps implemented by the author. The first two check for the presence of a debugger, while the third relies on a time check (using the rdtsc instruction).
0x004088ff add byte [eax], al
0x00408901 add byte [eax], al
0x00408903 add byte [ecx + 0x30], ah
0x00408904 mov eax, dword fs:[0x30]
0x0040890a mov al, byte [eax + 2]
...
0x0040891c mov eax, dword fs:[0x30]
0x00408922 mov al, byte [eax + 0x68]
...
0x0040894d rdtsc
0x0040894f sub eax, ebx
0x00408951 cmp eax, 0x3e8 ; 1000
0x00408956 jg 0x408992
To overcome these protections, the conditional jumps (jne, jg) are patched out and replaced with NOPs, nullifying the traps.
...
0x00408911 nop
0x00408912 nop
...
0x00408929 nop
0x0040892a nop
...
0x00408956 nop
0x00408957 nop
...
Subsequent instructions reveal that the application iterates over the _main function and applies an XOR operation with 0x5c to decrypt it before jumping back to the actual entry point.
0x0040896e mov eax, _main ; 0x401620
0x00408973 xor byte [eax], 0x5c ; 92
0x00408976 inc eax
0x00408977 cmp eax, 0x401791
0x0040897c jle 0x408973
0x0040897e mov esi, data.00414015 ; 0x414015
0x00408983 push ebp
0x00408984 mov ebp, esp
0x00408986 sub esp, 0x18
0x00408989 jmp 0x4010ff
0x0040898e add byte [eax], al
0x00408990 add byte [eax], al
0x00408992 mov esp, ebp
0x00408994 pop esp
0x00408995 ret
Execution is restarted in winedbg, and a breakpoint is placed at 0x408986 to pause execution once the _main function has been decrypted.
$ winedbg debugme.exe
WineDbg starting on pid 015c
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
0x0000007bcefcb6 ntdll+0x6fcb6: movl -0x58(%ebp), %eax
Wine-dbg> break *0x408986
Breakpoint 1 at 0x00000000408986 debugme+0x8986
Continuing execution stops the binary at the intended point.
Wine-dbg> c
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
Stopped on breakpoint 1 at 0x00000000408986 debugme+0x8986
Another breakpoint is set directly at the now-decrypted _main function (0x401620).
Wine-dbg> break *0x401620
Breakpoint 2 at 0x00000000401620 debugme+0x1620
Wine-dbg> c
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
Stopped on breakpoint 2 at 0x00000000401620 debugme+0x1620
Examining the first few instructions of _main demonstrates that the same three anti-debugging checks are present once again.
Wine-dbg> x/20i $eip
0x00000000401620 debugme+0x1620: pushl %ebp
0x00000000401621 debugme+0x1621: movl %esp, %ebp
0x00000000401623 debugme+0x1623: xorl %eax, %eax
0x00000000401625 debugme+0x1625: xorl %edx, %edx
0x00000000401627 debugme+0x1627: movl %fs:0x30, %eax
0x0000000040162d debugme+0x162d: movb 2(%eax), %al
0x00000000401630 debugme+0x1630: movb %al, %dl
0x00000000401632 debugme+0x1632: cmpb $0, %al
0x00000000401634 debugme+0x1634: jne 0x401680 debugme+0x1680
0x00000000401636 debugme+0x1636: jmp 0x40163b debugme+0x163b
0x0000000040163b debugme+0x163b: xorl %eax, %eax
0x0000000040163d debugme+0x163d: xorl %edx, %edx
0x0000000040163f debugme+0x163f: movl %fs:0x30, %eax
0x00000000401645 debugme+0x1645: movb 0x68(%eax), %al
0x00000000401648 debugme+0x1648: movb %al, %dl
0x0000000040164a debugme+0x164a: cmpb $0, %al
0x0000000040164c debugme+0x164c: jne 0x401680 debugme+0x1680
0x0000000040164e debugme+0x164e: rdtsc
0x00000000401650 debugme+0x1650: movl %eax, %ebx
0x00000000401652 debugme+0x1652: pushl %ecx
To bypass these checks, the execution flow needs to be redirected past them. Inspecting further ahead reveals the beginning of the flag construction process.
Wine-dbg> x/40i 0x401652
0x00000000401652 debugme+0x1652: pushl %ecx
0x00000000401653 debugme+0x1653: popl %ecx
0x00000000401654 debugme+0x1654: addl %edi, %edi
0x00000000401656 debugme+0x1656: subl %edi, %edi
0x00000000401658 debugme+0x1658: pushl %esi
0x00000000401659 debugme+0x1659: popl %esi
0x0000000040165a debugme+0x165a: addl %ecx, %ecx
0x0000000040165c debugme+0x165c: subl %ecx, %ecx
0x0000000040165e debugme+0x165e: pushl %esi
0x0000000040165f debugme+0x165f: popl %esi
0x00000000401660 debugme+0x1660: addl %edi, %edi
0x00000000401662 debugme+0x1662: subl %edi, %edi
0x00000000401664 debugme+0x1664: pushl %ecx
0x00000000401665 debugme+0x1665: popl %ecx
0x00000000401666 debugme+0x1666: addl %ecx, %ecx
0x00000000401668 debugme+0x1668: subl %ecx, %ecx
0x0000000040166a debugme+0x166a: addl %edi, %edi
0x0000000040166c debugme+0x166c: subl %edi, %edi
0x0000000040166e debugme+0x166e: pushl %esi
0x0000000040166f debugme+0x166f: popl %esi
0x00000000401670 debugme+0x1670: rdtsc
0x00000000401672 debugme+0x1672: subl %ebx, %eax
0x00000000401674 debugme+0x1674: cmpl $0x3e8, %eax
0x00000000401679 debugme+0x1679: jg 0x401680 debugme+0x1680
0x0000000040167b debugme+0x167b: jmp 0x401694 debugme+0x1694
0x00000000401680 debugme+0x1680: pushl $0x409000
0x00000000401685 debugme+0x1685: calll 0x4085ec debugme+0x85ec
0x0000000040168a debugme+0x168a: addl $4, %esp
0x00000000401690 debugme+0x1690: movl %ebp, %esp
0x00000000401692 debugme+0x1692: popl %ebp
0x00000000401693 debugme+0x1693: retl
0x00000000401694 debugme+0x1694: pushl $0x409035
0x00000000401699 debugme+0x1699: calll 0x4085ec debugme+0x85ec
0x0000000040169e debugme+0x169e: addl $4, %esp
0x000000004016a4 debugme+0x16a4: pushl $0x409093
0x000000004016a9 debugme+0x16a9: calll 0x4085ec debugme+0x85ec
0x000000004016ae debugme+0x16ae: addl $4, %esp
0x000000004016b4 debugme+0x16b4: xorl %eax, %eax
0x000000004016b6 debugme+0x16b6: movl $0x6a253e2d, %eax
0x000000004016bb debugme+0x16bb: pushl %eax
At 0x4016b6, the flag is built byte by byte. To prepare the stack properly, the first two instructions of _main must be executed before manually setting the instruction pointer.
Wine-dbg> stepi
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
0x00000000401621 debugme+0x1621: movl %esp, %ebp
Wine-dbg> stepi
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
0x00000000401623 debugme+0x1623: xorl %eax, %eax
With the stack frame set, the instruction pointer (EIP) is advanced to 0x401694, bypassing the traps completely.
Wine-dbg> set $eip = 0x401694
Listing the instructions from this new position shows how a base value is mathematically manipulated and then pushed to the stack to reconstruct the flag.
Wine-dbg> x/30i $eip
0x00000000401694 debugme+0x1694: pushl $0x409035
0x00000000401699 debugme+0x1699: calll 0x4085ec debugme+0x85ec
0x0000000040169e debugme+0x169e: addl $4, %esp
0x000000004016a4 debugme+0x16a4: pushl $0x409093
0x000000004016a9 debugme+0x16a9: calll 0x4085ec debugme+0x85ec
0x000000004016ae debugme+0x16ae: addl $4, %esp
0x000000004016b4 debugme+0x16b4: xorl %eax, %eax
0x000000004016b6 debugme+0x16b6: movl $0x6a253e2d, %eax
0x000000004016bb debugme+0x16bb: pushl %eax
0x000000004016bc debugme+0x16bc: jmp 0x4016c1 debugme+0x16c1
0x000000004016c1 debugme+0x16c1: subl $0x560c29fc, %eax
0x000000004016c6 debugme+0x16c6: pushl %eax
0x000000004016c7 debugme+0x16c7: jmp 0x4016cc debugme+0x16cc
0x000000004016cc debugme+0x16cc: andl $0x41414141, %eax
0x000000004016d1 debugme+0x16d1: andl $0x3e3e3e3e, %eax
0x000000004016d6 debugme+0x16d6: movl $0x6a253e2d, %eax
0x000000004016db debugme+0x16db: subl $0x49fd1bf4, %eax
0x000000004016e0 debugme+0x16e0: pushl %eax
0x000000004016e1 debugme+0x16e1: jmp 0x4016e6 debugme+0x16e6
0x000000004016e6 debugme+0x16e6: xorl %eax, %eax
0x000000004016e8 debugme+0x16e8: movl $0x6a253e2d, %eax
0x000000004016ed debugme+0x16ed: subl $0x2b1124ff, %eax
0x000000004016f2 debugme+0x16f2: pushl %eax
0x000000004016f3 debugme+0x16f3: jmp 0x4016f8 debugme+0x16f8
0x000000004016f8 debugme+0x16f8: andl $0x41414141, %eax
0x000000004016fd debugme+0x16fd: andl $0x3e3e3e3e, %eax
0x00000000401702 debugme+0x1702: movl $0x6a253e2d, %eax
0x00000000401707 debugme+0x1707: subl $0x5e190004, %eax
0x0000000040170c debugme+0x170c: pushl %eax
0x0000000040170d debugme+0x170d: jmp 0x401712 debugme+0x1712
Continuing to inspect the memory layout, more manipulations are found before all fragments are finally pushed to the stack.
Wine-dbg> x/30i 0x401712
0x00000000401712 debugme+0x1712: andl $0x41414141, %eax
0x00000000401717 debugme+0x1717: andl $0x3e3e3e3e, %eax
0x0000000040171c debugme+0x171c: movl $0x6a253e2d, %eax
0x00000000401721 debugme+0x1721: addl $0xde9d64d, %eax
0x00000000401726 debugme+0x1726: pushl %eax
0x00000000401727 debugme+0x1727: jmp 0x40172c debugme+0x172c
0x0000000040172c debugme+0x172c: xorl %eax, %eax
0x0000000040172e debugme+0x172e: movl $0x6a253e2d, %eax
0x00000000401733 debugme+0x1733: subl $0x2b003419, %eax
0x00000000401738 debugme+0x1738: pushl %eax
0x00000000401739 debugme+0x1739: jmp 0x40173e debugme+0x173e
0x0000000040173e debugme+0x173e: andl $0x41414141, %eax
0x00000000401743 debugme+0x1743: andl $0x3e3e3e3e, %eax
0x00000000401748 debugme+0x1748: movl $0x6a253e2d, %eax
0x0000000040174d debugme+0x174d: subl $0x3e001c06, %eax
0x00000000401752 debugme+0x1752: pushl %eax
0x00000000401753 debugme+0x1753: jmp 0x401758 debugme+0x1758
0x00000000401758 debugme+0x1758: andl $0x41414141, %eax
0x0000000040175d debugme+0x175d: andl $0x3e3e3e3e, %eax
0x00000000401762 debugme+0x1762: movl $0x6a253e2d, %eax
0x00000000401767 debugme+0x1767: subl $0x42aa050e, %eax
0x0000000040176c debugme+0x176c: pushl %eax
0x0000000040176d debugme+0x176d: jmp 0x401772 debugme+0x1772
0x00000000401772 debugme+0x1772: pushl %esp
0x00000000401773 debugme+0x1773: popl %esi
0x00000000401774 debugme+0x1774: xorl %edx, %edx
0x00000000401776 debugme+0x1776: movl %esi, %edi
0x00000000401778 debugme+0x1778: movl %edi, %edx
0x0000000040177a debugme+0x177a: cld
0x0000000040177b debugme+0x177b: movl $0x24, %ecx
A breakpoint is placed at 0x401772, right before the stack is processed.
Wine-dbg> break *0x401772
Breakpoint 3 at 0x00000000401772 debugme+0x1772
Execution continues until this breakpoint is hit.
Wine-dbg> c
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
Stopped on breakpoint 3 at 0x00000000401772 debugme+0x1772
Examining the top of the stack ($esp) shows heavily obfuscated characters, indicating that the flag is still encrypted.
Wine-dbg> x/s $esp
9{''"%,
%?zx)>
.?9"( 1->%j 2
Analyzing the subsequent logic uncovers a decryption loop that XORs each byte with 0x4B until the plaintext is derived. The instruction at 0x40178e cleans up the stack frame afterwards.
Wine-dbg> x/20i $eip
0x00000000401772 debugme+0x1772: pushl %esp
0x00000000401773 debugme+0x1773: popl %esi
0x00000000401774 debugme+0x1774: xorl %edx, %edx
0x00000000401776 debugme+0x1776: movl %esi, %edi
0x00000000401778 debugme+0x1778: movl %edi, %edx
0x0000000040177a debugme+0x177a: cld
0x0000000040177b debugme+0x177b: movl $0x24, %ecx
0x00000000401780 debugme+0x1780: movl $0x4b, %ebx
0x00000000401785 debugme+0x1785: xorl %eax, %eax
0x00000000401787 debugme+0x1787: pushl %eax
0x00000000401788 debugme+0x1788: lodsb (%esi), %al
0x00000000401789 debugme+0x1789: xorl %ebx, %eax
0x0000000040178b debugme+0x178b: stosb %al, %es:(%edi)
0x0000000040178c debugme+0x178c: loop 0x401788
0x0000000040178e debugme+0x178e: movl %ebp, %esp
0x00000000401790 debugme+0x1790: popl %ebp
0x00000000401791 debugme+0x1791: retl
0x00000000401792 debugme+0x1792: nop
0x00000000401794 debugme+0x1794: pushl %ebp
0x00000000401795 debugme+0x1795: movl %esp, %ebp
To intercept the decrypted text before it is lost, a final breakpoint is set at 0x40178e.
Wine-dbg> break *0x40178e
Breakpoint 4 at 0x0000000040178e debugme+0x178e
Once the execution is resumed and halts at the loop's exit, inspecting the $edx register successfully reveals the inner content of the flag.
Wine-dbg> c
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
Stopped on breakpoint 4 at 0x0000000040178e debugme+0x178e
Wine-dbg> x/s $edx
[FLAG] 2
Prepending the standard HTB prefix to this string successfully completes the challenge.