
Android와 모바일 해킹, 앱 버그바운티까지 공부해야겠다는 결심이 들었다.
그래서 frida 기본적 명령어와 옵션 정리하며 frida 입문으로 남들 다 풀어본 fridalab 풀어보고 정리하며 공부하려한다.
Frida 명령어
/data/local/tmp 에 frida server 설치 후 진행한다.
기본적인 명령어
| frida-ls-devices | 연결된 device 들의 리스트 출력 |
| frida-ps -D emulator-5554 | (-U : usb로 연결된 디바이스 / -D emulator-5554 : 가상 디바이스) 기기 연결 |
| frida -D emulator-5554 -F [packagename] | 앱에 attach, 작동 안되면 프리다 서버 확인. ATTACH 관련 표 참고 |
| -l [filename] | 후킹 할때 js파일 함께 (-D -F 와 같이씀) |
frida 연결관련 옵션
| -F | 현재 foreground에서 실행되고 있는 앱을 연결 | frida -D emulator-5554 -F FridaLab |
| -n <PACKAGE NAME> | 앱의 패키지 이름으로 앱을 연결 | |
| -p <PID> | 앱의 프로세스 ID로 앱을 연결 | |
| -f <PACKAGE NAME> | *앱을 실행후, 연결* |
Frida Hooking 관련 함수
fridalab 을 풀면서 사용할 후킹 관련 함수 모음이다.
| Java.perform(function() {}); | 현재 스레드가 디바이스에 연결 되어있는지 확인 후 함수 호출 |
| Java.use(className) | 변수와 메서드에 엑세스 할 수 있는 클래스 객체를 반환 |
| Java.choose(className, callbacks) | Java.use와 혼동x , 인스턴스화 된 객체를 반환 (인스턴스 메서드 ↔ 스태틱 메서드 차이) |
| Java.enumerateLoadedClasses(className, callbacks) | 로드된 모든 클래스를 열거하고 모든 일치 항목을 출력 |
| .implementation | 정의된 메소드의 구현 내용을 재작성 |
| overload() | implement 와 함께쓰기, 자바 오버로딩 |
| Java.setImmediate(fn) | 단말기가 느려질 때 자동으로 프로세스를 종료하는 특성이 있다.이러한 경우를 방지하기 위해서 setImmediate()를 사용한다. 백그라운드로 자동으로 스크립트가 재실행되어 종료되지 않는다. |
https://frida.re/docs/javascript-api/
JavaScript API
Observe and reprogram running programs on Windows, macOS, GNU/Linux, iOS, watchOS, tvOS, Android, FreeBSD, and QNX
frida.re
FridaLab writeup
Challenge 1
package uk.rossmarks.fridalab;
public class challenge_01 {
static int chall01;
public static int getChall01Int() {
return chall01;
}
}
Java.perform(()=>{
var ch1 = Java.use("uk.rossmarks.fridalab.challenge_01");
ch1.chall01.value= 1
});
MainActivity 를 확인하면 chall01 의 값을 1로 만들어야 한다고 분석할 수 있다.
java는 변수를 자동 0으로 초기화해서 Java.use로 chall01 static 변수에 접근해서 값을 강제로 수정한다.
Challenge 2
private void chall02() {
this.completeArr[1] = 1;
}
Java.perform(()=>{
var ch2 = Java.choose("uk.rossmarks.fridalab.MainActivity",{
"onMatch": function(instance){
instance.chall02();
},
"onComplete": function(){}
});
});
chall02 를 실행시키기만 하면된다.
instance method 이기 떄문에, Java.choose 사용한다. onMatch. onComplete 반환한다.
Challenge 3
public boolean chall03() {
return false;
}
Java.perform(()=>{
var ch3 = Java.use("uk.rossmarks.fridalab.MainActivity");
ch3.chall03.implementation = function() {
return true;
}
});
메서드를 직접 호출할 필요 없이 반환 값만 변경해도 되기 때문에 static 메서드가 아니여도 Java.use()를 사용하여 후킹이 가능하다. 만약 메서드를 직접 호출하려면 choose 사용해야 한다.
Challenge 4
public void chall04(String str) {
if (str.equals("frida")) {
this.completeArr[3] = 1;
}
}
Java.perform(()=>{
var ch4 = Java.choose("uk.rossmarks.fridalab.MainActivity",{
"onMatch": function(instance){
instance.chall04("frida");
},
"onComplete": function(){}
});
});
challenge2 와 비슷하지만 frida 문자열을 전달해야 한다.
Challenge 5
public void chall05(String str) {
if (str.equals("frida")) {
this.completeArr[4] = 1;
} else {
this.completeArr[4] = 0;
}
}
Java.perform(()=>{
var ch5 = Java.use("uk.rossmarks.fridalab.MainActivity");
ch5.chall05.implementation = function(a) {
this.chall05("frida");
}
});
challenge4 와 같이 frida 문자열을 넘겨주는 것이 목표이나, MainActivity 에서 notfrida! 를 인자로 호출해버린다.
따라서 Java.use 사용하여 chall05를 오버로딩하여 frida 인자를 넘겨주도록 한다.
Challenge 6
public void chall06(int i) {
if (challenge_06.confirmChall06(i)) {
this.completeArr[5] = 1;
}
}
public class challenge_06 {
static int chall06;
static long timeStart;
public static void startTime() {
timeStart = System.currentTimeMillis();
}
public static boolean confirmChall06(int i) {
return i == chall06 && System.currentTimeMillis() > timeStart + 10000;
}
public static void addChall06(int i) {
chall06 += i;
if (chall06 > 9000) {
chall06 = i;
}
}
}
Java.perform(()=>{
var ch6 = Java.use("uk.rossmarks.fridalab.challenge_06");
ch6.chall06.value= 1;
Java.choose("uk.rossmarks.fridalab.MainActivity",{
"onMatch": function(instance){
instance.chall06(1);
},
"onComplete": function(){}
});
});
문제의 의도는 10초 후에 i와 chall06의 값이 같아야 하지만, chall06 변수의 값을 1로 설정하고, i값으로 1을 넣어주어 바로 풀리도록 했다.
의도대로라면 js의 setTimeout 을 활용하여 10초 후에 실행 하도록 하면 될것 같다.
Challenge 7
public void chall07(String str) {
if (challenge_07.check07Pin(str)) {
this.completeArr[6] = 1;
} else {
this.completeArr[6] = 0;
}
}
public class challenge_07 {
static String chall07;
public static void setChall07() {
chall07 = BuildConfig.FLAVOR + (((int) (Math.random() * 9000.0d)) + 1000);
}
public static boolean check07Pin(String str) {
return str.equals(chall07);
}
}
Java.perform(()=>{
var ch7 = Java.use("uk.rossmarks.fridalab.challenge_07");
ch7.chall07.value= "frida";
Java.choose("uk.rossmarks.fridalab.MainActivity",{
"onMatch": function(instance){
instance.chall07("frida");
},
"onComplete": function(){}
});
});
랜덤하게 설정되는 chall07 값을 맞춰 주어야 하는 문제이다.
brute force attack 으로 풀 수도 있지만, 나는 challenge6 과 같은방법으로 풀었다.
Challenge 8
public boolean chall08() {
return ((String) ((Button) findViewById(R.id.check)).getText()).equals("Confirm");
}
Java.perform(()=>{
var ch8 = Java.use("uk.rossmarks.fridalab.MainActivity");
ch8.chall08.implementation = function() {
return true;
}
});
문제의 의도는 아니나, 풀리기는 한다.
Java.perform(()=>{
var button = Java.use("android.widget.Button");
Java.choose("uk.rossmarks.fridalab.MainActivity",{
"onMatch": function(instance){
var check = instance.findViewById(0x7f07002f)
var confirm = Java.cast(check, button);
confirm.setText(Java.use("java.lang.String").$new("Confirm"));
},
"onComplete": function(){}
});
});
버튼의 check 문자열을 confirm 으로 바꾸는 것의 원래의 의도이고, 그에 맞는 풀이이다.

우선 똑같이 mainactivity 의 인스턴스를 매칭하여 findViewById 로 버튼을 지정, Confirm 이라는 string 객체를 생성해서 버튼의 문자열을 변경한다.
스프링개발과 앱개발 경험이 있기때문에 이해하는데에 큰 어려움은 없었으나, 실제 환경에서 버그바운티에 어떻게 활용하고 우회할지는 아직 감이 오지 않는다.
계속 공부해 나가야 할 것 같다.
'Mobile' 카테고리의 다른 글
| AVD BurpSuite CA 시스템 인증서 설치 및 remount 오류해결 (1) | 2025.10.10 |
|---|---|
| [Android] Glide 이미지 재로딩시 실패 오류 해결 (0) | 2023.10.11 |
| [Android / Kotlin] Recyclerview + data binding 데이터 값에 따른 이미지 출력 여부 결정 (0) | 2023.09.19 |