Self-Improvement
[UnCrackable-Level 3] Frida Hooking ( Sequence, Memory.protect , context) 본문
AOS/UnCrackable
[UnCrackable-Level 3] Frida Hooking ( Sequence, Memory.protect , context)
JoGeun 2021. 12. 1. 01:37Uncrackable Level 3
Uncrackable Level 3 을 실행하면 Rooting or tampering이 탐지되었다고 한다.
이를 우회하기 위해서 디컴파일하여 소스코드를 확인해보면 각 검사하는 메소드들 중 참이 하나라도 존재하면 showDialog() 메소드가 동작되어 진다.
showDialog() 메소드가 실행되면 System.exit(0)이 실행되어 어플리케이션이 그대로 종료가 되어진다.
System.exit() 우회
위에서 분석된 내용을 토대로 어플리케이션이 종료되는 System.exit()를 후킹하여 우회해본다.
import frida, sys
Hook_package = "owasp.mstg.uncrackable3"
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
console.log("[*] Injected JS");
Java.perform(function() {
var system = Java.use("java.lang.System");
system.exit.implementation = function(){
console.log("[*] System.exit hooking");
}
});
"""
try:
device = frida.get_usb_device(timeout=5)
pid = device.spawn([Hook_package])
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 FR1DA!")
script.load()
sys.stdin.read()
except Exception as e:
print(e)
Tampering 분석
System.exit() 함수를 제대로 후킹하여 실행하였지만 여전히 우회가 되지 않음으로 tampering 이라는 것에 의하여 안된다는 것으로 추측하여 다른 것을 찾아보기로 하였다.
여러가지 확인하던 중 어플리케이션을 실행하면서 logcat에서 의심할만한 로그가 출력되는 것을 알 수 있었다.
또한 frida -Uf owasp.mstg.uncrackable3 --no-pause을 실행하였을 때 backtrace 부분에서 libfoo.so 라이브러리의 goodbye() 함수가 실행된 것을 알 수 있었다.
해당 문자열은 자바 소스코드에는 존재하지 않으며 MainActivity 을 보면 libfoo.c 라이브러리를 로드하는 것을 보고 grep 명령어를 수행하였더니 libfoo.so 라이브러리 파일에서 사용된다는 것을 알게되어 분석하기로 하였다.
libfoo.so 라이브러리의 sub_23C4() 함수에서 프로세스의 maps를 보고 frida와 xposed 문자열이 존재하면 "Tampering detected! Terminating..." 문자열 로그를 출력하고 goodbye() 함수가 수행하는 로직이 존재한다.
sub_23C4() 함수를 호출하는 로직은 sub_2468()이며 이는 init_arrary에서 수행되는 함수이다.
그리고 init_arrary는 main() 함수를 실행하기전에 수행됨으로 라이브러리가 로드되고 수행되기전 init_arrary에 의하여 Tampering을 탐지하게 되는 것이다.
Tampering 우회 - strstsr()
이제 Tampering을 탐지하는 로직을 우회하기 위해서 strstr()에서 "frida"와 "xposed" 문자열을 검사하는 부분을 후킹하여 로그가 출력되지 않도록 해준다.
이때 후킹하는 순서도 중요하며 system.exit()를 수행하기전에 Tampering 탐지가 이루어짐으로 먼저 작성해주도록 한다.
jscode = """
console.log("[*] Injected JS");
Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
onEnter: function(args) {
this.return = 0;
var haystack = args[0];
var needle = Memory.readUtf8String(args[1]);
if(needle == "frida" || needle == "xposed"){
this.return = 1;
}
},
onLeave: function(retval) {
if (this.return == 1){
retval.replace(0);
}
return retval;
}
});
Java.perform(function() {
var system = Java.use("java.lang.System");
system.exit.implementation = function(){
console.log("[*] System.exit hooking");
}
});
"""
Tampering 우회 2 - strstr(), indexOf()
indexOf()를 사용하여 할 수 있다. indexOf에서 인자로 받은 문자열을 존재하는지 찾기되며 해당 포인터? 값을 리턴하는데 존재하지 않다면 -1을 리턴하게 된다.
그리하여 -1이 아닐 경우 즉 "frida"와 "xposed" 문자열이 존재할 경우에 if문이 돌아가도록 하였다.
jscode = """
console.log("[*] Injected JS");
Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
onEnter: function(args) {
this.return = 0;
var haystack = args[0];
var needle = Memory.readUtf8String(args[1]);
if(needle.indexOf("frida") != -1 || needle.indexOf("xposed") != -1){
this.return = 1;
}
},
onLeave: function(retval) {
if (this.return == 1){
retval.replace(0);
}
return retval;
}
});
Java.perform(function() {
var system = Java.use("java.lang.System");
system.exit.implementation = function(){
console.log("[*] System.exit hooking");
}
});
"""
Tampering 우회 3 - fopen()
https://hackcatml.tistory.com/105
위의 블로그를 참조하여 작성하였다.
Memory.protect(args[0], 16, 'rwx');을 안하면 frida가 메모리 영역에 write할 수 있는 권한이 없음으로 작성함으로써 가능하게 해준다.
jscode = """
console.log("[*] Injected JS");
Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
onEnter: function(args) {
if (Memory.readUtf8String(args[0]) == "/proc/self/maps"){
Memory.protect(args[0], 16, 'rwx'); // 메모리 접근 권한
Memory.writeUtf8String(args[0], "/proc/self/stat");
}
},
onLeave: function(retval) {
}
});
Java.perform(function() {
var system = Java.use("java.lang.System");
system.exit.implementation = function(){
console.log("[*] System.exit hooking");
}
});
"""
문자열을 입력한 후 VERIFY 버튼을 클릭하게 되면 알맞지 않다는 텍스트 박스가 출력되어 진다.
MainActivity에서 확인해보면 입력받은 문자열을 obj 변수로 받은 후 check 클래스의 check_code() 메소드의 인자로 사용되고 있다.
check_code() 메소드를 확인해보면 네이티브로 정의된 bar()의 리턴된 값을 그대로 리턴하게 되고 참일 경우에는 secert 값이 맞다고 출력되고 아닐 경우에는 틀렸다고 출력하게 된다.
check_code() 후킹
위 문제를 풀기 위한 첫 번째 방법은 역시나 check_code() 메소드의 리턴값을 무조건 참으로 후킹하면 될 것 이다.
jscode = """
console.log("[*] Injected JS");
Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
onEnter: function(args) {
this.return = 0;
var haystack = args[0];
var needle = Memory.readUtf8String(args[1]);
if(needle.indexOf("frida") != -1 || needle.indexOf("xposed") != -1){
this.return = 1;
}
},
onLeave: function(retval) {
if (this.return == 1){
retval.replace(0);
}
return retval;
}
});
Java.perform(function() {
var system = Java.use("java.lang.System");
system.exit.implementation = function(){
console.log("[*] System.exit hooking");
}
var ck = Java.use("sg.vantagepoint.uncrackable3.CodeCheck");
ck.check_code.implementation = function(arg1){
var retval = this.check_code(arg1);
console.log("[*] return val = "+retval);
return true;
}
});
"""
bar() 후킹
https://richong.tistory.com/428
https://m.blog.naver.com/gigs8041/221982471693
https://chp747.tistory.com/362
bar() 함수에서 qword_15038에 있는 값을 출력해보니 XOR key인 "pizzapizza..."가 존재하였다.
XOR Key 값과 입력한 값을 XOR한 후 어떤 특정한 값과 1byte 씩 비교하게 되는데 이때 비교하는 offset 주소 0x3450을 후킹하여 강제로 서로 같게 한 후 W12(=X12)를 출력하게 하면 Secret 값을 얻을 수 있다.
jscode = """
Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
onEnter: function(args) {
if (Memory.readUtf8String(args[0]) == "/proc/self/maps"){
Memory.protect(args[0], 16, 'rwx');
Memory.writeUtf8String(args[0], "/proc/self/stat");
}
},
onLeave: function(retval) {
}
});
function native_bar(){
var foo = Module.findBaseAddress('libfoo.so');
if(!foo){
console.log("libfoo not loaded!");
return 0;
}
console.log("[*] libfoo.so = " +foo); // == foo.toString()
//var base_off = foo.add(0x15038); // XOR Key
//console.log("[*] base_off = "+ hexdump(base_off, {
// offset: 0,
// length: 32,
//}));
var cmp_key = foo.add(0x3450); // CMP W12, W10
Interceptor.attach(cmp_key,{
onEnter: function(args) {
//console.log("[*] context = "+JSON.stringify(this.context));
this.context.x12 = this.context.x10;
console.log("[*] x12 = "+this.context.x12);
},
onLeave: function(retval) {
}
});
}
Java.perform(function() {
var system = Java.use("java.lang.System");
system.exit.implementation = function(){
console.log("[*] exit hooking");
native_bar();
}
});
"""