PWNABLE] ptrace Bypass

노션으로 옮김·2020년 5월 19일
1

skills

목록 보기
33/37
post-thumbnail

ptrace

ptrace는 프로세스를 추적하거나 실행되는 기능을 관리할 수 있는 함수이다.
manpage에 설명이 잘 나와있다.

http://man7.org/linux/man-pages/man2/ptrace.2.html

The ptrace() system call provides a means by which one process (the"tracer") may observe and control the execution of another process(the "tracee"), and examine and change the tracee's memory and registers. It is primarily used to implement breakpoint debugging and system call tracing.

마지막 줄의 설명처럼 ptrace는 디버거에서 사용하는 함수이다.
따라서, 반대로 해당 함수를 이용해 안티 디버깅 기능을 구현할 수도 있다.

Example

defcamp의 r200 문제 바이너리를 예제로 확인해보면,
이 바이너리는 쉘에서 직접 실행할 경우 정상적으로 프로세스가 처리되지만

root@ubuntu:/work/tmp# ./r200
Enter the password: 123
Incorrect password!
root@ubuntu:/work/tmp#

디버거와 같은 원리로 실행되는 strace 명령어로 바이너리를 실행해보면 다음처럼 ptrace에서 멈추는 것을 확인할 수 있다.

root@ubuntu:/work/tmp# strace ./r200
...
...
...
mprotect(0x7fabc2ff6000, 4096, PROT_READ) = 0
munmap(0x7fabc2fe2000, 79222)           = 0
ptrace(PTRACE_TRACEME)                  = -1 EPERM (Operation not permitted)

IDA로 해당 코드 부분을 확인해보면 .init에서 sub_40084a를 호출하고 있다.

sub_40084a

__int64 sub_40084A()
{
  __int64 result; // rax@3

  if ( getenv("LD_PRELOAD") )
  {
    while ( 1 )
      ;
  }
  result = ptrace(0, 0LL, 0LL, 0LL);
  if ( result < 0 )
  {
    while ( 1 )
      ;
  }
  return result;
}

이 함수는 먼저 LP_PRELOAD 환경변수를 검사하며 로드되는 라이브러리가 변경됐는지 확인한다.
그 후 ptrace 를 호출하는데 이것의 정의를 보면

ptrace

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

첫 번째 인자로 action을 지정한다. 두 번째 인자가 그 대상 PID다.
r200에서 첫 번째 인자로 0을 전달하고 있는데 이것은 PTRACE_TRACEME를 나타내며
이것은 자기 자신을 추적 가능한 상태로 만드는 것이다.

PTRACE_TRACEME

Indicate that this process is to be traced by its parent. A process probably shouldn't make this request if its parent isn't expecting to trace it. (pid, addr, and data are ignored.)

따라서 두 번째 인자인 PID도 자기 자신을 가리키는 0을 전달하는 것이며, 이렇게 ptrace를 실행할 경우 성공시 0을 반환한다. 하지만, 디버거에 의해 실행된 프로세스라면 이미 추적되고 있는 상태이므로 에러가 발생하여 -1을 반환할 것이다.

즉, strace 명령어로 실행할 때 ptrace 함수 호출 후 멈춘 것은 result가 -1이 되어 while 무한 루프를 실행하기 때문이다.

Bypass

위에서 본 안티디버깅을 우회하는 것은 후킹으로도 가능하지만 이번에 정리할 것은 gdb를 이용한 방법이다.

gdb로 바이너리를 로드하고 ptrace를 캐치하도록 만든다.

gdb$ catch syscall ptrace
Catchpoint 1 (syscall 'ptrace' [101])

해당 옵션에 대한 자세한 설명은 다음을 참고.

https://sourceware.org/gdb/onlinedocs/gdb/Set-Catchpoints.html

syscall [name | number | group:groupname | g:groupname] …

A call to or return from a system call, a.k.a. syscall. A syscall is a mechanism for application programs to request a service from the operating system (OS) or one of the OS system services. GDB can catch some or all of the syscalls issued by the debuggee, and show the related information for each syscall. If no argument is specified, calls to and returns from all system calls will be caught.

그리고 해당 캐치포인트에 접근하여 시스템콜 호출, 반환시 $rax의 값이 0으로 설정되게 만든다.

gdb$ commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>set ($rax) = 0
>continue 
>end

그 후 프로그램을 실행시키면 정상적으로 프로세스가 처리되는 것을 확인할 수 있다.

0개의 댓글