iOS 앱 점검(ObjC)/안티디버깅

ios) ptrace관련 안티디버깅 정리

nightohl 2020. 7. 27. 15:29
반응형

ios) ptrace관련 안티디버깅 정리

https://alexomara.com/blog/defeating-anti-debug-techniques-macos-ptrace-variants/

 

Defeating Anti-Debug Techniques: macOS ptrace variants | Alexander O'Mara

Every reverse engineer who handles software for macOS knows about ptrace(PT_DENY_ATTACH, 0, 0, 0), the infamous kernel-enforced anti-tracing DRM feature added to OS X years back (somewhere around Leopard) and most-notably used in iTunes. There are plenty o

alexomara.com

[ios 관련 팁]

ptrace로 PT_DENY_ATTACH로 디버깅을 못하게하는 건 가장 기본적인 안티디버깅 방법.

근데 ptrace는 public iOS API가 아니므로 직접적으로 쓰면 앱스토어 검수에서 불통.

따라서 아래 코드처럼 dlsym으로 ptrace 함수의 주소를 알아와서 사용하는 방식으로 사용함.

#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); // PTRACE_DENY_ATTACH = 31
}
추가 팁 : 종료된 메세지가 exited with status 45(0x2d)라면 ptrace로 인해서 종료됐다는 뜻.

사례1

가장 기본적인 사례.

// clang -o main main.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
	ptrace(PT_DENY_ATTACH, 0, 0, 0);
	printf("SUCCESS\n");
	return 0;
}

1. bp ptrace로 ptrace부분에 중단점 설정하고.

2. ptrace 함수 호출 시의 인자인 PT_DENY_ATTACH는 31(0x1F)인데, 이 값을 0으로 바꿔서 호출시키면 우회 끝.


사례2

exited with status 45인데도 불구하고 bp ptrace가 안먹히는 경우.

이때는 시스템 함수를 직접 호출한 경우임.

// clang -o main main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
	asm(
		"pushq %rax\n"
		"pushq %rdi\n"
		"movq $0x1f, %rdi\n"
		"movq $0x200001A, %rax\n"
		"syscall\n"
		"popq %rdi\n"
		"popq %rax\n"
	);
	printf("SUCCESS\n");
	return 0;
}
syscall(0x200001A)

따라서 syscall 호출 순간을 잡아서 0x200001A인 인자를 0으로 바꾸고 syscall 호출하도록 변경하면 우회 끝.


사례3

잘 알려지지 않은 사례

1. ptrace(PT_DENY_ATTACH, 0, 0, 0)이 잘 동작하는 지

2. 이와 관련된 코드가 변경됐는지를 검사

ptrace(PT_DENY_ATTACH, 0, 0, 0 )가 적용된 프로세스에 attach하면 segmentation fault가 발생한다는 점을 이용한 방법.

// clang -o main main.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <unistd.h>

int deny_attach_successful = 0;

void sigsegv_handler(int sig) {
	printf("sigsegv_handler: %i\n", sig);
	deny_attach_successful = 1;
}

int main(int argc, char *argv[]) {
	pid_t pid = getpid();
	ptrace(PT_DENY_ATTACH, 0, 0, 0);
	signal(SIGSEGV, sigsegv_handler);
	ptrace(PT_ATTACH, pid, 0, 0);

	if (!deny_attach_successful) {
		printf("FAILURE\n");
		return 1;
	}

	printf("SUCCESS\n");
	return 0;
}
1. 전역변수는 default 값으로 0(false)으로 설정한 상태.
2. 첫번째 ptrace 호출에서 PT_DENY_ATTACH를 등록하였다.
그러면 이후의 attach 시도 시 segmentation fault가 뜨는 게 정상이다.
3. 따라서 seg fault가 뜨면 전역변수를 1(true)로 설정하는 커스텀 시그널 핸들러를 별도로 등록하고,
4. 자기 자신에게 ptrace attach를 시도한다.

 

==> 스스로에게 attach 시도한 것이 첫번째 ptrace에 의해 정상적으로 segmentation fault를 유발했다면, 전역변수를 1로 설정하는 커스텀 핸들러가 동작했을테고, 이후의 if문을 정상적으로 통과할 수 있다.

 

==> 공격자가 ptrace에 bp걸고 PT_DENY_ATTACH 인자를 변경하여 디버깅이 가능하도록 한 경우라면, 두번째 ptrace attach가 제대로 동작하면서 전역변수가 그대로 0(false)로 유지되므로 if문을 통과하지 못하고 공격자의 디버깅 상태를 감지할 수 있게된다.

 

[우회 방법]

1. 첫번째 ptrace 우회

    공격자가 디버거로 attach를 할 수 있어야하니까 PT_DENY_ATTACH를 먼저 우회함.

2. signal에 중단점 설정하고 핸들러 주소를 알아둠.

3. 두번째 ptrace 우회

4. 수동으로 signal 핸들러를 호출한다.

    if문 검사 전에 전역변수 값을 바꾸려는 것인데, 여러 순간에 검사하는 게 아니라면 그냥 if문을 우회하는 것으로도 충분.

    물론 전역변수 메모리 값을 직접 바꿔도 됨. (핸들러 함수 분석 결과 전역변수 값 하나만 바꾼다는 확신이 있다면 ㄱㄱ)

    우회는 알아서...

반응형

'iOS 앱 점검(ObjC) > 안티디버깅' 카테고리의 다른 글

ios) 안티디버깅 - getppid()  (0) 2020.07.27
ios) 안티디버깅 - sysctl  (0) 2020.07.27