Anatomy of a gdb anti-debug trick part II: GDB isn’t alone !

After having found the source of the gdb anti-debug trick, I started modifying gdb to work around the problem and fix the number of sections on the fly (it’s damn simple to calculate the real number of sections !!!). I was coding on a long train trip and everything was going great… My hack worked and gdb fixed and loaded the file without a problem… Next step was to run the program but when I tried I had this surprise:

gdb$ r
 
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x00005040
0x8fe146d8 in __dyld__ZN16ImageLoaderMachO13parseLoadCmdsEv ()
--------------------------------------------------------------------------[regs]
EAX: 00005008  EBX: 8FE143C1  ECX: 44001048  EDX: 00000000  o d I t s z a p c
ESI: 00001054  EDI: 00000001  EBP: BFFFE0C8  ESP: BFFFE060  EIP: 8FE146D8
CS: 0017  DS: 001F  ES: 001F  FS: 0000  GS: 0037  SS: 001F
--------------------------------------------------------------------------[code]
0x8fe146d8:     0f b6 50 38                   movzx  edx,BYTE PTR [eax+0x38]
0x8fe146dc:     80 fa 09                      cmp    dl,0x9
0x8fe146df:     75 e2                         jne    0x8fe146c3
0x8fe146e1:     8b 55 08                      mov    edx,DWORD PTR [ebp+0x8]
0x8fe146e4:     81 4a 74 00 80 00 00          or     DWORD PTR [edx+0x74],0x8000
0x8fe146eb:     eb e0                         jmp    0x8fe146cd
0x8fe146ed:     8b 55 08                      mov    edx,DWORD PTR [ebp+0x8]
0x8fe146f0:     81 4a 74 00 08 00 00          or     DWORD PTR [edx+0x74],0x800
--------------------------------------------------------------------------------

Holy crap ! I must have goofed somewhere with my code… This ocurred due to the fact that I had never tried to run the program while I was debugging the problem and modifying gdb so it was natural to think it was my problem (I didn’t even bothered to look to the function name that gave the error! doh ;)). Gave a quick review into the code and it seemed ok since it was a simple modification. Tried to run the program outside gdb and voila, the real problem has shown itself.

dyldcrash

dyldcrashreport

After some tests, the conclusion was that the problem ocurred for most values except 0xFFFFFFFF (else the trick wouldn’t work!). I downloaded dyld source code but I couldn’t find where the crash was located. Train arrived to destination, weekend time and in the next week I had to finish some documentation since I’m leaving my current job and joining an MBA program. Today I was bored and decided to get back to the problem. After disassembling dyld, setting a breakpoint at ImageLoaderMachO::parseLoadCmds and tracing, I finally found the interesting piece of code:

+753  8fe146a1  8b5630                  movl        0x30(%esi),%edx <- code for case LC_SEGMENT_COMMAND
+756  8fe146a4  8d4638                  leal        0x38(%esi),%eax
+759  8fe146a7  89d1                    movl        %edx,%ecx
+761  8fe146a9  c1e106                  shll        $0x06,%ecx
+764  8fe146ac  8d1491                  leal        (%ecx,%edx,4),%edx
+767  8fe146af  8d0c10                  leal        (%eax,%edx),%ecx
+770  8fe146b2  39c8                    cmpl        %ecx,%eax <- for condition sect < sectionsEnd
+772  8fe146b4  0f8336ffffff            jael        0x8fe145f0
+778  8fe146ba  0fb65038                movzbl      0x38(%eax),%edx
+782  8fe146be  80fa09                  cmpb        $0x09,%dl <- if ( type == S_MOD_INIT_FUNC_POINTERS )
+785  8fe146c1  741e                    je          0x8fe146e1
+787  8fe146c3  80fa0a                  cmpb        $0x0a,%dl <- else if ( type == S_MOD_TERM_FUNC_POINTERS )
+790  8fe146c6  7434                    je          0x8fe146fc
+792  8fe146c8  80fa0f                  cmpb        $0x0f,%dl <- else if ( type == S_DTRACE_DOF )
+795  8fe146cb  7457                    je          0x8fe14724
+797  8fe146cd  83c044                  addl        $0x44,%eax <- ++sect
+800  8fe146d0  39c1                    cmpl        %eax,%ecx <- for condition sect < sectionsEnd
+802  8fe146d2  0f8618ffffff            jbel        0x8fe145f0
+808  8fe146d8  0fb65038                movzbl      0x38(%eax),%edx <- CRASH HERE
(...)

Where the original source code is:

case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
#if IMAGE_NOTIFY_SUPPORT
const bool isDataSeg = (strcmp(seg->segname, "__DATA") == 0);
#endif
const struct macho_section* const sectionsStart=(struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
const uint8_t type = sect->flags & SECTION_TYPE;
if ( type == S_MOD_INIT_FUNC_POINTERS )
fHasInitializers = true;
else if ( type == S_MOD_TERM_FUNC_POINTERS )
fHasTerminators = true;
else if ( type == S_DTRACE_DOF )
fHasDOFSections = true;
#if IMAGE_NOTIFY_SUPPORT
else if ( isDataSeg && (strcmp(sect->sectname, "__image_notify") == 0) )
fHasImageNotifySection = true;
#endif
}
}
break;

Ecx holds the value of sectionsEnd and the number of sections described in the header affects it’s value, because sectionsEnd = &sectionsStart[seg->nsects]. Dyld suffers from a similar problem to gdb, because it trusts the header information without any further check. One small thing was left to explain… Why 0xFFFFFFFF works ? By setting a breakpoint at 0x8fe146b2 it’s very easy to see why! If nsects is 0x00FFFFFF then ecx=44001048, if nsects is 0x0FFFFFFF then ecx=40001048, if nsects if 0xFFFFFFFF then ecx=00001048. So 0xFFFFFFFF overflows sectionsEnd and that’s why the tricks works for that value !

Unless Apple fixes this dyld bug (maybe I should report it) there’s no much sense in fixing gdb except to produce a warning about a possible usage of that anti-debug trick. The bug itself is pretty easy to fix in gdb, dyld and other programs (HT Editor, IDA, etc) because the real number of sections can be calcuted using the cmdsize field from the load command. It’s not possible to play tricks with this field (I did a few tests and it doesn’t work) so it must be a reliable value.

I think I can finally close this bug hunt ! It was fun… Only thing left is to release the patches I did for gdb. I must finish the fixes to other read commands and then I will publish them.

That’s all for now folks!
fG!

Leave a Reply

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