서론프로젝트를 진행하는 과정에서, 정리해두면 좋겠다는 부분이 있어서 오랜만에 글을 쓴다글을 안 쓴지 이렇게 오래되었을 줄이야..본론으로 넘어가서, 오늘 작성할 내용은 Java 코드를 사용하여, Frida Server를 감지하는 코드이다해당 코드를 여러 번 시도해본 결과, 특징과 주의사항은 아래와 같다1. Android 14에서는 작동하지 않는다 ( 현재, Android 9까지 작동되는 것을 확인하였다 )2. Runtime.getRuntime.exec()를 사용하는 것이 아닌, ProcessBuilder 객체를 사용해야한다3. ProcessBuilder에 Argument를 줄 때, "ps -a" 가 아닌, "ps", "-a" 이런식으로 주어야 한다 본론으로 들어가보자 - Runtime.getRuntime..
🔒 Security/Mobile
[붉은외계인] Mobile - OWASP UnCrackable L3
🗓️ 2024.03.12
💬
2024.03.05 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L2 [붉은외계인] Mobile - OWASP UnCrackable L22024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1 [붉은외계인] Mobile - OWASP UnCrackable L1 Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스redalien.tistory.com이전 Level 2단계에 이어서, 3단계를 진행하겠다바로 들어가보자! 시작해당 단계 또한, Rooting을 감지하면 앱을 종료시켜버린다매 단계에서 진행했던 Rooting 우회를 시도해보겠..
🔒 Security/Mobile
[붉은외계인] Mobile - OWASP UnCrackable L2
🗓️ 2024.03.05
💬
2024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1 [붉은외계인] Mobile - OWASP UnCrackable L1Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스로 Android, iOS 상당히 많은 앱을 제공하고 있고, 앱 외에도 해킹 도구라던지 각종 좋은 정보들redalien.tistory.com 1단계에 이어서, 2단계를 알아보겠다 참고로, 2단계에서의 루팅 우회는 1단계에서와 동일하기 때문에, 이번 포스트에서 루팅 우회는 넘어갈 것이다 분석루팅 우회를 한 후, 어플에 접속을 해보면 위의 그림과 같다 이것 또한 Secret String을 입력해야한다j..
🔒 Security/Mobile
[붉은외계인] Mobile - OWASP UnCrackable L1
🗓️ 2024.03.04
💬
Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다OWASP에서는 래퍼런스로 Android, iOS 상당히 많은 앱을 제공하고 있고, 앱 외에도 해킹 도구라던지 각종 좋은 정보들을 제공하고 있다아래 링크를 첨부할테니, 꼭 한번씩 들어가보길 바란다https://mas.owasp.org/MASTG/apps/ Reference applications - OWASP Mobile Application SecurityReference applications The applications listed below can be used as training materials. Note: only the MASTG apps and Crackmes are tested..
🔒 Security/Mobile
[붉은외계인] Mobile - FridaLab 풀이
🗓️ 2024.02.26
💬 1
들어가며Frida를 배우고 난 후, 연습을 위하여 FridaLab 을 풀어보았다 1 ~ 5단계는 쉽기 때문에, 해당 포스트에서는 6 ~ 8 단계만 다루고 있다나머지 Solution Scripts는 아래 Github에 올려놓았으니, 참고하길 바란다https://github.com/RedAlien00/FridaLab GitHub - RedAlien00/FridaLabContribute to RedAlien00/FridaLab development by creating an account on GitHub.github.com 서론 FridaLab을 풀어볼 때, 외우는것이 아닌 왜 이러한 메소드를 썼고, 이러한 코드를 짰는가에 포인트를 두고 진행하였다그리고 많은 풀이들을 살펴보고, 이리저리 바꾸어가며 나의 걸..
🔒 Security/Mobile
[붉은외계인] Mobile - smali 코드 분석 1 with KGB Messenger
🗓️ 2024.02.14
💬
KGB Messenger CTF를 하던 중에, 처음으로 smali 코드 분석을 하게 되었다smali코드를 이해할 수 있다면 추후, 취약점 분석에 많은 도움이 될 거 같아서, 오늘 공부한 내용을 정리하려한다 시작해보자 Java 소스 코드아래는 원본 Java 소스 코드이다 ( 디컴파일은 jadx-gui를 통해 하였다 )package com.tlamb96.kgbmessenger;import android.content.DialogInterface;import android.content.Intent;import android.os.Bundle;import android.support.p022v7.app.ActivityC0494c;import android.support.p022v7.app.DialogInte..
🔒 Security/Mobile
[붉은외계인] Mobile - Diva Hardcoding Issues - Part 2
🗓️ 2024.02.07
💬
Nox 에뮬레이터에, 취약한 모바일 앱인 diva를 통하여 공부를 하던 도중 상당히 재미있었던 부분을 정리할려고 한다 시작해보자 Hardcoding Issues - Part 2 Part 1은 정말 쉬웠으나, Part 2는 jadx를 통해 디컴파일한 소스를 들여다보는 과정 + 추가 작업이 필요하다 Hardcode2Activity 클래스에서는 DivaJni 클래스의 access 함수를 사용한다 이러한 access 함수는 native 키워드가 사용되었기 때문에, JNI를 통해서 라이브러리를 호출 할 때 구현될 것이다 어떤 라이브러리를 호출하는지 살펴보면 divajni 라이브러리를 호출하고 있다 라이브러리 jadx를 통해 컴파일 한 후, lib 디렉토리를 살펴보면 각 아키텍쳐마다 사용할 수 있게끔, 동적 라이브..
프로젝트를 진행하는 과정에서, 정리해두면 좋겠다는 부분이 있어서 오랜만에 글을 쓴다 글을 안 쓴지 이렇게 오래되었을 줄이야..
본론으로 넘어가서, 오늘 작성할 내용은 Java 코드를 사용하여, Frida Server를 감지하는 코드이다 해당 코드를 여러 번 시도해본 결과, 특징과 주의사항은 아래와 같다
1. Android 14에서는 작동하지 않는다 ( 현재, Android 9까지 작동되는 것을 확인하였다 ) 2. Runtime.getRuntime.exec()를 사용하는 것이 아닌, ProcessBuilder 객체를 사용해야한다 3. ProcessBuilder에 Argument를 줄 때, "ps -a" 가 아닌, "ps", "-a" 이런식으로 주어야 한다
참고로, 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() 메소드를 후킹해보겠다
혹은 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 등 자기만의 방식으로 풀어본 사람들이 많았다 해당 단계 덕분에 많은 공부를 하게 된 것 같다
반대로, 위에 코드는 제작자가 원하는 방식의 풀이 방법을 기술한 것이며, 해결 포인트는 아래와 같다
1. MainActivity가 Oncreate 될 때, 위의 그림처럼 메소드가 호출되면서 challenge_06 클래스의 chall06, timeStart 필드값이 설정된다는 것 2. 10초가 지난 뒤에, chall06값과 일치하는 int 자료형을 넣으면 해결된다는 것
10초가 지난 뒤에 실행은 setTime() 함수로 구현하면 된다
문제 해결을 위해서는 MainActivity.chall06() 메소드를 호출하여야 하는데, 해당 메소드는 인스턴스 맴버이기 때문에, Java.use가 아닌, Java.choose를 사용해야 한다 ( 위에서 언급하였지만, Java.choose는메모리에 적재된 인스턴스 맴버에 접근하는 메소드라고 하였다 )
하지만 challenge_06의 chall06 필드는 static 맴버이기 때문에 Java.use를 사용하여 접근하여야 한다
Challenge 7
이번 단계의 목표는 check07pin()메소드를 Brute Force하여 chall07을 confirm 하는 것이다
이것 또한 위에서 설명한 것과 마찬가지로, 인스턴스 멤버는 Java.choose를, Static 맴버는 Java.use를 사용하여 접근하면 된다
0.0 ~ 0.9 의 난수를 생성하는 Math.random()함수 뒤에 추가로 연산이 있는데, 이는 결국 1000 ~ 9999이하 범위의 난수를 생성하는 것이다
그리고 랜덤 숫자는 "" + <랜덤숫자> 의 연산을 통해 chall07 필드에 저장된다 빈 문자열과 문자열 연결 연산이 되기 때문에, 만약 랜덤 숫자가 8이라면 "8" 문자열이 저장될 것이다 이러한 이유로, chall07() 메소드에 매개변수를 집어넣을 때는 문자열로 자료형 변환을 해주어야 한다
challenge 8
해당 단계에서 목표는 check 버튼의 text value를 Confirm으로 변경하는 것이다
KGB Messenger CTF를 하던 중에, 처음으로 smali 코드 분석을 하게 되었다
smali코드를 이해할 수 있다면 추후, 취약점 분석에 많은 도움이 될 거 같아서, 오늘 공부한 내용을 정리하려한다 시작해보자
Java 소스 코드
아래는 원본 Java 소스 코드이다 ( 디컴파일은 jadx-gui를 통해 하였다 )
package com.tlamb96.kgbmessenger;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.p022v7.app.ActivityC0494c;
import android.support.p022v7.app.DialogInterfaceC0492b;
import android.widget.LinearLayout;
import com.tlamb96.spetsnazmessenger.R;
import p000a.p001a.p002a.p003a.C0000a;
/* loaded from: classes.dex */
public class MainActivity extends ActivityC0494c {
/* renamed from: a */
private void m531a(String str, String str2) {
DialogInterfaceC0492b m2324b = new DialogInterfaceC0492b.C0493a(this).m2324b();
m2324b.setTitle(str);
m2324b.m2331a(str2);
m2324b.setCancelable(false);
m2324b.m2333a(-3, "EXIT", new DialogInterface.OnClickListener() { // from class: com.tlamb96.kgbmessenger.MainActivity.1
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
MainActivity.this.finish();
}
});
m2324b.show();
LinearLayout linearLayout = (LinearLayout) m2324b.m2334a(-3).getParent();
linearLayout.setGravity(1);
linearLayout.getChildAt(1).setVisibility(8);
}
@Override // android.support.p008v4.p010b.ActivityC0099l, android.app.Activity
public void onBackPressed() {
Intent intent = new Intent("android.intent.action.MAIN");
intent.addCategory("android.intent.category.HOME");
intent.setFlags(268435456);
startActivity(intent);
}
@Override // android.support.p022v7.app.ActivityC0494c, android.support.p008v4.p010b.ActivityC0099l, android.support.p008v4.p010b.AbstractActivityC0090h, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
String property = System.getProperty("user.home");
String str = System.getenv("USER");
if (property == null || property.isEmpty() || !property.equals("Russia")) {
m531a("Integrity Error", "This app can only run on Russian devices.");
} else if (str == null || str.isEmpty() || !str.equals(getResources().getString(R.string.User))) {
m531a("Integrity Error", "Must be on the user whitelist.");
} else {
C0000a.m3944a(this);
startActivity(new Intent(this, LoginActivity.class));
}
}
}
smali 코드 1
smali코드를 부분부분 보면서, 해석을 하겠다
그 전에, 미리 알아둬야 할 것을 적어두겠다
◆ p0, p1, p2 ~ : p0은 보통 java에서와 같이 this를 의미한다 p1, p2 ~ 는 레지스터를 의미하되, 매개변수로 들어가는 것을 의미한다 ◆ v0, v1, v2 ~ : 이것 또한 레지스터를 의미하되, 전역 변수를 담는 레지스터이다
1번째 줄 : 현재 클래스의 경로를 나타낸다 2번째 줄 : 부모 클래스의 경로를 나타낸다
6번째 줄 : 생성자를 정의하는 부분이다 <init>()은 어떠한 매개변수도 받지 않는다는 의미
Java에서는 객체를 생성할 때, 반드시 생성자를 호출하게 된다 하지만 클래스를 정의할 때, 생성자를 정의하지 않았더라도, 컴파일러가 자동으로 생성자를 생성한다 원본 소스 코드를 보면, 생성자를 정의하는 부분이 없다 그래서 이러한 smali 코드가 생성된 것 같다
7번쨰 줄 : 해당 메소드에서 사용되는 레지스터는 1개이다
9번째 줄 : invoke-direct는 메소드를 직접 호출하는 명령어이다 invoke-direct의 특징은 public,protected의 상관없이 메소드를 호출할 수 있다
11번째 줄: return-void는 아무것도 반환하지 않고 return(종료) 한다는 의미이다 12번째 줄: 메소드 정의 종료
smali 코드 2
1번째 줄: 2개의 String 자료형을 매개 변수로 받는 메소드 a 정의 2번째 줄: 해당 메소드는 8개의 레지스터를 사용한다 4번째 줄: 레지스터 v4에 4비트 상수 1을 저장 ( const/4는 4bit를 의미 ) 6번째 줄: 레지스터 v3에 4비트 상수 -3을 저장 8번째 줄: 레지스터 v0에 객체 할당 10번째 줄: v0과 p0을 매개변수로 받는 생성자 호출 ( 여기서 p0은 this와 같은 의미 ) 12번째 줄: v0을 매개변수로 받는 가상 메소드 b를 호출한다 해당 메소드의 반환 타입은 Landroid/support/v7/app/b 16번째 줄: v0과 p1을 매개변수로 받는, setTitle 매소드를 호출한다 여기서 p1은 Ljava/lang/CharSequence를 의미한다 또한 반환 타입은 아무것도 반환하지 않는 V(void)이다 18번째 줄: v0과 p2를 매개변수로 받는 a 메소드를 호출한다 여기서 p2는 Ljava/lang/Charsequence를 의미한다 이것 또한 반환 타입은 V이다 20번째 줄: 생략 22번째 줄: 생략 ( 여기서 Z는 Boolean 자료형을 의미한다 )
smali 코드 3
1번째 줄: v1 레지스터에 문자열 "EXIT" 할당 3번째 줄: MainActivity의 내부 클래스 객체 생성 5번째 줄: v2와 p0(MainActivity)를 매개변수로 받는 생성자 호출 8번째 줄: android/support/v7/app/b에 속한 a 메소드 호출, CharSequence와 OnClickListener를 매개변수로 받으며, 반환하는게 없는 V 11번째 줄: v0을 매개변수로 받는 가상메소드 show() 호출 반환 타입 V 13번째 줄: v0과 v3를 매개변수로 받는 android/support/v7/app/b에 속한 a 메소드 호출, I(Integer)를 매개변수로 으며, Landroid/widget/button을 반환한다 15번째 줄: 이전에 호출된 반환값을 v0 레지스터에 저장한다 --------- 생략 ----------- 33번째 줄: return-void는 아무것도 반환하지 않는다는 뜻
smali 코드 4 ( 이번건 길기 때문에, 중복된 내용은 과감히 건너뛴다 )
4번째 줄: invoke-super는 부모 클래스의 메소드를 호출하는 명령어로서, 여기서는 onCreate을 호출한다 6번째 줄: 레지스터 v0에 상수 0x7f09001c를 할당하고 있다 이것은 리소스 ID를 의미한다 12번째 줄: invoke-static은 정적 메소드를 호출하는 명령어로서, getProperty를 호출하고 있다 22번째 줄: if-eqz는 equal to zero의 약자로, 0일 때 실행하는 조건문이다 여기서는 v0이 0일 때, cond_25 레이블로 이동하라는 의미이다
28번째 줄: if-nez는 not equal to zero의 약자로, 0이 아닐때 실행하는 조건문이다 여기서는 v2가 0이 아닐 때, con_25 레이블로 이동하라는 의미이다 38 ~ 46번째 줄: cond_25 레이블을 의미한다
마무리
오늘 알아본 소스코드의 smali 코드는 하나씩, 천천히 알아보면서 읽어나가다 보면 어렵지 않다 여담으로, 나는 다른 툴도 써보았지만 리버싱할 때, jadx-gui와 apktool-GUI를 자주 사용한다
jadx-GUI의 경우, 업데이트도 꾸준히 하고 있고 무엇보다 smali 코드와 소스 코드를 같이 볼 수 있다는 점, 그리고 검색 기능이 가장 강력하다고 생각한다
apktool-GUI의 경우, CLI 기반인 apktool을 사용하다 불편하여, 찾아보다가 apktool-GUI를 택하였다 이것 또한 업데이트가 꾸준한데, 무려 3주전에 업데이트가 됐을 정도로 따끈따끈하다 또한, 디컴파일한 apk파일의 코드를 수정하여 다시 컴파일 할 때, 서명 과정도 알아서 해준다 기존에는, uber-apk-signer를 통해 서명을 해줘야 했지만, apktool-GUI는 그러한 번거로움을 없애준다