ptrace - Linux injection ( code injection / so injection )
리눅스는 윈도우의 ``c CreateRemoteThread()``같은 API를 제공하지 않기 때문에, injection에 ``c ptrace()``를 이용한다.
타인의 권한으로 실행된 프로세스에는 TRACEME와 ATTACH가 불가능하기 때문에 동작하지 않는다.
타인 소유 파일을 내가 실행하는 경우는 동작한다.
code injection
특정 실행파일에서 원하는 정보를 추출하거나 원하는 동작을 하게 하는 데 유용하게 쓰일 수 있다.
code injection을 통해 target 프로세스가 ``c system("echo Code inject");``을 호출하게 해 "Code inject" 문자열을 출력하도록 해보았다. Ubuntu 15.04에서 진행했다.
target : injection의 대상이 될 대상 프로세스 ( tracee )
실행하면 자신의 pid를 출력하고 대기한다.
대상 프로세스에 code를 injection할 때, 기계어를 써야 하므로
대상 프로세스에서 동작시킬 코드를 인라인 어셈블리로 작성하고 이에 대응되는 기계어 코드를 얻어낸다.
inlinecode : injection code의 assembly
어셈블리 코드가 제대로 동작하는지 테스트.
- 이제 objdump 등을 이용해 기계어를 얻어내고,
- ``c ptrace(PTRACE_POKEDATA, ...)``를 이용해 타겟 프로세스에 쓰고(injection),
- EIP를 수정해 실행 흐름을 injection code 쪽으로 돌려서 code를 실행하면 된다.
다음은 injection을 수행할 프로세스의 코드다.
``c injectcode[]`` 부분이 타겟 프로세스에 inject하게 될, little endian으로 정렬된 기계어 코드다.
``c ptrace(PTRACE_POKEDATA, ...)``의 입출력 단위는 long이다. 작업을 진행한 OS는 32bit로, ``c sizeof(long) == 4``다.
메모리에 4byte 씩 쓰려면 little endian으로 정렬해주어야 하기 때문에 injectcode를 little endian으로 정렬해 주었다.
처음에 ``c 0x90909090``을 안넣으면 fault가 나서 추가해줬다. (core dump를 확인해보니 아마 eip 때문인 듯 싶다)
injector _code : ( tracer )
```c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/ptrace.h>
long targetAddr(pid_t pid){
FILE *fp;
char filename[30];
char line[85];
long addr;
char str[20];
sprintf(filename, "/proc/%d/maps", pid);
fp=fopen(filename, "r");
if(fp == 0)
exit(1);
while(fgets(line, 85, fp) != 0){
printf("%s", line);
sscanf(line, "%x-%*x %*s %*s %s", &addr, str, str, str, str);
//break at first 00:00 (device value)
if(strcmp(str, "00:00") == 0)
break;
/*
//break at stack area
if(addr >= 0xbf000000)
break;
*/
}
fclose(fp);
return addr;
}
int main(int argc, char **argv){
pid_t tracee;
struct user_regs_struct oldregs,regs;
long addr;
long peek;
int i;
long injectcode[] = {
0x90909090,
0x6850c031,
0x7463656a,
0x69206568,
0x6320686e,
0x6568646f,
0x896f6863,
0x60bb50e0,
0xffb7e421,
0x18c483d3
};
if(argc != 2){
puts("argc error");
exit(1);
}
tracee=atoi(argv[1]);
ptrace(PTRACE_ATTACH, tracee, NULL, NULL);
wait(NULL);
ptrace(PTRACE_GETREGS, tracee, NULL,regs);
printf("[old-eip = %p]\n", regs.eip);
printf("\n------------\n");
addr = targetAddr(tracee);
printf("[new-eip(=addr=injection_point) = %p]\n", addr);
memcpy(&oldregs,regs, sizeof(regs));
regs.eip = addr+4;
printf("=====%p's old data=====\n", addr);
for(i = 0; i < sizeof(injectcode); i += sizeof(long)){
peek = ptrace(PTRACE_PEEKDATA, tracee, addr+i, 0);
printf("%x, ", peek);
}
for(i = 0; i < sizeof(injectcode); i += sizeof(long)){
ptrace(PTRACE_POKEDATA, tracee, addr+i, injectcode[i/sizeof(long)]);
}
printf("\n=====%p's new data=====\n", addr);
for(i = 0; i < sizeof(injectcode); i += sizeof(long)){
peek = ptrace(PTRACE_PEEKDATA, tracee, addr+i, 0);
printf("%x, ", peek);
}
ptrace(PTRACE_SETREGS, tracee, NULL,regs);
ptrace(PTRACE_CONT, tracee, NULL, NULL);
wait(NULL); // wait int3
printf("\n...receive int3, restore original eip\n");
ptrace(PTRACE_SETREGS, tracee, NULL, &oldregs);
ptrace(PTRACE_DETACH, tracee, NULL, NULL);
}
```
procfs를 이용해 target에서 injection할만한 주소를 얻어낸 다음 그 자리에 injection하게된다.
이제 target을 실행하고, injectproc에 target의 pid를 넘기면 된다.
injectproc의 출력을 보면 target의 0xb7e06000에 제대로 injection되었음을 알 수 있다.
그러나 target에는 아무것도 출력되지 않았다. 왜인가 했더니 procfs의 flag를 보면 0xb7e06000에 실행권한이 없다.
즉 DEP가 걸려있다. DEP를 해제하고 다시 시도.
* DEP를 해제하지 않고 ``c mprotect()``로 `` x``권한을 주는 식으로 진행해야 하지만, 귀찮았다.
제대로 injection되었고, injection code가 실행되어 target에서 code inject 문자열이 출력되었다.
so injection
유의해야 할 것은, ``c dlopen()``이 `` libdl``에 있기 때문에 `` libdl``을 로드하는 프로세스만 injection이 된다는 것이다. 그래서 활용도가 상당히 떨어진다. 굳이 so injection해야 한다면, LD_PRELOAD를 이용하는 편이 여러모로 낫다. hooking도 자동으로 되니까.
libshell.so : 타겟 프로세스에 injection할 library
``c __attribute__((constructor))``가 붙어있으니 로드되자마자 ``c constructorPrint()``를 호출할 것이다.
target : injection 대상 프로세스 ( tracee )
처음에 -ldl만 써도 되는줄 알고 -ldl만 줬더니 최적화 때문에 libdl을 로드를 안한다. dlopen을 호출해야 libdl이 로드된다.
결국 injection code에서 dlopen()을 호출하려면 traget이 libdl을 로드한 상태여야 하기 때문에, libdl을 로드하도록 dlopen을 호출해줬다.
inlinecode : injection code의 assembly
여기서는 딱히 dlopen을 호출할 필요는 없었으나, 컴파일해서 어셈블리 코드가 잘 동작하는지 확인해보기 위해서는 libdl이 있어야 하므로 dlopen을 호출해줬다.
injection을 수행할 프로그램으로는 위의 injectproc를 그대로 사용하면 된다. 단, injectcode부분을 dlopen을 호출하도록 변경해주어야한다.
injector _so : ( tracer )
성공적으로 target 프로세스에서 OK, injected 문자열이 출력되었다.
'Security > Reversing & Dbg' 카테고리의 다른 글
x64/x32 dbg (0) | 2017.07.01 |
---|---|
LD_PRELOAD를 이용한 so injection과 hooking. + wrapping function (2) | 2016.12.19 |
jmp, call instruction 주소 계산 (2) | 2016.11.15 |
Tools - Reversing, bf tool (0) | 2016.09.25 |
Anti debugging (0) | 2016.08.31 |