앱 정보
- 하드 코딩된 정보를 찾아 입력하면 해결할 수 있는 문제
기본 화면은 다음 [그림 1]과 같이 vendor key를 입력할 수 있는 부분과 ACCESS 버튼으로 구성되어있다.
아무 값이나 입력한 후 ACCESS를 눌러보면 다음과 같은 메시지를 확인할 수 있다.
앱 분석
Hardcode2Activity
private DivaJni djni;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView((int) R.layout.activity_hardcode2);
this.djni = new DivaJni();
}
public void access(View view) {
if (this.djni.access(((EditText) findViewById(R.id.hc2Key)).getText().toString())!= 0) {
Toast.makeText(this, "Access granted! See you on the other side :)", 0).show();
} else {
Toast.makeText(this, "Access denied! See you in hell :D", 0).show();
}
}
access 함수를 분석하면 다음과 같은 정보를 알 수 있다.
- onCreate()에서 생성한 DivaJni 클래스의 access() 함수에 인자로 사용자의 입력값을 넘겨준 후 반환 값에 따라 다음과 같은 동작을 한다.
- 0이라면 "Access granted! See you on the other side :)" 문자열이 출력된다.
- 0이 아니라면 "Access denied! See you in hell :D" 문자열이 출력된다.
DivaJni
public class DivaJni {
private static final String soName = "divajni";
public native int access(String str);
public native int initiateLaunchSequence(String str);
static {
System.loadLibrary(soName);
}
}
이전 Hardcode2Activity의 access() 함수에서 호출한 access() 함수가 속한 DivaJni클래스를 확인해보면 코드와 같이 "divajni"라는 라이브러리를 로드하는 것을 확인할 수 있는데 이를 통해 access() 함수는 네이티브 코드로 작성되었 다는 것을 알 수 있다.
Exploit
라이브러리의 하드코딩을 이용한 방법
DivaJni 클래스에서 가져오고 있는 라이브러리를 확인하기 위해 먼저 해당 패키지의 pid를 확인한다.
- ps -A | grep "패키지 이름"
pid를 확인하였다면 /proc/[pid]/maps를 사용하여 프로세스의 메모리를 확인한다. 이때 그냥 사용하면 출력되는 라이브러리나 다양한 정보가 많기 때문에 이전 코드에서 확인한 "divajni"를 사용하여 관련된 라이브러리만 출력하도록 한다.
- cat /proc/[pid]/maps | grep divajni
확인해보면 "/lib/arm64/libdivajni.so" 라이브러리를 사용하는 것을 알 수 있으므로 diva.apk를 압축 해제하여 해당 경로에 있는 라이브러리를 찾는다.
이후 찾은 라이브러리를 ida와 같은 disassassembly를 사용하여 열어준다.
함수 목록을 확인해보면 "DivaJni_access"를 확인할 수 있는데 해당 함수를 눌러보면 다음과 같이 strncmp() 함수를 통해 비교하는 문자열이 하드 코딩되어 있는 것을 확인할 수 있다.
이후 발견한 하드코딩 문자열을 입력 후 ACCESS 버튼을 눌러주면 "Access granted! See you on the other side :)"라는 문자열이 출력되는 것을 확인할 수 있다.
후킹을 이용한 우회
Exploit Code
Interceptor.attach(
Module.findExportByName("libdivajni.so", "strncmp"), {
onEnter: function(args) {
if(Memory.readUtf8String(args[1]) == "A"){
console.log("arg0 : " + Memory.readUtf8String(args[0]));
console.log("arg1 : " + Memory.readUtf8String(args[1]));
console.log("arg2 : " + args[2]);
}
},
onLeave: function(retl) {
retl.replace(0);
}
}
);
- Module.findExportByName(moduleName|null, exportName) : moduleName에서 exportNamed의 절대 주소 값을 반환한다.
- onEnter : 함수가 실행될 때 실행할 코드를 지정한다(인자는 해당 함수가 호출될 때 넘어오는 인자를 의미한다).
- onLeave : 함수가 종료될 때 실행항 코드를 지정한다(인자는 return 값을 의미한다).
코드는 libdivajni.so 라이브러리에서 사용 중인 strncmp() 함수를 후킹 한 후 입력 값이 A라면 모든 인자를 출력한다. 그리고 함수가 종료될 때 0을 반환한다.
실제 스크립트를 실행해보면 다음과 같이 에러가 발생하는 것을 확인할 수 있는데 이때 실행된 애플리케이션에서 level 12로 이동 후 아무 데이터를 넣어주고 ACCESS를 눌러준다.
이후 스크립트 코드에서 엔터 한 줄을 추가한 후 저장해주면 정상적으로 후킹이 되는 것을 확인할 수 있다. 마지막으로 A를 입력하고 ACCESS를 눌러주면 인자 값이 출력되고 0을 반환하여 "Access granted! See you on the other side :)"라는 문자열이 출력되는 것을 확인할 수 있다.
Reference
'Android > Diva' 카테고리의 다른 글
Access Control Issues(Part 3) - Level 11 (0) | 2021.11.01 |
---|---|
Insecure Logging - Level 1 (0) | 2021.11.01 |