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 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 in 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! duh 😉). 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 shown itself.
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 a 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:
8fe146a1 8b5630 movl 0x30(%esi),%edx <- code for case LC_SEGMENT_COMMAND
8fe146a4 8d4638 leal 0x38(%esi),%eax
8fe146a7 89d1 movl %edx,%ecx
8fe146a9 c1e106 shll $0x06,%ecx
8fe146ac 8d1491 leal (%ecx,%edx,4),%edx
8fe146af 8d0c10 leal (%eax,%edx),%ecx
8fe146b2 39c8 cmpl %ecx,%eax <- for condition sect < sectionsEnd
8fe146b4 0f8336ffffff jael 0x8fe145f0
8fe146ba 0fb65038 movzbl 0x38(%eax),%edx
8fe146be 80fa09 cmpb $0x09,%dl <- if ( type == S_MOD_INIT_FUNC_POINTERS )
8fe146c1 741e je 0x8fe146e1
8fe146c3 80fa0a cmpb $0x0a,%dl <- else if ( type == S_MOD_TERM_FUNC_POINTERS )
8fe146c6 7434 je 0x8fe146fc
8fe146c8 80fa0f cmpb $0x0f,%dl <- else if ( type == S_DTRACE_DOF )
8fe146cb 7457 je 0x8fe14724
8fe146cd 83c044 addl $0x44,%eax <- ++sect
8fe146d0 39c1 cmpl %eax,%ecx <- for condition sect < sectionsEnd
8fe146d2 0f8618ffffff jbel 0x8fe145f0
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 = §ionsStart[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 its value, because sectionsEnd = §ionsStart[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 is 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!