엄범


리눅스는 윈도우의 ``c CreateRemoteThread()``같은 API를 제공하지 않기 때문에, injection에 ``c ptrace()``를 이용한다.

2016/12/25 - [System/LINUX & UNIX] - ptrace


타인의 권한으로 실행된 프로세스에는 TRACEME와 ATTACH가 불가능하기 때문에 동작하지 않는다.

타인 소유 파일을 내가 실행하는 경우는 동작한다.


code injection

특정 실행파일에서 원하는 정보를 추출하거나 원하는 동작을 하게 하는 데 유용하게 쓰일 수 있다.

code injection을 통해 target 프로세스가 ``c system("echo Code inject");``을 호출하게 해 "Code inject" 문자열을 출력하도록 해보았다. Ubuntu 15.04에서 진행했다.


실행 흐름을 injection code 쪽으로 돌리기 위해서 eip를 조정해야 하므로 target의 레지스터를 담을 구조체가 필요하다.
보통 레지스터를 담을 구조체로 `` user_regs_struct``를 이용하며, `` sys/user.h``에 architecture 별로 정의되어 있다.
```c
#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 )

```c
int main(){
        printf("%d\n", getpid());
        getchar();
        return 0;
}
```

실행하면 자신의 pid를 출력하고 대기한다.



대상 프로세스에 code를 injection할 때, 기계어를 써야 하므로

대상 프로세스에서 동작시킬 코드를 인라인 어셈블리로 작성하고 이에 대응되는 기계어 코드를 얻어낸다.

inlinecode : injection code의 assembly

```c
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;
}
```

어셈블리 코드가 제대로 동작하는지 테스트.


  1. 이제 objdump 등을 이용해 기계어를 얻어내고,
  2. ``c ptrace(PTRACE_POKEDATA, ...)``를 이용해 타겟 프로세스에 쓰고(injection), 
  3. 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

windows의 dll injection이 ``c CreateRemoteThread()``를 이용해 ``c LoadLibrary()``를 호출하는 것 말고는 code injection과 별 차이가 없는 것 처럼 so injection도 그냥 injection code가 ``c dlopen()``을 호출하도록 하면 된다. 그래서 injection을 수행할 프로그램으로 위의 injectproc를 그대로 사용했으며 ``c injectcode[]`` 부분만 ``c dlopen()``을 호출하도록 변경해주었다. 

유의해야 할 것은, ``c dlopen()``이 `` libdl``에 있기 때문에 `` libdl``을 로드하는 프로세스만 injection이 된다는 것이다. 그래서 활용도가 상당히 떨어진다. 굳이 so injection해야 한다면, LD_PRELOAD를 이용하는 편이 여러모로 낫다. hooking도 자동으로 되니까.



libshell.so : 타겟 프로세스에 injection할 library

```c
__attribute__((constructor))
void constructorPrint(){
        printf("Ok, injected\n\n");
}
```

``c __attribute__((constructor))``가 붙어있으니 로드되자마자 ``c constructorPrint()``를 호출할 것이다.


target : injection 대상 프로세스 ( tracee )

```c
#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

```c
#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 )

```c
...
        int injectcode[] = {
0x90909090,
0x01b2d231,
0x6850c031,
0x6f732e6c,
0x65687368,
0x6c2f686c,
0x2f686269,
0x89706d74,
0xbb5052e0,
0xb7fbdcd0,
0x90ccd3ff
};
...
```




성공적으로 target 프로세스에서 OK, injected 문자열이 출력되었다.


'Security > Reversing & Dbg' 카테고리의 다른 글

gdb peda / gdb-multiarch  (0) 2017.07.13
gdb  (0) 2017.07.12
LD_PRELOAD를 이용한 so injection과 hooking. + wrapping function  (2) 2016.12.19
ptrace - Linux injection ( code injection / so injection )  (0) 2016.12.12
jmp, call instruction 주소 계산  (2) 2016.11.15
Anti debugging  (0) 2016.08.31