Obfuscation #2: Playing entrypoint hide & seek game with dyld

Load command 9
        cmd LC_UNIXTHREAD
    cmdsize 80
     flavor i386_THREAD_STATE
      count i386_THREAD_STATE_COUNT
	    eax 0x00000000 ebx    0x00000000 ecx 0x00000000 edx 0x00000000
	    edi 0x00000000 esi    0x00000000 ebp 0x00000000 esp 0x00000000
	    ss  0x00000000 eflags 0x00000000 eip 0x186b2662 cs  0x00000000
	    ds  0x00000000 es     0x00000000 fs  0x00000000 gs  0x00000000

This is from the header of my crackme and that entrypoint is a random value. When the entrypoint is the original and valid one, IDA is more or less smart and uses that information if the headers are mangled (just the offsets). Instead of modifying the entrypoint to some stub I wanted to use an invalid value. A good place for this is dyld, who is responsible for jumping to the program’s entrypoint. The interesting code snippet is (@ dyld/src/dyldStartup.s):

        # call dyldbootstrap::start(app_mh, argc, argv, slide)
        call    L__dyld_start_picbase
L__dyld_start_picbase:
        popl    %ebx            # set %ebx to runtime value of picbase
        movl    __dyld_start_static_picbase-L__dyld_start_picbase(%ebx), %eax
        subl    %eax, %ebx      # slide = L__dyld_start_picbase - [__dyld_start_static_picbase]
        pushl   %ebx            # param4 = slide
        lea     12(%ebp),%ebx
        pushl   %ebx            # param3 = argv
        movl    8(%ebp),%ebx
        pushl   %ebx            # param2 = argc
        movl    4(%ebp),%ebx
        pushl   %ebx            # param1 = mh
        call    __ZN13dyldbootstrap5startEPK12macho_headeriPPKcl
 
        # clean up stack and jump to result
        movl    %ebp,%esp       # restore the unaligned stack pointer
        addl    $8,%esp         # remove the mh argument, and debugger end
                                #  frame marker
        movl    $0,%ebp         # restore ebp back to zero
        jmp     *%eax           # jump to the entry point

What we need is to restore the eax value to the real entrypoint and everything will be fine. One easy way to accomplish this is to set a breakpoint at the jmp eax address, fix the eax value and continue normal execution. Another way is to modify the jmp to an absolute address. The jmp is just two bytes long – not enough for this. But there should be enough alignment space above _dyld_start. Example:

__text:8FE01010                   _offset_to_dyld_all_image_infos:
__text:8FE01010 00 44 04 00                       db 0,44h,4,0 ; add     [esp+eax+0], al
__text:8FE01010                   ; ---------------------------------------------------------------------------
__text:8FE01014 00 00 00 00 00 00+                dd 5 dup(0)
__text:8FE01028 0F 1F 84 00 00 00+                align 10h
__text:8FE01030
__text:8FE01030                   ; =============== S U B R O U T I N E =======================================
__text:8FE01030
__text:8FE01030
__text:8FE01030                                   public __dyld_start
__text:8FE01030                   __dyld_start    proc near
__text:8FE01030 6A 00                             push    0
__text:8FE01032 89 E5                             mov     ebp, esp
__text:8FE01034 83 E4 F0                          and     esp, 0FFFFFFF0h
__text:8FE01037 E8 00 00 00 00                    call    $+5
__text:8FE0103C
__text:8FE0103C                   loc_8FE0103C:                           ; DATA XREF: __data:__dyld_start_static_picbase
__text:8FE0103C 5B                                pop     ebx
__text:8FE0103D 8B 83 64 1C 04 00                 mov     eax, ds:(__dyld_start_static_picbase - 8FE0103Ch)[ebx]
__text:8FE01043 29 C3                             sub     ebx, eax
__text:8FE01045 53                                push    ebx
__text:8FE01046 8D 5D 0C                          lea     ebx, [ebp+0Ch]
__text:8FE01049 53                                push    ebx
__text:8FE0104A 8B 5D 08                          mov     ebx, [ebp+8]
__text:8FE0104D 53                                push    ebx
__text:8FE0104E 8B 5D 04                          mov     ebx, [ebp+4]
__text:8FE01051 53                                push    ebx
__text:8FE01052 E8 4F 05 00 00                    call    __ZN13dyldbootstrap5startEPK12macho_headeriPPKcl
__text:8FE01057 89 EC                             mov     esp, ebp
__text:8FE01059 83 C4 08                          add     esp, 8
__text:8FE0105C BD 00 00 00 00                    mov     ebp, 0
__text:8FE01061 FF E0                             jmp     eax
__text:8FE01061                   __dyld_start    endp

Modify the jmp eax to a negative offset into the slack space and modify that space to jump into the real entrypoint. The only piece left in this puzzle is ASLR. The dyld address can be easily found, especially if we give a base value where to start searching from (refer to the dyld randomization article). Having dyld address, you can find its symbol table. I used __dyld_start_static_picbase symbol for 32 bits (64 is __dyld_start_static). While writing this I can’t remember why I have used this symbol instead of dyld_start!
The final step is to compute the real address of the jmp eax. I used an hash of the last bytes, which should be stable enough for this:

        # clean up stack and jump to result
        movl    %ebp,%esp       # restore the unaligned stack pointer
        addl    $8,%esp         # remove the mh argument, and debugger end
                                #  frame marker
        movl    $0,%ebp         # restore ebp back to zero
        jmp     *%eax           # jump to the entry point

Now we have the interesting address and can use one of the solutions above or something else you can come up with.
The problem with this approach is that it requires a constructor to manipulate dyld’s code before it is executed. Yesterday’s spoofing article and lots of junk functions could help to hide our intentions :-).

Have fun,
fG!

2 thoughts on “Obfuscation #2: Playing entrypoint hide & seek game with dyld

  1. I’m glad you’ve highlighted this on your site, and find it amusing that there are several reversers actually using this trick with their own patches. I’m suspicious of those who feel the need to do it, since it only raises questions as to why. In particular, those in the community who try and use it to deter our own is a bit selfish and somewhat foolish.

Leave a Reply

Your email address will not be published. Required fields are marked *