ptrace - Linux injection ( code injection / so injection )
리눅스는 윈도우의 CreateRemoteThread()
같은 API를 제공하지 않기 때문에, injection에 ptrace()
를 이용한다.
타인의 권한으로 실행된 프로세스에는 TRACEME와 ATTACH가 불가능하기 때문에 동작하지 않는다. 타인 소유 파일을 내가 실행하는 경우는 동작한다.
code injection
특정 실행파일에서 원하는 정보를 추출하거나 원하는 동작을 하게 하는 데 유용하게 쓰일 수 있다. code injection을 통해 target 프로세스가 system("echo Code inject");
을 호출하게 해 “Code inject” 문자열을 출력하도록 해보았다. Ubuntu 15.04에서 진행했다.
실행 흐름을 injection code 쪽으로 돌리기 위해서 eip를 조정해야 하므로 target의 레지스터를 담을 구조체가 필요하다. 보통 레지스터를 담을 구조체로 user\_regs_struct
를 이용하며, sys/user.h
에 architecture 별로 정의되어 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sys/user.h>
struct user\_regs\_struct
{
long int ebx;
long int ecx;
long int edx;
long int esi;
long int edi;
long int ebp;
long int eax;
long int xds;
long int xes;
long int xfs;
long int xgs;
long int orig\_eax;
long int eip;
long int xcs;
long int eflags;
long int esp;
long int xss;
};
target : injection의 대상이 될 대상 프로세스 ( tracee )
1
2
3
4
5
int main(){
printf("%d\n", getpid());
getchar();
return 0;
}
실행하면 자신의 pid를 출력하고 대기한다.
대상 프로세스에 code를 injection할 때, 기계어를 써야 하므로 대상 프로세스에서 동작시킬 코드를 인라인 어셈블리로 작성하고 이에 대응되는 기계어 코드를 얻어낸다.
inlinecode : injection code의 assembly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(){
\_\_asm\_\_ \_\_volatile\_\_(
"xor %eax,%eax \n\t"
"push %eax \n\t"
"push $0x7463656a \n\t"
"push $0x6e692065 \n\t"
"push $0x646f4320 \n\t"
"push $0x6f686365 \n\t"
"mov %esp,%eax \n\t"
"push %eax \n\t"
"mov $0xb7e42160,%ebx \n\t"
"call \*%ebx \n\t"
"add $0x18, %esp \n\t"
);
return 0;
}
어셈블리 코드가 제대로 동작하는지 테스트.
- 이제 objdump 등을 이용해 기계어를 얻어내고,
ptrace(PTRACE_POKEDATA, ...)
를 이용해 타겟 프로세스에 쓰고(injection),- EIP를 수정해 실행 흐름을 injection code 쪽으로 돌려서 code를 실행하면 된다.
다음은 injection을 수행할 프로세스의 코드다. injectcode[]
부분이 타겟 프로세스에 inject하게 될, little endian으로 정렬된 기계어 코드다. ptrace(PTRACE_POKEDATA, ...)
의 입출력 단위는 long이다. 작업을 진행한 OS는 32bit로, sizeof(long) == 4
다. 메모리에 4byte 씩 쓰려면 little endian으로 정렬해주어야 하기 때문에 injectcode를 little endian으로 정렬해 주었다. 처음에 0x90909090
을 안넣으면 fault가 나서 추가해줬다. (core dump를 확인해보니 아마 eip 때문인 듯 싶다)
injector _code : ( tracer )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#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를 해제하지 않고 mprotect()
로 x
권한을 주는 식으로 진행해야 하지만, 귀찮았다.
제대로 injection되었고, injection code가 실행되어 target에서 code inject 문자열이 출력되었다.
so injection
windows의 dll injection이 CreateRemoteThread()
를 이용해 LoadLibrary()
를 호출하는 것 말고는 code injection과 별 차이가 없는 것 처럼 so injection도 그냥 injection code가 dlopen()
을 호출하도록 하면 된다. 그래서 injection을 수행할 프로그램으로 위의 injectproc를 그대로 사용했으며 injectcode[]
부분만 c dlopen()
을 호출하도록 변경해주었다. 유의해야 할 것은, dlopen()
이 libdl
에 있기 때문에 libdl
을 로드하는 프로세스만 injection이 된다는 것이다. 그래서 활용도가 상당히 떨어진다. 굳이 so injection해야 한다면,LD_PRELOAD 를 이용하는 편이 여러모로 낫다. hooking도 자동으로 되니까.
libshell.so : 타겟 프로세스에 injection할 library
1
2
3
4
\_\_attribute\_\_((constructor))
void constructorPrint(){
printf("Ok, injected\n\n");
}
\_\_attribute\__((constructor))
가 붙어있으니 로드되자마자 constructorPrint()
를 호출할 것이다.
target : injection 대상 프로세스 ( tracee )
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <dlfcn.h>
int main(){
dlopen(0, RTLD\_NOW); //to avoid optimization
printf("%d\n", getpid());
getchar();
printf("\nback to target\n");
getchar();
return 0;
}
처음에 -ldl만 써도 되는줄 알고 -ldl만 줬더니 최적화 때문에 libdl을 로드를 안한다. dlopen을 호출해야 libdl이 로드된다. 결국 injection code에서 dlopen()을 호출하려면 traget이 libdl을 로드한 상태여야 하기 때문에, libdl을 로드하도록 dlopen을 호출해줬다.
inlinecode : injection code의 assembly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <dlfcn.h>
int main(){
dlopen(0, RTLD\_NOW); //to avoid optimization
\_\_asm\_\_ \_\_volatile\_\_(
"xor %edx,%edx \n\t"
"mov $0x1,%dl \n\t"
"xor %eax,%eax \n\t"
"push %eax \n\t"
"push $0x6f732e6c \n\t"
"push $0x6c656873 \n\t"
"push $0x62696c2f \n\t"
"push $0x706d742f \n\t"
"mov %esp,%eax \n\t"
"push %edx \n\t"
"push %eax \n\t"
"mov $0xb7fbdcd0,%ebx \n\t"
"call \*%ebx \n\t"
);
return 0;
}
여기서는 딱히 dlopen을 호출할 필요는 없었으나, 컴파일해서 어셈블리 코드가 잘 동작하는지 확인해보기 위해서는 libdl이 있어야 하므로 dlopen을 호출해줬다.
injection을 수행할 프로그램으로는 위의 injectproc를 그대로 사용하면 된다. 단, injectcode부분을 dlopen을 호출하도록 변경해주어야한다.
injector _so : ( tracer )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
int injectcode[] = {
0x90909090,
0x01b2d231,
0x6850c031,
0x6f732e6c,
0x65687368,
0x6c2f686c,
0x2f686269,
0x89706d74,
0xbb5052e0,
0xb7fbdcd0,
0x90ccd3ff
};
...
성공적으로 target 프로세스에서 OK, injected 문자열이 출력되었다.