작은 메모장
보안 기술 우회 본문
보안 솔루션 이해 (Android)
모바일 보안 솔루션은 어플리케이션 분석을 어렵게 하여 공격자로부터 어플리케이션을 보호하려는 목적으로 제작된 방어 대책이다.
해커의 어플리케이션의 분석을 어렵게 만들거나, 악성 행위를 주입한 어플리케이션의 배포를 막는 등의 목적으로 주로 쓰인다.
보안 솔루션은 크게 4가지 종류가 있다.
- Rooting 탐지 : 루팅된 기기에서 어플리케이션이 실행되는 것을 인지 시, 즉시 어플리케이션이 종료되는 솔루션.
위험한 환경에서 실행되지 않도록 도와준다. 또한, 루팅된 기기에서 분석하려는 시도 자체를 방해하는 목적으로 쓰인다. - 안티 디버깅 : 디버거를 사용하여 어플리케이션 내부 동작을 분석 혹은 수정하려는 시도를 방지하는 솔루션.
주로 디버거 설치 여부를 검사하거나 디버깅 관련 API 호출을 막고, 코드 실행 시간, 환경 변수를 검사하는 식으로 방어한다. - App 무결성 검증 : 어플리케이션이 원래의 상태에서 변경되었는지 여부를 확인하는 솔루션.
주로 어플리케이션 자체 파일이나 중요 파일의 해시값을 사전에 저장해두고, 실행 시점에 이를 검사하는 식으로 방어하거나, 런타임 시점에서 메모리 상의 코드와 저장된 코드가 일치하는 지 확인하는 방식으로 방어한다. - 난독화 : 어플리케이션의 코드를 읽기 어렵게 만드는 솔루션.
클래스, 메서드, 변수 등의 이름을 난잡한 이름으로 바꾸거나, 의미없는 프로그램의 흐름을 추가하여 실행 흐름을 복잡하게 만들고, 문자열 암호화 및 라이브러리 난독화 등으로 방어한다.
보안 솔루션 ByPass (Android)
Rooting 탐지
루팅 탐지의 대표적인 방법은 루팅 패키지를 확인하는 방법이다.
안드로이드 환경에서 루팅을 했을 시, 주로 사용되는 루팅 패키지를 탐지하는 것이다.
public static final String[] knownRootAppsPackages = {
"com.noshufou.android.su",
"com.noshufou.android.su.elite",
"eu.chainfire.supersu",
"com.koushikdutta.superuser",
"com.thirdparty.superuser",
"com.yellowes.su",
"com.topjohnwu.magisk",
"com.kingroot.kinguser",
"com.kingo.root",
"com.smedialink.oneclinkroot",
"com.zhiqupk.root.global",
"com.alephzain.framaroot"
};
대표적으로 su, supersu, magisk, kinguser 등등, 딱 봐도 루팅된 기기에서 사용할 것 같은 앱이 있는지 확인하는 것이다.
해당 리스트를 만들어두고, 반복문을 통해 하나하나씩 검사하는 것이다.
또 다른 방법은 파일을 체크하는 방법이다.
안드로이드 환경에서 루팅을 하면, 그 루팅파일(su)가 있는지를 확인하는 방법이다.
public static final String BINARY_SU = "su";
public static final String BINARY_BUSYBOX = "busybox";
public static final String[] suPaths = {
"/data/local/",
"/data/local/bin/",
"/data/local/xbin/",
"/sbin/",
"/su/bin/",
"/system/bin/",
"/system/bin/.ext/",
"/system/bin/failsafe/",
"/system/sd/xbin/",
"/system/usr/we-need-root/",
"/system/xbin/",
"/cache/",
"/data/",
"/dev/"
};
타겟 파일과 루팅 파일이 있을 법한 경로를 전부 저장해놓고, 이를 조합하여 확인하는 방식이다.
해당 리스트를 만들어두고, 반복문을 통해 하나하나씩 검사한다.
build.prop 파일을 확인하여 루팅을 감지하는 방식도 있다.
루팅을 하게 될 경우, build.prop 파일의 내용이 변하게 되며 이를 확인하여 루팅을 확인한다.
...
public boolean detectTestKeys() {
String buildTags = android.os.Build.TAGS;
return buildTags != null && buildTags.contains("test-keys");
}
...
build.prop 파일에서 태그 값을 확인하면 그 차이를 확인할 수 있다.
루팅을 하지 않은 순정상태에서는 "release-keys"라는 값을 가지는데, 루팅시 이 값이 "test-keys"로 바뀌는 경우가 있어 이를 확인하는 것이다.
시스템 디렉토리의 권한이 변경되었는지를 확인하는 방법도 있다.
루팅 시 시스템 디렉토리의 권한을 변경할 수도 있는데, 이를 확인하는 것이다.
통상적으로 시스템 디렉토리는 읽기 권한만 존재하는데, 쓰기 권한까지 부여되어 있는가를 확인하는 방식.
해당 솔루션을 우회하는 방법은 크게 2가지 방향으로 나뉜다.
코드 패치 방식은 바이너리, smali 코드를 변조 한 후, 리패키징을 통해 새로운 앱으로 만드는 방법. 즉, 코드를 수정하는 방법이다.
리패키징만 성공한다면 그 어플리케이션을 계속 사용하면 되므로 상당히 편하지만, 리패키징을 방어하는 솔루션이 적용되어 있는 경우 오히려 더 난이도가 높아지는 단점이 있다.
다른 방법은 함수 후킹 방식으로, 탐지 로직 함수를 후킹하여 탐지 로직을 우회하는 방법. 즉, 인자값이나 리턴값을 변조하는 방법이다.
앱을 실행할 때마다 Frida를 이용하여 함수를 후킹을 하는 과정이 상당히 귀찮고 번거로우나, 방해되는 솔루션 혹은 탐지 로직을 대부분 우회 가능하다는 점이 장점이다.
무결성 검증
무결성을 검증하는 방식은 다양하지만, 전반적인 기술로 봤을 때 단 하나로 통일된다.
파일의 Hash 값을 비교하여 어플리케이션 변조 여부를 판단하는 방식이다.
더욱 자세히는, DEX 파일을 암호화 후 저장해둔 뒤, 어플리케이션을 실행 시 해당 파일을 복호화 해 로딩하는 방식으로 사용한다.
무결성 검증은 검증 위치에 따라 구분된다.
서버에 해시값을 제출하고 그 결과를 받아오거나, 서버에서 원본 해시값을 받아와 이를 비교하는 방식 등 서버를 끼고 검증하는 방식이 있다.
다른 하나는, 클라이언트 내에 원본 해시값이 존재하여 이를 실행 시 비교하는 형태로 검증하는 방식이 있다.
각각의 방식을 우회하기 위해서는 각 방식에 맞게 처리를 해야한다.
서버를 끼고 검증을 하는 무결성 검증의 경우, 통신 데이터를 변조하는 방향으로 우회한다. 가령, 앱 서버에 전달되는 Hash를 변조하거나, 앱 서버에서 온 결과를 변조하는 방법이 있다.
클라이언트 자체적으로 검증을 하는 경우, 함수 후킹을 통해 검증 로직을 무력화하는 방향으로 우회한다. 만약 어플리케이션이 고정된 로딩 방식이 아닌 동적으로 로딩을 한다면, 변조된 DEX 파일로 교체, 이를 로딩하게 만들도록 함수를 후킹한다.
안티 디버깅
안티 디버깅은 디버거가 보호하려는 프로그램에 붙어 실행되지 않도록 방어하는 솔루션으로, 여러 방법이 존재한다.
안티 디버깅의 대표적인 방식은 시간 기반 탐지다.
두 명령어(어셈블리어) 사이에 실행 시간이 일정 시간보다 크면 디버깅 중인 것으로 판단, 실행을 종료하는 방식이다.
extern "C"
JNIEXPORT jstring
JNICALL Java_com_example_test_MainActivity_method(JNIEnv *env, jobject) {
long start, end;
std::string hello = "Hello from time";
start = clock();
...
end = clock();
if(end-start > 10000){
hello = "Debug from time";
}
return env->NewStringUTF(hello.c_str());
}
다른 안티 디버깅 방식은 파일 기반 탐지다.
첫 번째 방식은 /proc/pid/status, /proc/pid/task/status 파일을 살펴 TracerPid 값을 살펴보는 것이다.
TracerPid값은 기본적으로 0이지만, 디버깅 중이라면 디버깅 프로세스의 PID 값이 된다.
이를 확인하여 디버깅 여부를 탐지하는 것이다.
두 번째 방식은 /proc/pid/stat, /proc/pid/task/pid/stat 파일 내용을 살펴보는 것이다.
해당 내용 중, 패키지 번호와 이름이 써져있는 내용 바로 뒤 t라는 값이 붙었다면, 디버깅중이라는 의미다.
이 값을 확인하여 디버깅 여부를 탐지하는 것이다.
VM 필드 탐지를 이용하여 안티 디버깅을 하는 방식도 있다.
Android Java VM 구조체에 있는 디버깅 상태 정보를 확인하는 방식이다.
이를 알아내기 위해, 보통 android.os.Debug 클래스의 isDebuggerConnected() 메서드를 이용한다.
if(android.os.Debug.isDebuggerConnected()){
...
}
ptrace를 이용하여 안티 디버깅을 하는 방법도 존재한다.
일종의 발상의 전환으로, 디버깅을 막기 보단 디버깅을 먼저 해버리는 방식이다.
ptrace 함수를 이용하여 대상 프로세스 디버깅을 선점하는 방법이다.
void anti_debug() {
child_pid = fork();
if (child_pid == 0) {
int ppid = getppid();
int status;
if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0) {
waitpid(ppid, &status, 0);
ptrace(PTRACE_CONT, ppid, NULL, NULL);
while (waitpid(ppid, &status, 0)) {
if (WIFSTOPPED(status)) {
ptrace(PTRACE_CONT, ppid, NULL, NULL);
} else {
_exit(0);
}
}
}
}
}
BP를 스캔하는 방식도 사용한다.
디버깅을 할 때에는 BreakPoint를 사용하는 경우가 대부분이다.
이에 현재 코드 영역에 BreakPoint가 설치되었는지는 스캔 후 탐지하는 방법이 있다.
Mode | 명령어 |
ARM | 0x01, 0x00, 0x9f, 0xef |
Thumb16 | 0x01, 0xde |
Thumb32 | 0xf0, 0xf7, 0x00, 0xa0 |
이러한 안티 디버깅을 우회하기 위해서는, 무결성 검증과 비슷한 방법을 쓰면 된다.
코드 패치를 하여 바이너리, smali 코드 변조 후 리패키징하여 새로운 앱을 설치하는 방법, 즉 코드를 수정하는 방법과
안티 디버깅 함수를 후킹하여 탐지 로직을 우회, 인자값과 리턴값을 변조하여 솔루션을 우회하는 방법이 있다.
보안 솔루션 이해 (iOS)
모바일 보안 솔루션은 어플리케이션 분석을 어렵게 하여 공격자로부터 어플리케이션을 보호하려는 목적으로 제작된 방어 대책이다.
iOS의 보안 솔루션은 의외로 Android보다 허술할 수 있는데, 이는 Apple의 폐쇄적인 정책으로 개발자들 또한 iOS에서 Apple이 허용한 기능만 이용이 가능하기 때문이다.
때문에, Android Mobile Solution보다 구현될 수 있는 것이 적다.
보안 솔루션은 크게 4가지 종류가 있다.
- Jailbreak 탐지 : 탈옥된 기기에서 어플리케이션이 실행되는 것을 인지 시, 즉시 어플리케이션이 종료되는 솔루션.
위험한 환경에서 실행되지 않도록 도와준다. 또한, 탈옥된 기기에서 분석하려는 시도 자체를 방해하는 목적으로 쓰인다. - 안티 디버깅 : 디버거를 사용하여 어플리케이션 내부 동작을 분석 혹은 수정하려는 시도를 방지하는 솔루션.
주로 디버거 연결 여부를 검사하거나 디버깅 관련 API 호출을 막고, 코드 실행 시간, 프로세스 상태를 검사하는 식으로 방어한다. - App 무결성 검증 : 어플리케이션이 원래의 상태에서 변경되었는지 여부를 확인하는 솔루션.
주로 서명을 검증하거나 해시 값 검사, 코드 무결성 검사, 탐지 기반 체크로 이를 구현한다. - 난독화 : 어플리케이션의 코드를 읽기 어렵게 만드는 솔루션.
클래스, 메서드, 변수 등의 이름을 난잡한 이름으로 바꾸거나, 의미없는 프로그램의 흐름을 추가하여 실행 흐름을 복잡하게 만들고, 문자열 암호화 및 라이브러리 난독화 등으로 방어한다.
보안 솔루션 ByPass (iOS)
Jailbreak 탐지
통상적으로 탈옥을 탐지하는 방법은 fopen을 쓰거나, fileManager의 fileExistsAtPath를 사용하여 루트 파일을 탐지하는 방법을 주로 사용한다.
탈옥 관련 파일이 존재하는지 하나하나 탐색하는 방법이다.
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:@"/Applications/Cydia.app"]){
return YES;
} else if ([fileManager fileExistsAtPath:@"/Library/MobileSubstrate/MobileSubstrate.dylib"]){
return YES;
} else if ([fileManager fileExistsAtPath:@"/bin/bash"]){
return YES;
} else if ([fileManager fileExistsAtPath:@"/usr/sbin/sshd"]){
return YES;
} else if ([fileManager fileExistsAtPath:@"/etc/apt"]){
return YES;
} else if ([fileManager fileExistsAtPath:@"/usr/bin/ssh"]){
return YES;
}
Sandbox의 제한된 기능을 사용할 수 있는지 체크하는 방법도 쓸 수 있다.
탈옥 시 기기에서 App이 Sandbox로 제한된 기능들을 수행할 수 있는데, 보통 App 영역이 아닌 곳에 쓰기 권한이 생긴다.
따라서 해당 영역에 쓰기가 실행되면 탈옥을 탐지하는 방법으로 사용한다.
NSError *error = nil;
NSString *string = @".";
[string writeToFile:@"/private/jailbreak.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (!error) {
[fileManager removeItemAtPath:@"/private/jailbreak.txt" errornil];
return YES;
}
URL Scheme를 확인하여 탈옥을 확인하기도 한다.
탈옥 시 Cydia App이 설치되는데, 설치 과정에서 Cydia App이 자신의 URL Scheme를 등록한다.
등록된 Cydia App 경로를 인지하여 탈옥을 탐지하는 방법이다.
이는 canOpenURL 함수를 이용해 Cydia App을 열어보는 방식으로 구현한다.
if UIApplication.shared.canOpenURL((URL(string: "cydia://package/com.example.package"))!) {
return true
}
해당 솔루션을 우회하는 방법은 크게 2가지 방향으로 나뉜다.
코드 패치 방식은 바이너리, smali 코드를 변조 한 후, 리패키징을 통해 새로운 앱으로 만드는 방법. 즉, 코드를 수정하는 방법이다.
리패키징만 성공한다면 그 어플리케이션을 계속 사용하면 되므로 상당히 편하지만, 리패키징을 방어하는 솔루션이 적용되어 있는 경우 오히려 더 난이도가 높아지는 단점이 있다.
다른 방법은 함수 후킹 방식으로, 탐지 로직 함수를 후킹하여 탐지 로직을 우회하는 방법. 즉, 인자값이나 리턴값을 변조하는 방법이다.
앱을 실행할 때마다 Frida를 이용하여 함수를 후킹을 하는 과정이 상당히 귀찮고 번거로우나, 방해되는 솔루션 혹은 탐지 로직을 대부분 우회 가능하다는 점이 장점이다.
무결성 검증
무결성을 검증하는 방식은 다양하지만, 전반적인 기술로 봤을 때 단 하나로 통일된다.
파일의 Hash 값을 비교하여 어플리케이션 변조 여부를 판단하는 방식이다.
더욱 자세히는, DEX 파일을 암호화 후 저장해둔 뒤, 어플리케이션을 실행 시 해당 파일을 복호화 해 로딩하는 방식으로 사용한다.
무결성 검증은 검증 위치에 따라 구분된다.
서버에 해시값을 제출하고 그 결과를 받아오거나, 서버에서 원본 해시값을 받아와 이를 비교하는 방식 등 서버를 끼고 검증하는 방식이 있다.
다른 하나는, 클라이언트 내에 원본 해시값이 존재하여 이를 실행 시 비교하는 형태로 검증하는 방식이 있다.
각각의 방식을 우회하기 위해서는 각 방식에 맞게 처리를 해야한다.
서버를 끼고 검증을 하는 무결성 검증의 경우, 통신 데이터를 변조하는 방향으로 우회한다. 가령, 앱 서버에 전달되는 Hash를 변조하거나, 앱 서버에서 온 결과를 변조하는 방법이 있다.
클라이언트 자체적으로 검증을 하는 경우, 함수 후킹을 통해 검증 로직을 무력화하는 방향으로 우회한다. 만약 어플리케이션이 고정된 로딩 방식이 아닌 동적으로 로딩을 한다면, 변조된 DEX 파일로 교체, 이를 로딩하게 만들도록 함수를 후킹한다.
안티 디버깅
안티 디버깅은 디버거가 보호하려는 프로그램에 붙어 실행되지 않도록 방어하는 솔루션으로, 여러 방법이 존재한다.
ptrace에 특정 옵션을 부여하여 디버깅 자체를 막아버리는 방법이 대표적으로 사용된다.
이는 ptrace 함수에서 PTRACE_DENY_ATTACH 옵션을 주고 실행하여 디버깅 추적 자체를 막는 것이다.
이는 dlsym 함수를 통해 함수 주소를 구해와 실행하는 것으로 구현한다.
#import <dlfcn.h>
#import <sys/types.h>
#import <stdio.h>
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
void anti_debug() {
ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(RTLD_SELF, "ptrace");
ptrace_ptr(31, 0, 0, 0);
}
sysctl 함수를 이용해 탈옥을 인지하는 방법도 있다.
sysctl 함수를 이용하면 현재 앱 프로세스의 정보를 가져올 수 있는데, 디버깅 중을 나타내는 정보를 확인하는 방법이다.
static bool AmIBeingDebugged(void) {
int junk;
int mib[4];
struct kinfo_proc info;
size_t size;
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
assert(junk == 0);
return ((info.kp_proc.p_flag & P_TRACED) != 0);
}
이러한 안티 디버깅을 우회하기 위해서는, 무결성 검증과 비슷한 방법을 쓰면 된다.
코드 패치를 하여 바이너리, smali 코드 변조 후 리패키징하여 새로운 앱을 설치하는 방법, 즉 코드를 수정하는 방법과
안티 디버깅 함수를 후킹하여 탐지 로직을 우회, 인자값과 리턴값을 변조하여 솔루션을 우회하는 방법이 있다.
'KISA 사이버 보안 훈련 > 버그헌팅 실습 중급' 카테고리의 다른 글
Bug Bounty 사례 (0) | 2024.07.14 |
---|---|
Metasploit (0) | 2024.07.13 |
어플리케이션 보안 이해 (0) | 2024.07.11 |
향상된 타겟 정보 수집 (0) | 2024.07.01 |
버그 헌팅이란? (0) | 2024.07.01 |