systemhacking/background

linux background 2 (binary hacks)

qkqhxla1 2015. 2. 8. 15:50

gcc 확장기능(page 94)

#include <stdio.h>


void __attribute__((constructor)) test_ctors()

{

        printf("cons\n");

}

void __attribute__((destructor)) test_dtors()

{

        printf("des\n");

}

void main()

{

        printf("main\n");

}


와 같은 애트리뷰트를 덧붙일수 있다.


glibc를 이용하지 않은 Hello World (page 109)

Hello World는 c언어로 5줄 정도면 작성할수 있다. 그런데 동적 링크를 이용한다고 해도 크기가 5kb정도 된다. 좀더 작은 바이너리를 생성하려면?? gcc -o hello -v hello.c 로 컴파일해보면 다양한 .o파일을 링크함을 알수 있다. 그러므로 시스템 콜만으로 hello world를 출력하면 상당히 작은 바이너리를 얻을 수 있다. 



mprotect 시스템콜 (page 142)

메모리를 잘 보호하고 있는 프로세서나 os에서는 힙에 놓인 코드를 실행할수 없는데, mprotect를 이용하면 가능하다.

include <stdio.h>

#include <stdlib.h>


double func()

{

        return 3.14;

}

int main()

{

        void *p=malloc(1000);

        memcpy(p,func,1000);

        printf("pi=%g",((double (*)(void))p)());

        return 0;

}

라는 소스코드를 컴파일해서 실행해보자. 보호 기법이 적용된 운영체제에서는 세그먼테이션 폴트 오류만 뜬다. 그런데 mprotect를 이용해서 실행 권한을 추가시켜주면 잘 실행되는걸 확인할수 있다!!!

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/mman.h>

void allow_execution(const void *addr)

{

        long pagesize = (int)sysconf(_SC_PAGESIZE);

        char *p = (char *)((long)addr & ~(pagesize - 1L));

        mprotect(p,pagesize * 10L, PROT_READ|PROT_WRITE|PROT_EXEC);

}

double func()

{

        return 3.14;

}

int main()

{

        void *p=malloc(1000);

        memcpy(p,func,1000);

        allow_execution(p);

        printf("pi=%g",((double (*)(void))p)());

        return 0;

}


처럼 컴파일해보니 제대로 잘 실행된다. (개신기)



PIE.(page 144)

gcc로 컴파일시 -fPIE옵션을 주게되면 PIE를 생성할 수 있는데 만들어진 파일을 역어셈블해보면 주소가 굉장히 작은 숫자로 되어 있다. 이는 공유 라이브러리처럼 주소공간의 어디에 매핑되어도 작동하도록 PIE내에는 모두 상대주소로 되어있기 때문이다.

[root@localhost 다운로드]# gcc -c -fPIE hi.c

[root@localhost 다운로드]# gcc -o hi -pie hi.o

[root@localhost 다운로드]# ./hi

hello



[root@localhost 다운로드]# objdump -d hi | grep main -A3

000003e0 <__libc_start_main@plt>:

 3e0: ff a3 10 00 00 00     jmp    *0x10(%ebx)

 3e6: 68 08 00 00 00       push   $0x8

 3eb: e9 d0 ff ff ff       jmp    3c0 <_init+0x30>

--

 43c: e8 9f ff ff ff       call   3e0 <__libc_start_main@plt>

 441: f4                   hlt    

 442: 8b 1c 24             mov    (%esp),%ebx

 445: c3                   ret    

--

00000532 <main>:

 532: 55                   push   %ebp

 533: 89 e5                 mov    %esp,%ebp

 535: 83 e4 f0             and    $0xfffffff0,%esp


위에 보면 주소가 3e0등으로 매우 작은걸 확인할수있음.

PIE는 공유 라이브러리와 매우 유사한 성질을 갖고 있다. 링크시 -rdynamic옵션을 부여하면 실행도 가능하고 동적 링크도 가능한 바이너리를 생성할수 있다. 



DWARF2 (page 168)

DWARF2는 디버그용으로 사용되는 정보 형식이다. 여기에는 자료형, 파일위치, 프레임 정보 등의 형식이 정해져 있다.



Valgrind - 비정상적인 메모리 접근 검출 (page 233)

메모리 누수뿐만 아니라 다양한 종류의 버그,실수, 비정상적인 메모리 접근을 검출할수 있다.

ㆍ초기화되지 않은 변수 사용

ㆍ범위를 벗어난 메모리 접근

ㆍ해제가 끝난 메모리 접근

ㆍ복사할 원본과 대상 중첩


예제소스

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>


static char onbss[128];

int main()

{

        char onstack[128];

        int uninit,dummy;

        char *onheap = (char*)malloc(128);


        dummy = onbss[128]; //스택에 공간 128개 할당했는데 129번째 공간 접근

        dummy = onstack[150]; //범위 벗어난 메모리 접근

        if(uninit==0) //초기화 안한 변수에 접근

        {

                printf("hello world!\n");

        }

        close(uninit);

        dummy = onheap[128]; //힙에 공간 128개 할당했는데 129번째 공간 접근

        free(onheap);

        dummy = onheap[0];//해제가 끝난 메모리에 접근

        strcpy(onstack,"build one to throw away; you will anyway");

        strcpy(onstack,onstack+1); //범위 벗어난 곳 접근

        return 0;

}


와 같이 만들어서 컴파일 후 valgrind로 실행해보자.

[root@localhost distribute-0.7.3]# gcc -g -o val val.c

[root@localhost distribute-0.7.3]# valgrind ./val

==15340== Memcheck, a memory error detector.

==15340== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.

==15340== Using LibVEX rev 1884, a library for dynamic binary translation.

==15340== Copyright (C) 2004-2008, and GNU GPL'd, by OpenWorks LLP.

==15340== Using valgrind-3.4.1, a dynamic binary instrumentation framework.

==15340== Copyright (C) 2000-2008, and GNU GPL'd, by Julian Seward et al.

==15340== For more details, rerun with: -v

==15340== 

==15340== Conditional jump or move depends on uninitialised value(s) //초기화되지 않은 변수 사용

==15340==    at 0x80484FE: main (val.c:15)

==15340== 

==15340== Syscall param close(fd) contains uninitialised byte(s)

==15340==    at 0xBB8B5C: __close_nocancel (in /lib/libc-2.10.1.so)

==15340==    by 0xB00A65: (below main) (in /lib/libc-2.10.1.so)

==15340== Warning: invalid file descriptor 134513664 in syscall close()

==15340== 

==15340== Invalid read of size 1 //범위를 벗어난 메모리 접근

==15340==    at 0x8048525: main (val.c:20)

==15340==  Address 0x40240a8 is 0 bytes after a block of size 128 alloc'd

==15340==    at 0x4006F3D: malloc (vg_replace_malloc.c:207)

==15340==    by 0x80484CB: main (val.c:11)

==15340== 

==15340== Invalid read of size 1 //해제가 끝난 메모리 접근

==15340==    at 0x8048548: main (val.c:22)

==15340==  Address 0x4024028 is 0 bytes inside a block of size 128 free'd

==15340==    at 0x4005BCA: free (vg_replace_malloc.c:323)

==15340==    by 0x8048540: main (val.c:21)

==15340== 

==15340== Source and destination overlap in strcpy(0xBEE80894, 0xBEE80895) //대상의 중첩

==15340==    at 0x4007780: strcpy (mc_replace_strmem.c:268)

==15340==    by 0x8048588: main (val.c:24)

==15340== 

==15340== ERROR SUMMARY: 5 errors from 5 contexts (suppressed: 12 from 1)

==15340== malloc/free: in use at exit: 0 bytes in 0 blocks.

==15340== malloc/free: 1 allocs, 1 frees, 128 bytes allocated.

==15340== For counts of detected errors, rerun with: -v

==15340== Use --track-origins=yes to see where uninitialised values come from

==15340== All heap blocks were freed -- no leaks are possible.


잘 잡아낸다. 이거 잘쓰면 유용할듯. valgrind로는 스택에 확보된 메모리나 data/bss영역의 메모리에 대한 비정상적인 접근은 검출하지 못한다. 따라서 위 소스의

dummy = onbss[128]; //스택에 공간 128개 할당했는데 129번째 공간 접근

dummy = onstack[150]; //범위 벗어난 메모리 접근

부분은 잘못됬지만 검출하지 못한다. (하지만 힙에서의 에러는 검출된걸 확인할수 있었음.)



Helgrind - 멀티스레드 프로그램 디버그 (page 237)

Valgrind의 확장된 툴? 이라고 보면 된다고 한다. Valgrind와 사용법이 비슷하므로 따로 적지는 않음.



C로 백트레이스 표시 (page 270)

현재 함수에 도달하기까지 지나온 과정을 표시할 수 있다.

#include <stdio.h>

#include <execinfo.h>

int foo()

{

        void *trace[128];

        int n = backtrace(trace, sizeof(trace) / sizeof(trace[0]));

        backtrace_symbols_fd(trace,n,1);

}

int main() { foo(); return 0; }


gcc -o back -g -rdynamic back.c 로 컴파일 후

실행해보면.

[root@localhost fedora]# ./back

./back(foo+0x1f)[0x80485e3]

./back(main+0xb)[0x8048610]

/lib/libc.so.6(__libc_start_main+0xe6)[0xb00a66]

./back[0x8048531]


처럼 main에서 foo를 호출하기까지의 과정을 보여준다.



pamp -실행 중인 프로세스의 메모리 맵 확인(page 286)

어떠한 프로세스가 사용ㅇ하고 있는 가상 메모리의 범위 등을 파악할 수 있다. pmap은 사실 /proc/pid/maps파일로부터 매핑정보를 얻어와 알기 쉬운 형태로 변환해서 표시하는 것 뿐이다.



strace - 시스템콜 호출하기(page 360)

strace로 시스템콜을 추적할수 있다. -o옵션을 주게되면 옵션 뒤의 파일로 출력한다.



ltrace - 공유 라이브러리의 함수호출 추적(page 362)

ltrace는 공유 라이브러리의 함수호출을 추적하는 툴이다. strace와 사용법의 거의 같다.



prelink - 대량의 공유 라이브러리 링크(page 368)

prelink를 이용하면 동적 링크시 연결을 매우 빠르고 효과적으로 할 수 있다.



livepatch - 실행중인 프로세스 패치. (page 370)

실행 중인 프로세스 패치로 livepatch를 사용할 수 있다.



gprof - 프로파일 조사하기

gprof로 어떠한 함수가 성능상으로 안좋은지, 좋은지 등을 파악할 수 있다.

#include <stdio.h>


void slow()

{

        int i;

        for(i=0;i<2000000;i++);

}

void f()

{

        int i;

        for(i=0;i<1000;i++) slow();

}

void g()

{

        int i;

        for(i=0;i<4000;i++) slow();

}

int main()

{

        f(); g();

}

gcc -O -g -pg t.c

./a.out

gprof a.out 으로 실행이 가능하다.