一 背景
二 初试(完结)
1.frida_server名字检测 2.端口号检测 3.D-Bus检测 4.maps检测 5.线程检测 6.内存特征检测
然后就从所有检测的共同点入手了,所有检测的共同点就是字符串检测,而字符串匹配最常见的就是strstr函数。
上面的几个检测点直接用frida进行hook就行了:
var ph = Module.findExportByName(null, "strstr"); Interceptor.attach(ptr(ph), { onEnter: function (args) { this.filename = args[0] this.checkname = args[1] // console.log(this.filename.readCString(), ptr(args[1]).readCString()) }, onLeave: function (retval) { var s = ptr(this.filename).readCString() if (s.indexOf("gmain") >= 0){ console.log("gmain anti.") retval.replace(0) }else if(s.indexOf("gum-js-loop") >= 0){ console.log("gum-js-loop anti.") retval.replace(0) }else if(s.indexOf("linjector") >= 0){ console.log("linjector anti.") retval.replace(0) }else if(s.indexOf("/data/local/tmp") >= 0){ console.log("/data/local/tmp anti.") retval.replace(0) } } })
三 定位检测代码位置
console.log(Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join("n"));
排除这些解密字符串的代码直接找到里面的函数调用的地方:
sub_1A940 线程名检测 sub_1AAEC 通过/proc/self/fd文件的实际路径检测是否存在匹配的文件 sub_1AC00 maps检测 sub_23804 xxx内容有点大,需要具体分析
v2 = dlopen("libart.so", 0); if ( !v2 ) goto LABEL_23; v3 = v2; ... off_47728[0] = (int *)dlsym(v3, (const char *)&v38); dlclose(v3); goto LABEL_23; ... LABEL_23: v0 = off_47728[0]; if ( off_47728[0] ) return (unsigned int)*v0 >> 1 == 0x2C000028; return 0LL;
四 尝试bpf定位
接下来假设前面的检测手段都被修正了,那么前面的都得失效,比如:
strstr函数自定义 相应的方法采用svc的方式调用 ...
python3 opensnoop -u 10094 python3 trace.py --uid 10094 'do_sys_openat2 "%s", arg2@user' -U --address -v -f maps
比如,这里是对这个app进行进行文件追踪的日志:
既然找到了切入点,那自然是通过trace的栈信息来定位了,trace的用户栈信息有几个问题,一个是栈信息不全,其次就是总是显示unknow的问题,这个有兴趣研究的可以参考大佬的文章https://bbs.kanxue.com/thread-274546.htm。
而我采用的是最笨最简单的一种方式,就是直接解析maps,将文件偏移与文件名关联起来直接替换trace脚本中的相关内容就行了,虽然没有解决特殊情况下栈不全的问题,但也勉强能用了,毕竟我的要求不高。
# 定义 class MapsItem: def __init__(self, startAddr, endAddr, fOffset, path): self.startAddr = int(startAddr, 16) self.endAddr = int(endAddr, 16) self.fileOffset = int(fOffset, 16) self.path = path def update(self, startAddr=None, endAddr=None, path=None): if startAddr is not None: self.startAddr = startAddr if endAddr is not None: self.endAddr = endAddr if path is not None: self.path = path def __str__(self): return "MapsItem{%s-%s %s %s}" % (hex(self.startAddr), hex(self.endAddr), hex(self.fileOffset), self.path) class PidItem(object): pid = 0 lst_segments = [] # MapsItem def __init__(self, pid): self.pid = pid def printLst(self): for item in self.lst_segments: print("%s" % (item)) class KKStackCache: pidmap = {} def __init__(self, pid): if pid in KKStackCache.pidmap: pass else: KKStackCache.parserMaps(pid) @staticmethod def parserMaps(pid): try: fname = "/proc/%d/maps" % (pid) # fname = "maps" # print("open %s" % fname) f = open(fname) pattern = re.compile(r'([w]+)-([w]+)s+([w-]+)s+([w-]+)s+([w:]+)s+([d]+)s+(S+(?:s+S+)*?)*s*$') KKStackCache.pidmap[pid] = PidItem(pid) mapsdata = [] lines = f.readlines() for line in lines: match = re.match(pattern, line) if match: mapsItem = MapsItem(match.group(1), match.group(2), match.group(4), match.group(7)) mapsdata.append(mapsItem) else: print("No match:" + line) KKStackCache.pidmap[pid].lst_segments = sorted(mapsdata, key=lambda x: x.startAddr) f.close() except: pass print("parser %d over." % pid) @staticmethod def addrInfo(pid, addr): pidmap = KKStackCache.pidmap.get(pid) if pidmap is not None: lst = pidmap.lst_segments bg = 0; end = len(lst) - 1 while bg <= end: mid = int((bg + end + 1) / 2) # print("mid %d: %s %s, addr:%s" % (mid, hex(lst[mid].startAddr), hex(lst[mid].endAddr), hex(addr))) if addr < lst[mid].startAddr: end = mid - 1 elif addr >= lst[mid].endAddr: bg = mid + 1 else: itm = lst[mid] path = itm.path offset = itm.fileOffset + (addr - itm.startAddr) return True, addr, offset, path return False, None, None, None @staticmethod def printLst(pid): pidmap = KKStackCache.pidmap.get(pid) if pidmap is not None: pidmap.printLst() # 使用 # 给_stack_to_string函数增加一个pid参数 def _stack_to_string(self, bpf, stack_id, tgid, pid): # 在处理栈信息的地方加上下面的代码 if pid > 0: name, offset, module = BPF._sym_cache(tgid).resolve(addr, False) if name is None: KKStackCache(pid) isExist, baseAddr, offset, path = KKStackCache.addrInfo(pid, addr) if not isExist: KKStackCache.parserMaps(pid) isExist, baseAddr, offset, path = KKStackCache.addrInfo(pid, addr) if isExist: if path is not None: symstr = "%s %s" % (hex(offset), path)
至于那个libtiny.so的栈信息,我就打开简单看了下:
//在trace_return里面增加代码 if(my_strnstr((const char*)data.name, "maps", 256, 5) != NULL){ bpf_override_return(ctx, -1); }
开启和关闭效果如下:
看雪ID:lxcoder
https://bbs.kanxue.com/user-home-719948.htm
原文始发于微信公众号(看雪学苑):bpf在android逆向中的辅助效果
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论