Usa el árbol para saltar entre colecciones sin salir del lector.

archivo Seleccionar un writeup Abrir árbol
HackTheBox/Challenges/Challenge Debugme.md READ_ONLY

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.