Frida를 이용한 훅스크립트 자동 생성 기능 2차 내용이다.

완벽한 자동화를 목표했으나, 하면 할수록 넘어야할 산이 매우 많은 것을 느끼고 있다.


개발 2차시에 큰 목표는 다음과 같다.

  • 다중 Class/Method 후킹 스크립트 생성
  • Arg/Ret 입력/출력 시 데이터형에 따른 이슈 확인



이번에도 추가된 주요 기능만을 설명하겠다.


다중 함수 후킹 코드는 기본 후킹 코드을 세분화하여 필요한 횟수만큼 반복하는 형태로 구현하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# self.target 은 타겟 Class정보를 Dic{}형태로 가진 List[] 변수
for i in range(len(self.target)):
    tClass = self.target[i]['class']
    tMethod = self.target[i]['method']
    mode = self.target[i]['mode']
    
    if self.target[i]['exe'> 0:
        exe = 'true'
 
    hcode += self.hook_head % (i, tClass, i, i, tMethod, i, i, tClass, tMethod, i, i, tMethod, i, tMethod, tMethod, tMethod)
            
    if (mode%2== 0:
        hcode += self.hook_arg_input
            
    if mode > 2:
        hcode += self.hook_tail % (exe, tMethod, tMethod, 'true')
    else:
        hcode += self.hook_tail % (exe, tMethod, tMethod, 'false')



후킹한 함수의 Argument Type이 object인 경우 해당 클래스 인스턴스의 멤버변수 정보(type, name, value)를 출력하는 기능이다.

arguments[index].class.getDeclaredFields() 해당 함수 실행 시 클래스멤버변수 정보를 배열로 반환한다.

# getDeclaredMethods( )는 클래스멤버함수 정보를 반환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(String(typeof(arguments[index])) == "object"){
    var var_name;
    var var_type;
    ownVars = arguments[index].class.getDeclaredFields();
 
    for(var i in ownVars){
        var_name = String(ownVars[i]).split('.');
        var_name = var_name[var_name.length-1];
 
        if(var_name!='$change' && var_name!='serialVersionUID') {
            var_type = String(ownVars[i]).split(' ');
            var_type = var_type[var_type.length-2];
            arg_dump += gubunja + "           L (" + var_type + ") " + var_name + " = " + String(eval("arguments[index]."+var_name+"['value']"));
        }
    }
}



Android 진단 시 유용하게 사용중인 frida를 이용하여 좀더 편리하게 작업하고자 후킹 자동화 코드를 개발하게 되었다.

# 어디까지나 내가 쓰기 편하기 위해 만든 내 입맛 맞춤용이다.


이러한 코드를 개발하게된 계기는 house 라는 반자동화(?) 모듈을 사용하는데 너무나도 훌륭한 모듈이지만 몇가지 기능이 아쉬워 직접 개발해보게 되었다.

그래서 house 코드들을 참고/분석하여 짜집기 식으로 개발하고 있다. (house 추천!!)


#house URL : https://github.com/nccgroup/house


코드 실행 시 다음의 입력을 요구하며, 요구에 맞게 선택하면 관련 기능의 스크립트를 자동으로 생성하여 Attach 하게된다.

  • class명을 입력하면 class내 method 목록이 나열되며, 목록 중 후킹 대상 method 선택(입력) 
  • method argument 타입 등의 정보는 자동으로 인식
  • method arg와 ret 데이터를 조회만 할것인지 조작까지 할 것인지 선택
  • arg와 ret 데이터 중 어떤 정보를 조작할지 선택
  • 후킹 함수의 실행 여부 선택(함수 실행을 못하도록 제한하는 경우를 고려)


아직 개선이 많이 필요한 코드이기에 전체 코드가 아닌 주요 부분만 포스트 하도록 하겠다.



Class 내 선언된 Method 정보(권한, 리턴타입, 함수명, 인자타입) 확인

1
2
3
4
5
6
7
8
9
10
Java.perform(function() {
    var method = "Methods:";
    var hook = Java.use("%s");
    var ownMethod = hook.class.getDeclaredMethods();
    ownMethod.forEach(function(value){
        method += String(value) + "/";
    });
    send(method);
    hook.$dispose;
});


후킹 대상 함수의 실행 콜 스택을 나열

1
2
3
function getCaller(){
    return clazz_Thread.currentThread().getStackTrace().slice(2,5).reverse().toString();
}



후킹 대상 함수를 지정 시 overload 함수가 있는지 알수 있으며, 반복문을 통해 모두 후킹

# 특정 함수만으로 제한하도록 구현할까 하다 우선 모두 후킹하도록 구현

1
2
3
4
5
6
hook0 = Java.use("클래스명");
var overloadz_hook0 = eval("hook0.메서드.overloads");
var count_over_hook0 = overloadz_hook0.length;
 
for (var i = 0; i < count_over_hook0; i++)
    hget_hook = eval('overloadz_hook0[i]');


후킹 대상 함수의 리턴 타입

1
var ret_type = String(hget_hook.returnType['className']);



동적으로 데이터를 입력하고 스크립트에서 받기

1
2
#python code
script.post({'type''input''payload': data})


1
2
3
4
5
6
7
//hook script
while (true){
    var op = recv('input'function(value) {
        recv_data = value.payload;
    });
    op.wait();
}



HMAC 개념 자료 : http://crono.tistory.com/3?category=779861

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
 
public class Main extends AppCompatActivity {
    private Button button;
    private EditText ed;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final Hmac hmsha256 = new Hmac();
 
        button = (Button)findViewById(R.id.button);
        ed = (EditText)findViewById(R.id.input);
 
        button.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View view){
                String result;
                if (ed.toString() != ""){
                    result = hmsha256.hget(ed.getText().toString());
                    Log.d("HMAC-SHA256",result);
                    Toast.makeText(getApplicationContext(), result, Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}



Hmac.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
 
 
public class Hmac {
    // hash 알고리즘 선택
    private static final String ALGOLISM = "HmacSHA256";
    // hash 암호화 key
    private static final String key = "nsHc6458";
 
 
    public static String hget(String message) {
        try {
            // hash 알고리즘과 암호화 key 적용
            Mac hasher = Mac.getInstance(ALGOLISM);
            hasher.init(new SecretKeySpec(key.getBytes(), ALGOLISM));
 
            // messages를 암호화 적용 후 byte 배열 형태의 결과 리턴
            byte[] hash = hasher.doFinal(message.getBytes());
            return byteToString(hash);
        }
        catch (NoSuchAlgorithmException e){
            e.printStackTrace();
        }
        catch (InvalidKeyException e){
            e.printStackTrace();
        }
        return "";
    }
 
    // byte[]의 값을 16진수 형태의 문자로 변환하는 함수
    private static String byteToString(byte[] hash) {
        StringBuffer buffer = new StringBuffer();
 
        for (int i = 0; i < hash.length; i++) {
            int d = hash[i];
            d += (d < 0)? 256 : 0;
            if (d < 16) {
                buffer.append("0");
            }
            buffer.append(Integer.toString(d, 16));
        }
        return buffer.toString();
    }
}


EditText 문자를 입력 받아 공유된 key를 바탕으로 sha256으로 암호화하여 hash를 반환하는 코드들로 검색 당시 많은 예제 코드에서 위에서 사용된 암호화 class 와 method를 사용하고 있었다.


하여, 위 코드를 바탕으로 HMAC 적용 App 진단 시 공략 포인트를 체크하고자 한다.


SecreKeySpec( ) class 초기화 시 Key와 hash알고리즘 정보가 인자로 전달되므로 암호화 관련 주요한 정보를 얻을 수 있는 공략 포인트가 될 것이다.


*.doFinal( ) method에서는 메시지의 byte형태의 데이터를 받아 Hash화 하는 함수로 Hook을 통해 변환될 데이터를 확인할 수 있다.


만약 진단 대상 APP에 hget( ) 과 같은 Method가 있다면 hook을 통해 입력 메세지를 얻을 수 있고, return 값을 조작하여 데이터 조작도 가능할 것이다.

return 값 조작 시에는 동일한 hash알고리즘과 Key를 이용하여 조작 메시지를 hash화하여야 한다.


hash값 조작 시 사용할 HMAC 파이썬(v2) 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import hashlib
import hmac
import base64
 
message = bytes("nshc is possible").encode('utf-8')
shared_key = bytes("nsHc6458").encode('utf-8')
 
hash = hmac.new(shared_key, message, hashlib.sha256)
 
print hash
 
result = hash.hexdigest()
 
print "Hash : %s(%d)" % (result, len(result))
#print "Base64(hash) : %s" % base64.b64encode(result)


+ Recent posts