앵하니의 더 나은 보안

Frida Encryption/Socket Viewer 본문

보안 기술/Android

Frida Encryption/Socket Viewer

앵한 2022. 7. 18. 22:56

증권 어플리케이션 진단 시 작성해 사용했던 frida 스크립트를 공유한다.

증권사는 실시간 매수/매도 때문에 빠른 통신에 민감하다.

그래서 증권사 어플리케이션에서는 HTTP 통신이 아니라 TCP/IP 소켓 통신을 수행한다.

이에 따라 어플리케이션에서 TCP/IP 소켓 내용을 확인하기 위해선 소켓 관련 api를 후킹해 패킷을 확인해야한다.

근데 또 소켓 데이터는 byte array 자료형이라, byte array 자료형의 16진수와 그에 맵핑되는 문자들을 편하게 보기위해

show_byte 함수를 작성해 사용했다.

 

암/복호화 후킹은 덤

자바 api에서 사용하는 암/복호화 하는 동작은 모두 확인할 수 있다.

 

var view_length = 16; 
var print_encrypt = 1; 
var print_socket = 1; 
function show_byte(byte_arr, filter){
	var buffer = byte_arr;
	var byte_hex = ""
	var one_line = "";
	var show_byte_text = ""
	var check_show_byte = 0
	var view_hex = 0
	var com_string = "";
	for(var i = 0; i < buffer.length; ++i){
		view_hex = buffer[i] & 0xff
		var hex_data = (view_hex).toString(16);
		if (hex_data.length == 1){
			hex_data = "0"+hex_data;
		}
		
		byte_hex += hex_data + " ";
		if (view_hex == 0)
			one_line += "●"
		
		else if (31 < view_hex && view_hex < 127)//아스키 코드로 표현할 수 있는 문자 범위 내라면~
			one_line += (String.fromCharCode(view_hex));
		
		else if(234<=view_hex && view_hex<=237)//ea, eb, ec, ed로 시작한다면~ uft-8 디코딩
		{
			var utf8_encoded = "%"+hex_data+"%"+(buffer[i+1] & 0xff).toString(16)+"%"+(buffer[i+2] & 0xff).toString(16);
			try{
				var decode_utf8 = decodeURIComponent(utf8_encoded);
				one_line += decode_utf8;
			}
			catch{
				one_line += "■";
			}
		}
		
		else{
			one_line += "■";
		}
		
		if (i%view_length==view_length-1){//설정된 view_length(기본 16)단위로 줄넘김 삽입
			check_show_byte = 1
			show_byte_text += byte_hex+"\t"+one_line+"\n";
			com_string+=one_line;
			one_line = ""
			byte_hex =""
		}
		
	}
	
	if (!check_show_byte)//지정한 view_length 보다 bytearray가 짧을때
		show_byte_text += byte_hex+"\t"+one_line+"\n";
		
	
	if(filter!=""){//필터로 넘어온 문자열 값이 포함된 소켓만 보여주기
		if (!(com_string.indexOf(filter)==-1)){
			return show_byte_text+"\n"+com_string+"\n";
		}
		else{
			return "";
		}
	}
	else
		return show_byte_text+"\n"+com_string+"\n";
}
Java.perform(function () {
	if(print_encrypt){ 
		Java.use('javax.crypto.spec.SecretKeySpec').$init.overload('[B','java.lang.String').implementation = function(arg1,arg2) { 
			console.log("\n\n[*] SecretKeySpec key data ("+arg2+")\n"+show_byte(arg1,"")); 
			return this.$init(arg1,arg2); 
		}; 
		var check_plain = Java.use("javax.crypto.Cipher"); 
		check_plain.doFinal.overload("[B").implementation = function(arg1){ 
			var retval = this.doFinal(arg1); 
			console.log("[*] plain text\n"+show_byte(arg1,"")); 
			console.log("[*] encrypted text\n"+show_byte(retval,"")); 
			return retval; 
		}; 
	}
	if(print_socket){
		var read_hk = Java.use("java.io.InputStream"); 
		read_hk.read.overload("[B","int","int").implementation = function(arg1,arg2,arg3){ 
			console.log("read's arg1\n"+show_byte(arg1,"")+"\n"); 
			console.log("read's arg2 > "+arg2) 
			console.log("read's arg3 > "+arg3) 
			return this.read(arg1,arg2,arg3) 
		}; 
		var socket_out_check = Java.use('java.io.OutputStream'); 
		socket_out_check.write.overload("[B").implementation = function(arg1){ 
			console.log("\n[Socket out]\n"+show_byte(arg1,"")+"\n") 
			this.write(arg1); 
		}; 
		var write_hk = Java.use("java.io.ByteArrayOutputStream"); 
		write_hk.write.overload("[B","int","int").implementation = function(arg1,arg2,arg3){ 
			console.log("write's arg1\n "+show_byte(arg1,"")+"\n"); 
			this.write(arg1,arg2,arg3) 
		}; 
		var read_hk = Java.use("java.io.ByteArrayInputStream"); 
		read_hk.read.overload("[B","int","int").implementation = function(arg1,arg2,arg3){ 
			console.log("ByteArrayInputStream read's arg1\n"+show_byte(arg1,"")+"\n"); 
			return this.read(arg1,arg2,arg3) 
		};
		Java.use("java.io.BufferedInputStream").read.overload('[B', 'int', 'int').implementation = function(arg1,arg2,arg3){
			console.log("BufferedInputStream read's data\n"+show_byte(arg1,"")+"\n");
			return this.read(arg1,arg2,arg3);
		};
		Java.use("java.io.DataInputStream").read.overload("[B", "int", "int").implementation = function(arg1,arg2,arg3){
			console.log("DataInput read's data\n"+show_byte(arg1,"")+"\n");
			return this.read(arg1,arg2,arg3);
		}
	}
});

 

혹 다른 byte array의 데이터를 보고싶다면

console.log(show_byte(arg1,arg2))로 작성해 사용하면 된다.

arg1 은 보고자하는 바이트형 데이터의 변수를 뜻한다.

arg2 는 필터링할 문자열을 뜻하는데, 바이트 데이터(16진수 데이터)에 맵핑되는 문자열 내에 arg2의 문자열이

포함되면 해당 소켓의 데이터를 문자열 형식으로 반환하고, 포함되지 않으면 빈 문자열을 반환한다.

한마디로 그냥 보고싶은 문자열이 포함된 소켓 데이터만 출력하게 하는 역할이다.

 

ex) console.log(show_byte(byte_data, "test1234"));

 > byte_data를 16진수와 그에 맵핑되는 문자열로 보려하는데,

그 중에서도 test1234가 있는 byte_data만 출력해줘라

 

만약 필터링할 문자열 없이 모든 소켓 데이터를 보고자 한다면 arg2로 빈문자열을 전달하면 된다.

ex) console.log(show_byte(byte_data, ""));

 

console.log("[Socket in byte array]\n"+show_byte(byte_data,"")) 동작 예시

 

Comments