Challenge Debugme
Este reto consiste en analizar un ejecutable PE32 de Windows que intenta evadir activamente los procesos de depuración mediante comprobaciones de memoria y tiempo y la solución consiste en emplear herramientas de análisis estático como Cutter para identificar las técnicas de evasión, realizar un análisis dinámico con winedbg, parchear el binario y extraer con éxito la flag oculta desde la memoria.
Reconocimiento Básico
El análisis comienza determinando el tipo de archivo mediante el comando file, lo que revela que se trata de un ejecutable PE32 para Windows con una inusual cantidad de 14 secciones.
$ file debugme.exe
debugme.exe: PE32 executable for MS Windows 4.00 (console), Intel i386, 14 sections
Al ejecutar el binario usando Wine, se muestra un mensaje provocador que confirma que el objetivo del desafío es obtener la flag durante la depuración del binario.
$ 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!!!
La extracción de cadenas del binario saca a la luz mensajes adicionales relacionados con la detección del depurador.
$ 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
Al abrir el binario en Cutter, se observan dos importaciones sospechosas que comúnmente están asociadas a trampas contra depuradores: GetTickCount y GetSystemTimeAsFileTime.
...
0x0040c170 FUNC KERNEL32.dll GetSystemTimeAsFileTime
0x0040c174 FUNC KERNEL32.dll GetTickCount
...
El seguimiento de las referencias cruzadas de GetTickCount indica que esta función es invocada por el mecanismo de protección Stack Canary (___security_init_cookie), sugiriendo que un enfoque puramente estático podría no ser la estrategia más efectiva para resolver el desafío.
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;
}
Debido a las limitaciones del análisis estático, el enfoque se desplaza hacia un análisis dinámico usando 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
Al continuar la ejecución, se produce un fallo de página. El binario intenta acceder intencionalmente a memoria inválida, provocando un fallo que el depurador intercepta.
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)
Al examinar la función entry0 en Cutter, queda claro que el punto de entrada original ha sido secuestrado con un salto directo a 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
Al seguir el salto hacia 0x408904, se descubren tres trampas antidetección implementadas por el autor. Las primeras dos validan la presencia de un depurador, mientras que la tercera depende de una verificación de tiempo empleando la instrucción rdtsc.
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
Para superar estas defensas, los saltos condicionales (jne, jg) son modificados y reemplazados por NOPs, lo cual anula su efecto.
...
0x00408911 nop
0x00408912 nop
...
0x00408929 nop
0x0040892a nop
...
0x00408956 nop
0x00408957 nop
...
Las instrucciones siguientes revelan que la aplicación recorre la función _main y aplica una operación XOR con el valor 0x5c para descifrarla, antes de volver a saltar al punto de entrada real.
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
Se reinicia la ejecución en winedbg y se coloca un punto de interrupción en 0x408986 para detener el proceso justo cuando la función _main ha sido descifrada.
$ 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
Al continuar, la ejecución se detiene en el punto previsto.
Wine-dbg> c
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
Stopped on breakpoint 1 at 0x00000000408986 debugme+0x8986
Luego se establece otro punto de interrupción directamente en la función _main ya descifrada (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
Examinar las primeras instrucciones de _main demuestra que las mismas tres verificaciones de antidetección vuelven a estar presentes.
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
Para evadir estas comprobaciones, es necesario redirigir el flujo de ejecución evitando pasar por ellas. Inspeccionando más adelante, se revela el inicio del proceso de construcción de la flag.
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
En 0x4016b6, la flag se va construyendo byte a byte. Para preparar correctamente la pila, se deben ejecutar las dos primeras instrucciones de _main antes de modificar el puntero de instrucción manualmente.
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
Una vez establecido el marco de la pila, se avanza el puntero de instrucción (EIP) hasta 0x401694, esquivando completamente las trampas.
Wine-dbg> set $eip = 0x401694
Listar las instrucciones desde esta nueva posición muestra cómo se manipula matemáticamente un valor base y luego se apila para reconstruir la 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
Al seguir inspeccionando el diseño de la memoria, se encuentran más manipulaciones antes de que todos los fragmentos sean finalmente apilados en la memoria.
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
Se establece un punto de interrupción en 0x401772, justo antes de que la pila sea procesada.
Wine-dbg> break *0x401772
Breakpoint 3 at 0x00000000401772 debugme+0x1772
La ejecución continúa hasta alcanzar este punto de interrupción.
Wine-dbg> c
0158:fixme:dbghelp:elf_search_auxv can't find symbol in module
Stopped on breakpoint 3 at 0x00000000401772 debugme+0x1772
Al examinar la cima de la pila ($esp), se observan caracteres ofuscados, lo que indica que la flag sigue cifrada.
Wine-dbg> x/s $esp
9{''"%,
%?zx)>
.?9"( 1->%j 2
El análisis de la lógica posterior destapa un bucle de descifrado que aplica una operación XOR a cada byte usando el valor 0x4B hasta obtener el texto en claro. La instrucción en 0x40178e se encarga de limpiar el marco de la pila una vez completado este proceso.
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
Para interceptar el texto descifrado antes de que se pierda, se configura un último punto de interrupción en 0x40178e.
Wine-dbg> break *0x40178e
Breakpoint 4 at 0x0000000040178e debugme+0x178e
Una vez que se reanuda la ejecución y el proceso se detiene a la salida del bucle, inspeccionar el registro $edx revela con éxito el contenido interno de la 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
Añadir el formato estándar HTB a esta cadena completa con éxito el desafío.