IOS의 Snoop-It 트윗과 같이 앱실행 시, 실시간으로 실행하는 Class를 알려주는 기능이 있으면 좋겠다고 생각하던 찰나, 이미 누군가가 해당 기능을 만들어서 깃헙에 올려놨네요.


다만 받아서 실행해보면 에러가.... 그래서 올라와 있는 코드 분석하여 로직은 동일하게 수정 및 작성해봤습니다.


기존 Method Trace코드 출처는 다음과 같습니다 : https://github.com/0xdea/frida-scripts


다음은 새로 제가 작성한 코드 입니다.


import sys
import frida

def on_message(message,data):
 print "[%s] -> %s" % (message, data)

PACKAGE_NAME = sys.argv[1]

jscode = """
Java.perform(function(){
 var pattern = \""""+sys.argv[1]+"""\"
 

 Java.enumerateLoadedClasses({
  onMatch:function(aClass) {
   var pattern2 = pattern.split('.');
   var temp = pattern2[0]+"."+pattern2[1];
   if (aClass.match(temp)) {
    traceClass(aClass);
   }
  },
  onComplete: function() {}
 });
 
 function traceClass(targetClass){
  var hook = Java.use(targetClass);
  var methods = hook.class.getDeclaredMethods();
  hook.$dispose;

  var parsedMethods=[];
  methods.forEach(function(method) {

   parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
   traceMethod(targetClass+"."+method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
  });
 }


 function traceMethod(targetClassMethod){

  var delim = targetClassMethod.lastIndexOf(".");
  if (delim === -1){ return;}
  var targetClass = targetClassMethod.slice(0, delim);
  var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length);
  
  var hook = Java.use(targetClass);
  var overloadCount = hook[targetMethod].overloads.length;

  for(var i=0;i<overloadCount;i++){
   hook[targetMethod].overloads[i].implementation=function(){

    console.log("***Entered Method is : "+targetClass+"."+targetMethod);

    var retval = this[targetMethod].apply(this, arguments);
    return retval;
   }
  }
 }

 function traceMainActivity(MainActivity){
  
  var throwable = Java.use('java.lang.Throwable');
  var Main = Java.use(MainActivity);
  Main.$init.implementation=function(a){
   throwable2 = throwable.$new();
   Stack = throwable2.getStackTrace();
   for(var i=0;i<Stack.length;i++){
    console.log(Stack[i].getClassName()+"."+getMethodName());
   }
  }
 }
});
"""
 
try:
 device = frida.get_usb_device()
 pid = device.spawn([PACKAGE_NAME])
 print("App is starting ... pid : {}".format(pid))
 process = device.attach(pid)
 device.resume(pid)
 script = process.create_script(jscode)
 script.on('message',on_message)
 print('[*] Running Frida')
 script.load()
 sys.stdin.read()
except Exception as e:
 print(e)


찬찬히 뜯어보면


 Java.enumerateLoadedClasses({
  onMatch:function(aClass) {
   var pattern2 = pattern.split('.');
   var temp = pattern2[0]+"."+pattern2[1];
   if (aClass.match(temp)) {
    traceClass(aClass);
   }
  },
  onComplete: function() {}
 });


해당 부분에서 enumerateLoadedClasses를 이용해서 로드되는 클래스를 확인 후 split함수를 이용, sys.argv[1]에 입력된 앱이름의 두번째 .(구분자)까지 맞으면 해당 Class명을 traceClass함수에 인자로 호출합니다.

ex) com.abc.1234.taesun1114라고 하면 enumerateLoadedClasses로 출력되는 함수중 com.abc가 포함된 클래스를 traceClass로 넘기게됩니다.


function traceClass(targetClass){
  var hook = Java.use(targetClass);
  var methods = hook.class.getDeclaredMethods();
  hook.$dispose;

  var parsedMethods=[];
  methods.forEach(function(method) {

   parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
   traceMethod(targetClass+"."+method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
  });
 }

traceClass 함수에서는 getDeclaredMethods함수를 이용, 해당 Class에 존재하는 Method를 추출합니다.


getDeclaredMethods함수를 사용하게 되면 리턴타입 및 인자 타입등등의 내용도 출력되므로, replace함수를 이용하여 함수명까지만 추려내고 다시 traceMethod함수를 호출합니다.


 function traceMethod(targetClassMethod){

  var delim = targetClassMethod.lastIndexOf(".");
  if (delim === -1){ return;}
  var targetClass = targetClassMethod.slice(0, delim);
  var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length);
  
  var hook = Java.use(targetClass);
  var overloadCount = hook[targetMethod].overloads.length;

  for(var i=0;i<overloadCount;i++){
   hook[targetMethod].overloads[i].implementation=function(){

    console.log("***Entered Method is : "+targetClass+"."+targetMethod);

    var retval = this[targetMethod].apply(this, arguments);
    return retval;
   }
  }
 }

traceMetdho함수에서는 인자로 받은 Method에 각각 후킹을 걸어 어느 함수가 호출되었는지 출력 및 기존 함수를 호출합니다.


언뜻보면 되게 간단한 코드인데, 조금 비효율적이기도 하고 솔루션이 씌워진 앱의 경우 APP_NAME이 com.abc.abcd.abcde여도 kr.net.~~~~.~~~로 시작할수도 있으므로 후킹이 되지 않는 메소드도 존재합니다. 그리고 enumerateLoadedClasses를 사용하다보니 처음에 실행하는 함수는 후킹을 못하는 경우도 있어요.. 후킹이 걸리기 전에 함수가 실행되면 후킹이 되지 않습니다...


그래서 코드 실행 시 메인 함수에 후킹을 걸어 StackTrace를 해보고 싶었으나... enumerateLoadedClasses의 특성상 실패...만약 메인함수를 두세번 호출하게 된다면 후킹이 가능하겠지만 처음 실행시에는 후킹이 걸리지 않네요..ㅠ  function traceMainActivity()는 삭제하셔도 됩니다~


그래도 앱 실행 시 호출되는 흐름을 얼추 확인할 수 있으므로 진단 시 조금 더 편의가 증가하긴 한것 같네요.


※ 기존 개발자가 개발한 버전은 어떤지 모르겠으나, 제가 작성한 코드는 버그나 에러가 조금 존재해서 참고용으로 보시면 될거 같네요 ^^:


사용방법은 python [코드를저장한파일이름].py [APP_NAME]으로 실행하시면 되고, 아래는 실행화면입니다. (SDMaid라는 앱 실행화면)




※ 개발 환경 및 단말 환경 

PC : Windows10 / Python 2.7

Mobile : 갤럭시 S6 / 6.0


'Mobile' 카테고리의 다른 글

Objection(with. iOS SSL Pinning)  (0) 2019.03.11
Cydia 에러(Could not connect to the server)  (0) 2019.02.25
Android Socket 통신 확인(with.Frida)  (2) 2019.01.18
apktool first type is not attr  (0) 2018.10.04
Android Proxy Burp 인증서 설치  (2) 2018.09.15

+ Recent posts