两种绕过方案 这里有两种方案去绕过这种反调试:静态内存匹配特征patch实现一个简单的inlinehook动态hook绕过静态内存匹配特征patch 在介绍思路之前,先看下一遍内联汇编去实现反调试的代码asmvolatile(movx0,31movx1,0movx2,0movx3,0movx16,26svc128); 这里可以看出原理就是调用了26号系统调用,那么我们是不是可以去代码段里面去搜索,找到满足该特征的代码位置,然后直接将svc置为nop不就可以了?下面引出两个问题,如何去遍历代码段以及如何去修改?获取代码段位置以及大小voidgetTextSegmentAddr(structsegmentRangetextSegRange){intoffset0;structmachheader64header(structmachheader64)dyldgetimageheader(0);if(headermagic!MHMAGIC64){return;}offsetsizeof(structmachheader64);intncmdsheaderncmds;while(ncmds){gothroughallloadcommandtofindTEXTsegmentstructloadcommandlcp(structloadcommand)((uint8t)headeroffset);offsetlcpcmdsize;if(lcpcmdLCSEGMENT64){structsegmentcommand64curSegment(structsegmentcommand64)lcp;structsection64curSection(structsection64)((uint8t)curSegmentsizeof(structsegmentcommand64));checkcurrentsectionofsegmentisTEXT?if(!strcmp(curSectionsegname,TEXT)!strcmp(curSectionsectname,text)){uint64tmemAddrcurSectionaddr;textSegRangestartmemAddrdyldgetimagevmaddrslide(0);textSegRangeendtextSegRangestartcurSectionsize;break;}}}return;} 代码不复杂,就是动态解析了自身内存里面的macho文件,根据macho文件格式找到代码段LCSEGMENT64(TEXT)然后就能得到text的开始位置以及大小。内存搜索匹配ptrace内联汇编代码voidlookupptracesvc(voidtargetaddr,uint64tsize){uint8tp(uint8t)targetaddr;for(inti0;isize;i){movx16,0x1a0xd2800350svc0x800xd4001001if(((uint32t)p)0xd2800350((uint32t)p1)0xd4001001){returnp;}p;}returnNULL;} 传入的就是代码段的地址以及大小,然后遍历整个代码段,找到满足以下ptrace特征汇编代码movx16,0x1a0xd2800350svc0x800xd4001001 然后就返回该地址。patch代码(将svc改为nop) iOSLLDB中基于内存单指令patch实现反反调试161这篇文章介绍了如何去patch代码的原理,但当时遇到一个bug:在iOS1112上面patch会失败,后面我花了一段时间去分析了失败的原因,后来也写了一篇文章去记录了分析的过程,感兴趣的可以访问iOS12内存patchremapbug分析57 这里我就直接给出patch的代码uint8tpatchinsdata〔4〕{0x1f,0x20,0x03,0xd5};noppatchCode(ptracesvcp4,patchinsdata,4); 完整流程代码如下(void)killantidebug{structsegmentRangetextSegRange;getTextSegmentAddr(textSegRange);voidptracesvcplookupptracesvc((void)textSegRange。start,textSegRange。endtextSegRange。start);if(!ptracesvcp){ADDLOG(〔〕notfoundptracesvc);return;}ADDXLOG(〔〕foundptracesvcaddressp,ptracesvcp);charptracebyteshexdump((void)ptracesvcp,8);ADDXLOG(〔〕readptracesvcinsaddress:psize:0xxinstbytes:s,ptracesvcp,8,ptracebytes);free(ptracebytes);ADDLOG(〔〕starttoptachptracesvctoret);uint8tpatchinsdata〔4〕{0x1f,0x20,0x03,0xd5};patchCode(ptracesvcp4,patchinsdata,4);ADDLOG(〔〕ptachptracesvctonopdone,readnewvalue);ptracebyteshexdump((void)ptracesvcp,8);ADDXLOG(〔〕readptracesvcinsaddress:psize:0xxinstbytes:s,ptracesvcp,8,ptracebytes);free(ptracebytes);} 通过比对前后的代码就发现svc出地址的代码已经变成了nop从而绕过了反调试inlinehook动态hook绕过 这种方式主要针对那些混淆了系统调用号或者其他编译版本,其绕过原理是直接hooksvc指令,然后判断是否为26号系统调用(让其他系统调用正常执行),若满足就直接跳过svc指令。 整体流程代码如下structsegmentRangetextSegRange;getTextSegmentAddr(textSegRange);voidsvcplookupsvcins((void)textSegRange。start,textSegRange。endtextSegRange。start);if(!svcp){ADDLOG(〔〕notfoundsvc);return;}ADDXLOG(〔〕foundsvcaddressp,svcp);charsvcbyteshexdump((void)svcp,4);ADDXLOG(〔〕readptracesvcinsaddress:psize:0xxinstbytes:s,svcp,4,svcbytes);free(svcbytes);xia0Hook(svcp); 同样遍历代码段找到所有的svc指令,然后进行hook,下面看hook的具体实现boolxia0Hook(voidtargetaddr){intlen(int)sysconf(SCPAGESIZE);1。gettargetaddresspageandpatchoffsetunsignedlongpagestart(unsignedlong)(targetaddr)PAGEMASK;unsignedlongpatchoffset(unsignedlong)targetaddrpagestart;printf(〔〕Targetaddress:pPagestart:pPatchoffset:p,targetaddr,(void)pagestart,(void)patchoffset);2。mapnewpageforpatchvoidnewmmap(NULL,len,PROTREADPROTWRITE,MAPANONMAPSHARED,1,0);if(!new){printf(〔〕mmapfailed!);returnfalse;}3。copytarget4instonewpageintcopysize44;voidcopyfromaddrtargetaddrcopysize;memcpy((void)(new),copyfromaddr,copysize);cmpx16,0x1ab。nelocnotptracesvcjmpldrx17,0x8brx17origsvcnextaddr1origsvcnextaddr2ldrx17,0x8brx17origsvcaddr1origsvcaddr2uint64torigsvcaddr(uint64t)targetaddr;uint64torigsvcnextaddr(uint64t)(targetaddr14);uint8tcheckjmpdata〔〕{0x1f,0x6a,0x00,0xf1,0x51,0x00,0x00,0x58,0x20,0x02,0x1f,0xd6,origsvcnextaddr0xff,(origsvcnextaddr81)0xff,(origsvcnextaddr82)0xff,(origsvcnextaddr83)0xff,(origsvcnextaddr84)0xff,(origsvcnextaddr85)0xff,(origsvcnextaddr86)0xff,(origsvcnextaddr87)0xff,0x51,0x00,0x00,0x58,0x20,0x02,0x1f,0xd6,origsvcaddr0xff,(origsvcaddr81)0xff,(origsvcaddr82)0xff,(origsvcaddr83)0xff,(origsvcaddr84)0xff,(origsvcaddr85)0xff,(origsvcaddr86)0xff,(origsvcaddr87)0xff};intcheckjmpdatasize104;memcpy((void)(new44),checkjmpdata,checkjmpdatasize);4。patchtargetaddresstojmphookcodevoidpatchaddrcopyfromaddr;uint64tnewp(uint64t)new;ldrx16,0x8brx16hookcodeaddr1hookcodeaddr2uint8tpatchdata〔〕{0x50,0x00,0x00,0x58,0x00,0x02,0x1f,0xd6,newp0xff,(newp81)0xff,(newp82)0xff,(newp83)0xff,(newp84)0xff,(newp85)0xff,(newp86)0xff,(newp87)0xff};intpatchdatasize44;patchCode(patchaddr,patchdata,patchdatasize);5。setnewpagetorxmprotect(new,len,PROTREADPROTEXEC);returntrue;} 这里代码比较复杂,大致分为以下步骤map一页内存new,后面会将hook的代码写到里面copy原svc前的四条指令保存到new页(目前没有进行相对寻址修复)将hook判断的代码写到紧接着前面四条指令的后面,汇编代码大致如下cmpx16,0x1ab。nelocnotptracesvcjmpldrx17,0x8brx17origsvcnextaddr1origsvcnextaddr2ldrx17,0x8brx17origsvcaddr1origsvcaddr2就是简单的判断了系统调用号是否为26,若满足就跳到svc的下一条指令,若不是则跳回原svc指令以保证其他系统调用正常执行。patch目标地址进行hook跳转,由于进行任意地址跳转需要4条指令大小,所以这里覆盖了svc前的四条指令ldrx16,0x8brx16hookcodeaddr1hookcodeaddr2这里就是在执行svc指令前使其跳转到我们的hook代码最后将new这页设置为可读不可写可执行的页属性总结Todo 其实对于这种inlinehook去绕过调试,后面发现已经有人已经实现了,因为只要实现了inlinehook,肯定能hook代码绕过。不过我这里主要是想去自己分析以及实现这里面的很多细节。因为hook框架由于要考虑到稳定,兼容等等因素,所以往往代码不是很直接。而这里通过仅仅实现绕过反调试的需求,所以代码都比较通俗易懂,原理来说都是一样的。只有自己去动手写了代码才发现里面的乐趣所在,比如如何去实现系统调用的判断?如何解决寄存器污染?如何去实现代码段patch?当然还有很多汇编级别的坑存在,踩坑解决坑同样有意思,这里就不一一介绍。 后面主要还有两个事需要做:相对寻址指令的修复问题,以及hook代码的稳定兼容扩展问题。抽离相关代码,集成到xia0LLDB143之中,真正实现调试器中一键绕过反调试。更新20190911 xia0LLDB143中已经集成了两种绕过反调试方案。app采取直接或间接调用ptrace函数的反调试方案〔xia0LLDBdebugme〕单指令patchptrace函数app采取单点或者多点多线程利用内联汇编方式的反调试方案〔xia0LLDBdebugme〕动态inlinehookapp中所有的svc指令,然后判断是否为26号系统调用,然后patch。 https:github。com4ch12dyxia0LLDB