![[붉은외계인] Mobile - OWASP UnCrackable L2](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboBOxY%2FbtsFuLWtDg0%2FwTtCCvh524miyCt59Cdfw1%2Fimg.png)
2024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1
[붉은외계인] Mobile - OWASP UnCrackable L1
Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스로 Android, iOS 상당히 많은 앱을 제공하고 있고, 앱 외에도 해킹 도구라던지 각종 좋은 정보들
redalien.tistory.com
1단계에 이어서, 2단계를 알아보겠다
참고로, 2단계에서의 루팅 우회는 1단계에서와 동일하기 때문에,
이번 포스트에서 루팅 우회는 넘어갈 것이다
분석
루팅 우회를 한 후, 어플에 접속을 해보면 위의 그림과 같다
이것 또한 Secret String을 입력해야한다
jadx를 통해 분석을 해보자
코드를 살펴보면, verify 메소드에서 사용자가 입력한 값을 obj 변수에 넣은 후,
this.m.a(obj) 메소드를 통해 Secret String여부를 체크하는 것을 확인할 수 있다
m이 무엇인지 살펴보면, CodeCheck이라는 클래스 인스턴스인 것을 확인할 수 있다
CodeCheck의 클래스를 확인해보면 위의 그림과 같다
가장 먼저, bar() 라는 Native 메소드를 선언한 다음에,
a() 메소드 내부, bar() 메소드에 Parameter값을 Byte로 변환하여 넣는 것을 확인할 수 있다
( 사용자 입력값이 Byte로 변환되어 들어가는 것 )
이러한 Native 메소드는 JNI를 통하여 사용할 수 있다
JNI는 Java에서 Native Code의 메소드를 사용할 수 있게 해주거나,
Native Code에서 Java 메소드를 사용할 수 있게 해주는 프레임워크이다
Java에서 Native 메소드를 사용하기 위해서는 2가지 조건이 필요하다
◆ 위에서 알아보았듯, 먼저 Native로 선언해야한다
◆ System.load 혹은 System.loadLibrary를 통해 외부 파일을 불러와야 한다
MainActivity 클래스에서 확인해보면 바로 위의 그림과 같이, 해당 메소드를 확인할 수 있다
foo 파일을 불러오고 있다
위의 그림처럼 jadx를 통하여 바로 확인할 수 있지만,
어떤 아키텍처의 so 파일이 올라가 있는지 모르기 때문에 체크하는 과정이 필요하다
체크하는 방법은 위의 그림과 같다
프로세스의 PID를 구한 후, 프로세스 관련 정보가 저장된 /Proc로 이동후, 해당 PID 디렉토리로 이동한다
그리고 메모리 관련 정보가 저장되어 있는 maps 파일에서, 로드된 so 파일을 찾으면 된다
위의 그림처럼 x86 아키텍처의 so 파일이 사용되고 있음을 확인할 수 있다
adb pull을 통해 해당 파일을 가져온 후, IDA 디스어셈블러를 통해 해당 파일을 열어본다
JNI를 통해 사용되는 Native 메소드는 "패키지_클래스_메소드" 순으로 이름이 작성되기 때문에
IDA에서 Java_sg_vantagepoint_uncrackable2_CodeCheck_bar 메소드를 열면 위의 그림과 같다
( F5를 누르면 C언어로 변환하여 보여준다 )
해결 방법 1
jadx 디컴파일을 통해 verify() 메소드를 확인해보면 this.m.a(obj)의 값이 참이 나와야한다
그리고 IDA를 통해 bar() 메소드를 확인해보면 1이 나와야만이 this.m.a(obj)가 참이 나온다
결론적으로, bar()메소드가 1을 반환하게 해주면 해당 문제를 해결할 수 있다
Frida를 사용하여, so 파일의 bar() 메소드를 후킹해보겠다
Java.perform(function(){
console.warn("[+] Frida Start ...");
let Activity = Java.use("sg.vantagepoint.a.b")
Activity.a.implementation = function(){return false}
Activity.b.implementation = function(){return false}
Activity.c.implementation = function(){return false}
Interceptor.attach(Module.getExportByName("libfoo.so", "Java_sg_vantagepoint_uncrackable2_CodeCheck_bar"), {
onEnter : function(arg){},
onLeave : function(arg){arg.replace(1)}
})
console.warn("[+] Done !")
})
코드를 실행하면 위의 그림과 같이, 아무거나 입력하여도 Success가 뜨는것을 확인할 수 있다
( 위에서 발생하는 오류는 코드를 재저장하면 오류없이 실행 할 수 있다 )
Java.perform(function(){
console.warn("[+] Frida Start ...");
let Activity = Java.use("sg.vantagepoint.a.b")
Activity.a.implementation = function(){return false}
Activity.b.implementation = function(){return false}
Activity.c.implementation = function(){return false}
let Activity2 = Java.use("sg.vantagepoint.uncrackable2.CodeCheck")
Activity2.a.implementation = function(arg){return true}
console.warn("[+] Done !")
})
혹은 Module내부의 함수를 건드리지 않고, CodeCheck 클래스의 a 함수가 true를 리턴하게 변경하여도
해결할 수 있다 위의 코드와 같다
해결 방법 2
bar() 함수의 코드를 살펴보면 strcpy 함수를 사용하여
길이 24 배열 변수v5에 "Thanks for all the fish" 문자열을 복사하여 넣었다
그리고 무엇인지는 모르겠지만 특정 연산 다음에
strncmp 함수를 사용하여, 변수 v3, v5가 길이 23인지를 비교하고 있다
만약 두 변수의 길이가 같다면 0을 반환하는데 ! 연산자로 인하여 True로 변경된다
앞의 연산들지 어떠한 연산을 하는지는 모르겠으나, Thanks for all the fish와 비교하는것은 확실하다
해당 문자열을 넣어보면 성공하는 것을 확인할 수 있다
Java.perform(function(){
console.warn("[+] Frida Start ...");
let system = Java.use("java.lang.System")
system.exit.overload("int").implementation = function(){
console.warn("Avoid exiting the App :)");
}
Interceptor.attach(Module.findExportByName("libfoo.so","strncmp"),{
onEnter: function(args){
var param1 = Memory.readUtf8String(args[0]);
var param2 = Memory.readUtf8String(args[1]);
if(param1.indexOf('01234567890123456789012') !== -1){
console.log(param1);
console.log(param2);
}
},
onLeave: function(retval){}
})
console.warn("[+] Done !")
})
만약, Secret String을 모른다고 했을 때, 확인하는 코드는 위와 같다
길이 23을 맞춘 "01234567890123456789012" 문자열을 넣었을 때,
strncmp() 함수의 Argument로 들어오는 것을 출력하는 코드이다
◆ onEnter의 args는 후킹한 함수가 실행되기 전의 Argument를 배열로 받고,
◆ onLeave의 retval은 함수의 return값을 받는다
◆ indexOf는 문자열에서 특정 문자열을 찾은 후, 해당 문자열 첫 번째 문자의 index를 반환한다 일치하지
않을 경우는 -1을 반환한다
해당 코드를 실행하면 위의 그림과 같이, 결과를 얻을 수 있다
오류
참고로, 위의 코드를 Android 5 (32bit) 환경에서 그대로 사용하면 위의 그림과 같이 에러가 발생한다
이유는 찾지 못했다
하지만 Android 9 (64bit) 환경에서 사용할 경우, 에러없이 성공적으로 실행되는 것을 확인하였다
마무리
해당 문제를 풀어보면서, JNI에 대해서 처음 배웠다
풀이 과정 또한 다양하게 찾아보았는데, Ghidra, Radare2 등 자기만의 방식으로 풀어본 사람들이 많았다
해당 단계 덕분에 많은 공부를 하게 된 것 같다
끝 ~
'Security > Mobile' 카테고리의 다른 글
[붉은외계인] Mobile - Frida Detected Java 코드 (0) | 2024.05.09 |
---|---|
[붉은외계인] Mobile - OWASP UnCrackable L3 (0) | 2024.03.12 |
[붉은외계인] Mobile - OWASP UnCrackable L1 (0) | 2024.03.04 |
[붉은외계인] Mobile - FridaLab 풀이 (1) | 2024.02.26 |
[붉은외계인] Mobile - smali 코드 분석 1 with KGB Messenger (0) | 2024.02.14 |
IT / Android
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!