4. PTRACE를 이용한 INJECTION
• 리눅스는 윈도우에서 DLL injection에 이용했던 CreateRemoteThread와 같은 API가 없음
• 대신 ptrace 함수 이용
• ptrace
• process trace의 약자
• 프로세스의 상태를 조사하고 조작할 수 있게 하여, 한 프로세스가 다른 프로세스를 제어할 수 있도록 하
는 시스템 콜
• 디버거, strace와 ltrace 등의 프로그램이 주로 이용
• 파일 디스크립터, 메모리, 레지스터 등을 조작할 수 있고, 코드를 싱글스텝하고 시스템 호출과 그 결과를
가로채며 대상의 시그널 핸들러와 시그널을 조작할 수 있다.
• ->강력한 능력이 있으므로, 오직 소유자가 시그널을 보낼 수 있는 프로세스들만 attach할 수 있음(일반
적으로 자신의 프로세스들)
• (최신 리눅스에는 ptrace protection이 적용되어 있음)
5. PTRACE를 이용한 INJECTION
• ptrace
• 인자
• request
• ptrace를 이용해 하고자 하는 작업
• pid
• 대상 프로세스 id
• addr
• request에 따라 무시되거나 이용 됨
• data
• request에 따라 무시되거나 이용 됨
6. PTRACE를 이용한 INJECTION – 실습
• 대상 프로그램
• 종료하기 전까지는 꺼지지 않는 프로그램을 제작
• 이 프로그램에 injection을 할 것임
• 일단 32bit로 컴파일
7. PTRACE를 이용한 INJECTION – 실습 – PTRACE 사용법
•ptrace 함수(시스템
콜)를 이용해본다.
• ptrace는 인자로 넣은
요구에 따라 여러가지
일을 해 준다.
• ptrace로 할 수 있는 일
을 몇 가지 정리했음
명령 설명 비고
PTRACE_ATTACH 프로세스 추적을 위해 대상 프로세스에 대한 부모 프로세스가 됨
일반적으로 자식 프로세스만 attach 가능 (OS 설정 따라 다름)
non-child를 attach하려면 root 권한 필요
attach된 프로세스는 stop됨
PTRACE_TRACEME 자식 측에서 호출해야 함. 부모가 trace할 수 있도록 함.
SIGKILL 이외의 다른 signal을 받으면 부모프로세스에게 SIGTRAP을 걸어 전달
부모 프로세스가 준비되기 전
에 호출하면 안 됨
PTRACE_DETATCH PTRACE_ATTACH로 붙은 프로세스를 분리
PTRACE_POKEDATA
PTRACE_POKETEXT
대상 프로세스 메모리에 기록 (워드 단위) 워드 크기는 OS마다 다름
PTRACE_PEEKDATA
PTRACE_PEEKTEXT
대상 프로세스 메모리에서 읽어서 내용 반환 (워드 단위) 워드 크기는 OS마다 다름
PTRACE_GETREGS
PTRACE_GETFPREGS
대상 프로세스의 레지스터 값을 읽음.
위는 general 레지스터, 아래는 부동소수점 레지스터
PTRACE_SETREGS
PTRACE_SETFPREGS
대상 프로세스의 레지스터에 값을 씀.
위는 general 레지스터, 아래는 부동소수섬 레지스터
PTRACE_CONT 정지 상태에 있는 대상 프로세스를 계속 실행하도록 함
PTRACE_SINGLESTEP 대상 프로세스가 하나의 기계어를 실행한 뒤 멈춤
PTRACE_KILL 대상 프로세스에 SIGKILL을 보내 종료하도록 함
8. PTRACE를 이용한 INJECTION – 실습 – PTRACE 사용법
• ptrace
• 프로세스 attach 하기
• 실행중인 프로그램에 대해 trace할 수 있
도록 attach 한다.
• register 값 얻기
• 프로세스의 register 값을 가져온다
• data 인자에 레지스터 값을 저장할 버퍼
주소를 넣어주어야 한다.
• 프로세스 detach 하기
• attach했던 프로세스를 detach 한다.
9. PTRACE를 이용한 INJECTION – 실습 – PTRACE 사용법
• ptrace 실습
• 컴파일은 32비트로 한다.
• 레지스터 값을 가져오는 ptrace의 data 인자로
버퍼 주소를 넣는다
• 데이터는 sys/user.h 에 정의된 구조체인
user_regs_struct 포맷이다.
10. PTRACE를 이용한 INJECTION – 실습 – PTRACE 사용법
• ptrace
• 대상 프로세스 register에 쓰기
• data에 적어준 주소에 있는 구조체(앞에서 언급)에 있는 값으로 레지스터 값 설정
• 실습
• 컴파일은 32비트로 한다.
• 구조체에 값을 설정하고, ptrace의 PTRACE_SETREGS
• data 인자로 버퍼 구조체 주소를 넣는다
11. PTRACE를 이용한 INJECTION – 실습 – PTRACE 사용법
• ptrace
• 대상 프로세스 메모리 읽기
• 인자에 넣은 addr를 시작으로 워드 크기만
큼 읽는다.
• 32비트의 워드는 보통 4바이트, 64비트의
워드는 8바이트
• 대상 프로세스 메모리에 쓰기
• 인자에 넣은 addr부터 data에 넣은 값을 쓴다.
• 워드 단위로 쓴다.
12. PTRACE를 이용한 INJECTION – 실습 – CODE INJECTION
• code injection
• 직접 코드를 끼워 넣음
• 기계어를 메모리에 쓰고, EIP를 조작하여 실행
• (기존 프로그램을 이어서 하기를 원한다면 레지스터 값을 백업해두고, dlopen 호출하기 전에 레지스터 값
을 백업해두고, bp를 걸어 둔 뒤 다시 복원하면 됨)
• 여기까진 안 하겠음
• 예에서는 그냥 코드 영역에 덮어 썼음
• EIP를 조작할 필요 없었음
• 새로 실행권한이 있는 메모리를 할당하고 이용하거나, 스택에 저장 후 mprotect 함수를 이용하여 실행 권
한을 줘도 됨
13. PTRACE를 이용한 INJECTION – 실습 – CODE INJECTION – INJECTOR
• code injection
• 왼쪽은 끼워 넣을 코드
• 예전에 작성했던 셸 코드를
이용했음
• 오른쪽은 인젝션하는 코드
• eip 바로 뒤에 코드를 넣었음
• ptrace는 워드 단위로 복사한다
는 점에 주의
14. PTRACE를 이용한 INJECTION – 실습 – CODE INJECTION – INJECTOR
• 인젝션된 모습
• 다음엔 기존 프로그램의 코드를 망치지 않고, 다른 영역에 코드를 인젝션하여 실행하고, 다시 기존 코드로 돌아오도록
해보자
15. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION
• so injection
• 기본적으로 Code injection과 비슷함
• dlopen 함수를 호출하는 코드를 injection하여 so injection
• dlopen은 Windows의 LoadLibrary에 해당하는 함수
• 인젝션 한 라이브러리에 __attribute__((constructor)) 를 붙인 함수가 있다면 dlopen이 반환하기 전에 실행 됨
• DllMain과 비슷한 역할
• __attribute__((constructor)) 는 gcc 지시자이다
• 참고로, __attribute__((destructor)) 를 붙이면 main이 끝난 뒤 호출된다.
• (기존 프로그램을 이어서 하기를 원한다면 레지스터 값을 백업해두고, dlopen 호출하기 전에 레지스터 값을 백업해
두고, bp를 걸어 둔 뒤 다시 복원하면 됨)
• 여기까진 안 하겠음
16. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – SHARED LIBRARY
• injection할 라이브러리 제작
• -fPIC : position-independent code로 컴파일 됨
• 올라오는 주소가 랜덤해도 상관없는 코드
• -shared : 공유 라이브러리이므로, 공유 되도록 함
17. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – SHARED LIBRARY
• injection할 라이브러리 테스트
• 동적 라이브러리를 로드하는 코드
• LoadLibrary를 호출한 것과 같음
• -ldl
• libdl.so를 링킹
18. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR
• Injector
• dlopen 함수는 libdl.so 라이브러리에 있는 함수이다
• 인젝션된 코드에서 dlopen을 호출하기 위해서는 대상 프로세스가 libdl.so를 로드해야 한다.
• LoadLibrary를 호출하기 위해 kernel32.dll이 올라와 있어야 한다는 것과 비슷한 맥락
• 따라서, dlopen을 이용한 방법에는 제약이 있다.
• dlopen 대신에 open 시스템 콜과 mmap 시스템 콜을 이용할 수 있음
• -> 직접 .so 파일을 열고, 메모리를 할당하여 올린다.
• 첫 번째 injector 실습은 dlopen을 이용
• ->대상 프로세스에 dlopen을 이용하는 코드를 넣을 것
19. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – DLOPEN
• 대상 프로그램 수정
• dlopen을 plt, got에 잡아 주기 위해 호출했다.
20. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – DLOPEN
• Injector
• dlopen 함수
• 라이브러리가 잘 작동하는지 체크하기 위해 이용했던 코드에
서 dlopen 인자가 어떻게 넘어가는지 확인
• RTLD_NOW는 1로 정의되어 있음
• 이를 참고하여 인라인 어셈블리로 코드를 작성한다.
• PLT를 이용하여 dlopen을 호출하도록 할 것이므로, PLT를 알아
낸다.
• 0x8048410
21. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – DLOPEN
• Injector
• 앞에서 얻은 정보를 이용하여
dlopen을 호출하는 코드를 어셈
블리로 씀
• 이 코드를 injection하여 대상 프
로세스가 dlopen 함수를 호출하
도록 함
• exit을 넣어서 종료되도록 했음
22. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – DLOPEN
• 인젝션된 모습
• 다음엔 기존 프로그램의 코드를 망치지 않고, 다른 영역에
코드를 인젝션하여 실행하고, 다시 기존 코드로 돌아오도
록 해보자
23. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• 특정 라이브러리가 필요한 dlopen은 제약이 있음
• open과 mmap 시스템콜을 이용하여 대신할 수 있다.
• 직접 라이브러리 파일을 열고, 그 내용을 메모리에 올려준다.
• 계획
• open으로 라이브러리 파일을 연다.
• mmap으로 메모리에 올린다.
• EIP를 옮겨 코드를 실행한다.
• 라이브러리가 올라온 주소에 맞게 relocation이 필요
• dlopen은 동적 라이브러리를 올리는 함수로, relocation 작업까지 포함하여 해준다.
• 이번에는 직접 파일을 열어 올리므로 relocation 작업을 직접 해주어야 한다.
• ->재배열이 싫다면 PIC로 컴파일하면 된다. 위치 독립적 코드로, relocation이 필요하지 않음.
24. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• mmap
• fd로 지정된 파일에서 offset을 시작으로 length바이트 만큼을 addr주소로 대응시키도록 한다.
• addr를 시작주소로, 파일 내용을 메모리에 올림
• addr는 단지 그 주소를 사용했으면 좋겠다 정도로, 무조건 해당 주소를 이용할 수 있는 것은 아님
• 보통 0을 지정한다. OS가 알아서 올려 줌
• prot은 원하는 메모리 보호 모드이다. bit OR로 설정.
• 반환 값
• 실제로 메모리에 올라온 시작 주소를 반환
• 아래 있는 munmap은 반대로 메모리를 해제하는 일을 한다.
25. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• open&mmap 실습
• 함수 사용법을 익히고, 인젝터에 필요
한 어셈블리어를 얻기 위해 간단한 예
제 프로그램을 작성해 본다.
26. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• 대상 프로그램
• 다시 dlopen코드를 뺌
• 인젝션 할 라이브러리
• 기존 코드 그대로 사용
• 단, PIC로 컴파일 해야 함(앞에서 PIC로 컴파일 했다)
27. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• 인젝션 할 라이브러리
• relocation이 필요하다.
• ->이 예에서는 relocation이 없어도 가능하도록 인라인 어셈블리로 코딩했음
• 문자열을 write하는 코드의 라이브러리
28. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• Injector
• 앞서 만든 open&mmap 실습 코드에서 시스템 콜 인자를 조사
• open
• flag로 이용할 O_RDWR는 2로 정의되어 있음
• 사실 open은 int open (const char *FILENAME, int FLAGS[, mode_t MODE]) 형태
• 뒤의 mode_t는 O_CREAT 옵션을 이용 할 시 필요
• O_CREAT가 아닐 경우 인자를 2개만 쓸 수 있음(오버로딩)
• edx에는 mode에 해당하는 값(2개만 쓸 경우 무시됨. 쓰레기 값을 넣으면 됨)
• ecx에는 flags에 해당하는 값 (2)
• ebx에는 파일 이름 문자열 주소
• eax에는 시스템 콜 번호인 5
29. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• Injector
• 앞서 만든 open&mmap 실습 코드에서 시스템 콜 인자를 조사
• mmap
• prot로 이용할 PROT_READ | PROT_WRITE | PROT_EXEC 는 0x7로 정의되어 있음
• 인젝터의 코드에서는 length를 100이 아니라 so파일 크기로 넣을 것임
• 7888 (=0x1ED0)
• flags의 MAP_PRIVATE은 0x2로 정의되어 있음
• fd는 open 시스템콜의 반환 값을 이용
• ebx에는 addr에 해당하는 값 (0)
• ecx에는 length에 해당하는 값
• edx에는 prot에 해당하는 값 (7)
• esi에는 flags에 해당하는 값 (2)
• edi에는 fd
• ebp에는 offset에 처리한 값 (파일 시작부터 올릴 것이므로, 0으로 설정하면 됨)
• eax에는 시스템 콜 번호인 0xc0
30. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• Injector
• 앞의 조사를 참고하여 인젝션할 코드 작성
• open과 mmap을 호출하는 코드
• 맨 끝에 bp를 넣음
• open과 mmap을 호출한 뒤 다시 tracer에 제어를 넘김
31. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
• Injector
• 인젝션하는 코드
• open과 mmap을 호출한 뒤 다시 제어를 받고, 인젝션한 라이브러리 루틴을 실행할 수
있도록 EIP를 조작해 줌
• 원본 프로그램을 이어서 실행할 수 있도록 했음
• 인젝션한 코드는 다시 쓰이지 않는 start 루틴에 덮어썼다.
• start 루틴 시작 주소를 쓰면 작동을 잘 안 하여 nop을 넣고 주소를 미뤘음
32. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
•Injector
• 뒤에는 다시 이어하는 루틴을 넣었음
• 원본 레지스터를 복구
33. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – INJECTOR – OPEN&MMAP
•성공
• 이번 예제에서는 인젝션한 라이브러리에 재배치할 요소
가 없도록 했다
• open/mmap을 이용한 경우 재배치가 필요할 수 있다
34. PTRACE를 이용한 INJECTION – VDSO를 이용
• VDSO란?
• Virtual Dynamically linked Shared Object
• user space에 shared object 형태로 mapping 됨
• 신중하게 선택된 커널 영역 루틴들의 집합을 사용자 영역 애플리케이션으로 내보내는 리눅스 커널 매커니즘
• 시스템 콜을 호출할 수 있는 코드를 담고 있다.
• 크게 2가지 일을 함
• 적절한 system call method를 선택하는 역할
• calling overhead를 줄여주는 역할
35. PTRACE를 이용한 INJECTION – VDSO를 이용
• 적절한 system call method를 선택하는 역할
• legacy system call인 int 0x80의 경우 full interrupt-handling paths를 타기 때문에 overhead가 큼
• 이를 해결하기 위해 sysenter가 생김
• system call method가 2개가 되어 어떤 system call method를 선택할 지 결정하는 루틴 필요
• ->VDSO의 __kernel_vsyscall 함수에서 처리
• __kernel_vsycall
• sysenter는 VDSO에서만 사용하는 것이 원칙
• library 등에서 시스템 콜을 호출하기 위해 _dl_sysinfo 전역변수나 gs 레지스터를 이용해 __kernel_vsycall을
호출하고, 여기서 sysenter 하게 됨
• _dl_sysinfo 전역변수에는 __kernel_vsycall 의 주소가 있음
• gs 레지스터는 TCB(Thread Control Block)을 가리키고, TCB의 0x10 위치에 __kernel_vsycall의 주소가 있음
• calling overhead를 줄여주는 역할
• user mode, kernel mode를 왔다 갔다 할 때마다 context switching -> 오버헤드가 큼
• VDSO는 user space에 mapping되기 때문에, user mode에서 실행해도 상관 없는 몇몇 system call의 실제 구현
부를 VDSO에 넣으면 이 시스템 콜을 context switching 없이 처리 가능
• ->가능한 함수는 VDSO에서 처리, context switching이 필요한 함수는 VDSO의 __kernel_vsyscall 호출
36. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – VDSO를 이용
• VDSO를 이용하면 별도의 code injection 없이도 시스템 콜을 호출할 수 있음
• 레지스터에 적절한 인자를 넣어두고 VDSO를 이용하면 됨
• ptrace를 이용하면 이 코드가 어디에 존재하는 지 찾아낼 수 있다
• PTRACE_SYSCALL 이용
• 기본적으로 PTRACE_CONT와 같지만, 시스템 콜이 불릴 때나 시스템 콜이 끝났을 때 tracer에게 제어가 돌아옴
• 호출하려는 순간 멈춤 : 시스템 콜 인자를 확인하기 위한 용도로 쓰임
• 끝나는 순간 멈춤 : return 값을 확인하기 위한 용도로 쓰임
• 이 예에서는 write 시스템 콜을 호출해 봄
• 시스템 콜만 필요한 동작이라면 굳이 injection할 필요 없이, VDSO를 이용하기만 해도 충분
• open / mmap 을 이용하면 so injection 가능
37. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – VDSO를 이용
• 문자열 저장을 위해 대상 프로그램의 메모리 중 읽을 수 있는 공간을 찾음
• ptrace를 이용하여 라이브러리 이름 문자열을 저장한다
• 리틀 엔디안에 주의
• write 할 문자열을 저장할 것
38. PTRACE를 이용한 INJECTION – 실습 – SO INJECTION – VDSO를 이용
• Injector
• bss 영역에 ptrace를 이용하여 문자열
저장
• VDSO를 이용하여 write 시스템 콜을
호출하여 출력
• 주의!
• 시스템 콜 번호를 설정할 때,
orig_eax에 설정해야 함
40. 환경변수를 이용한 INJECTION
• LD_PRELOAD
• LD_가 붙은, ld.so에 속하는 환경변수
• Windows의 AppInit_DLLs 레지스트리와 비슷한 역할을 함
• LD_PRELOAD에 설정된 shard object는 libc를 비롯한 다른 모든 shard object보다 먼저 로딩됨
• libc(기존 C라이브러리 함수)에 있는 함수의 이름과 동일한 함수가 있다면?
• LD_PRELOAD에 적힌 라이브러리 함수를 호출해 줌
• ->자동으로 함수 후킹이 되는 효과!
41. 환경변수를 이용한 INJECTION – 실습 – SO INJECTION
• 앞에서 만든 라이브러리 이용
• 환경변수 LD_PRELOAD에 경로를 저장
• 지속적으로 injection하기 위해서는 파일에 적어주어야 함
• .profile 등의 파일에 저장
42. 환경변수를 이용한 INJECTION – 실습 – SO INJECTION
• 대상 프로그램 실행
• 프로그램 비트 수가 맞아야 함
• 라이브러리가 로딩되며 라이브러리의 constructor 함수가 실행 됨
43. 환경변수를 이용한 INJECTION – 실습 – API HOOKING
• 인젝션을 이용한 API hooking
• 라이브러리 함수와 같은 이름을 이용하는 함수를 제작
• LD_PRELOAD에 써 준 라이브러리는 다른 동적 라이브러리보다 먼저 올라옴
• -> 같은 함수 이름으로 호출 시 인젝션한 라이브러리의 함수가 불림
• 원본 함수를 호출하고 싶을 시
• 동적 라이브러리를 이용하는 함수를 사용하여 직접 주소를 구해 호출
48. 참고 사이트
• Linux 공유 라이브러리
• http://it-diary.tistory.com/3
• ptrace 사용법
• https://mikecvet.wordpress.com/2010/08/14/ptrace-tutorial/
• http://man7.org/linux/man-pages/man2/ptrace.2.html
• Linux injection(code injection/so injection) – ptrace
• http://umbum.tistory.com/123
• http://it-diary.tistory.com/9
• ptrace 권한
• http://egloos.zum.com/seoz/v/4070663
• 동적 라이브러리 이용 함수
• https://wiki.kldp.org/HOWTO/html/Program-Library-HOWTO/dl-libraries.html
49. 참고 사이트
• __attribute__((constructor))
• https://kldp.org/node/68257
• open&mmap을 이용한 injection (ptrace)
• https://www.codeproject.com/Articles/33340/Code-Injection-into-Running-Linux-Application
• ptrace를 이용한 오픈소스 Injector
• https://github.com/gaffe23/linux-inject
• LD_PRELOAD를 이용한 injection과 hooking
• http://umbum.tistory.com/128
• http://it-diary.tistory.com/8
http://hyunmini.tistory.com/55
• http://i5on9i.blogspot.kr/2013/05/hooking-wrapper-function.html
50. 참고 사이트
• VDSO
• http://umbum.tistory.com/61
• so 인젝션 여러 종류 총 정리
• https://backtrace.io/blog/elf-shared-library-injection-forensics/
• Linux systemcall table
• https://syscalls.kernelgrok.com/