Notice
Recent Posts
Recent Comments
«   2024/11   »
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
Tags
more
Archives
Today
Total
관리 메뉴

topcue

ELF 바이너리 코드 인젝션 기법(Code Injection Techniques for ELF) 본문

바이너리 분석

ELF 바이너리 코드 인젝션 기법(Code Injection Techniques for ELF)

topcue 2021. 9. 14. 17:12

Bare-Metal Binary Modification

가장 간단한 방법은 hexedit 프로그램을 이용해 바이너리 파일의 바이트를 직접 수정하는 것이다.

디스어셈블러를 이용해 변경하려는 코드나 데이터를 찾은 후 hexedit 프로그램으로 해당 데이터를 패치할 수 있다.

  • 장점

방법이 간단하고 단순한 도구로도 충분히 수행할 수 있다.

예를 들어 악성코드에 안티 디버깅 기능이 있다고 가정하자. 안티 디버깅 코드로 의심되는 instruction을 찾아냈고 이를 단순히 nop으로 패치해 이를 우회할 수도 있다.

  • 단점

일부분만 패치할 수 있다. 바이트를 수정할 수는 있으나 새로 추가할 수는 없다.

만약 새 바이트를 삽입하면 이후 바이트들이 다른 주소로 밀려 각 바이트들의 참조 관계가 망가진다. 이를 다시 되돌리는 데 필요한 재배치 정보들은 링킹 과정에서 삭제된다.

만약 바이너리 내에 패딩 바이트나 불필요한 코드(dead code), 불필요한 데이터가 충분하다면 괜찮겠지만 모든 바이너리에게 이를 기대하기는 힘들다.

Modifying Shared Library Behavior Using LD_PRELOAD

LD_PRELOAD는 동적 링커의 동작과 관련이 있는 환경 변수(environment variable)로 바이너리가 사용하는 공유 라이브러리 함수의 동작을 변경할 수 있다.

링커는 LD_PRELOAD에 명시한 라이브러리들을 libc.so 등의 표준 시스템 라이브러리를 포함한 그 어떤 라이브러리보다도 먼저 로드한다.

이렇게 pre-load된 라이브러리 내에 이후에 로드될 라이브러리에 정의된 함수와 동일한 이름의 함수명을 정의하면 해당 함수를 오버라이드(override) 할 수 있다.

단 라이브러리 함수의 호출만을 변조할 수 있다는 단점이 있다.

또한 정적으로 링크된 함수들은 LD_PRELOAD를 사용해도 오버라이딩할 수 없다. 따라서 컴파일 시 gcc 옵션으로 -fno-builtin 플래그로 함수를 built-in 버전으로 사용해 정적으로 링크하지 못하도록 해주는 것이 좋다.

Injecting a Code Section

Injecting an ELF Section: A High-Level Overview

  • Replacing .note.ABI-tag with an injected code section.

위 그림은 ELF 바이너리에 새로운 코드 섹션을 삽입하는 절차를 나타내고 있다. 왼쪽이 원본 바이너리고, 오른쪽이 .injected 섹션이 삽입된 바이너리다.

바이너리에 섹션을 삽입하기 위해 해당 섹션의 바이트 수를 먼저 추가한 뒤 바이너리의 가장 끝 부분에 추가해야 한다➊.

그리고 삽입한 섹션 헤더에 대한 섹션 헤더➋와 프로그램 헤더➌를 만들어야 한다.

프로그램 헤더 테이블은 ELF 파일 헤더(executable header) 바로 뒤에 위치하므로 기존의 모든 섹션 헤더를 이동시켜야 한다➍.

이는 너무 복잡한 작업이므로 차라리 기존에 존재하던 것을 덮어 씌우는 것이 더 좋다.

Overwriting the PT_NOTE Segment

PT_NOTE 헤더는 PT_NOTE 세그먼트를 명세하는 헤더로서 안정적으로 덮어쓸 수 있다.

PT_NOTE 세그먼트에는 바이너리의 부가 정보가 포함된 섹션이 있다. 이 정보들이 없어도 로더는 단순히 native binary라고 인식할 뿐이다.

➋ 단계는 .injected 섹션 삽입을 위해 .note.* 섹션 헤더를 수정하는 과정이다. sh_type을 SHT_NOTE에서 SHT_PROGBITS로 변경하여 코드 섹션임을 명시했다. 또한 sh_addr, sh_offset, sh_size 필드를 각각 .injected 섹션 정보에 맞게 적절히 변경했다. 마지막으로 섹션 정렬 sh_addralign을 16바이트로 변경했고 sh_flags에 SHF_EXECINSTR을 추가해 실행 가능하도록 했다.

➌ 단계도 유사한데, PT_NOTE 값을 섹션 헤더에서 프로그램 헤더로 변경한다. p_type을 PL_LOAD로 설정해 로드 가능한 세그먼트로 지정한다. 그러면 프로그램 시작 시 로더에 의해 .injected 세그먼트가 메모리에 적재된다. 이후 p_offset, p_paddr, p_vaddr, p_filesz, p_memesz의 값을 변경한다. 그리고 읽기 전용 플래그였던 p_flags 필드를 읽기/쓰기가 가능하도록 수정하고, 정렬 정보 p_align도 수정한다.

Redirecting the ELF Entry Point

➍ 단계는 선택적인데, e_entry 필드를 수정해 .text 영역에 있던 엔트리 포인트(entry point) 대신 .injected 섹션의 주소를 가리키도록 하는 것이다.

그러면 프로그램 시작 시 .injected 섹션의 코드가 시작될 것이다.

만약 엔트리 포인트를 그대로 둔다면 .injected 영역의 코드를 트리거 하는 코드가 추가적으로 필요하다.

Using elfinject to Inject an ELF Section

elfinject와 같은 도구를 사용하는 방법도 있다.

  • elfinject usage
$ ./elfinject
Usage: ./elfinject <elf> <inject> <name> <addr> <entry>

Inject the file <inject> into the given <elf>, using
the given <name> and base <addr>. You can optionally specify
an offset to a new <entry> point (-1 if none)

elfinject는 5개의 인자가 필요하다.

각각 타겟 바이너리 경로, 삽입할 코드의 경로, 삽입할 섹션 이름, 삽입할 주소, 코드의 엔트리 포인트 오프셋(없으면 -1)이다

예를 들어 대상 바이너리인 target의 0x800000 주소에 inject.bin 코드를 삽입하려는 경우 다음과 같이 사용할 수 있다. 엔트리 포인트는 0으로 설정하여 프로그램 시작 시 inject.bin 코드가 바로 시작되도록 했다.

$ ./elfinject target inject.bin ".injected" 0x800000 0

코드는 참고 링크의 github에서 확인할 수 있다.

또한 자세한 사용 방법이나 다양한 예제와 실습 또한 확인할 수 있다.

Calling Injected Code

Entry Point Modification

엔트리 포인트 수정은 readelf 등을 이용해 entry point 주소를 찾은 뒤 해당 바이트를 hexedit과 같은 에디터나 스크립트를 통해 수정하는 방법이다.

대상 바이너리에 코드를 미리 삽입해둬야 하며 entry point를 해당 주소로 바꿔야 한다.

Hijacking Constructors and Destructors

constructor/destructor hijacking은 코드 삽입 이후에 생성자 또는 소멸자 주소로 제어권을 넘겨 프로그램 앞이나 뒤에 코드를 삽입할 수 있는 기법이다.

예를 들어 x86_64에서 gcc로 컴파일한 ELF 바이너리는 .init_array와 .fini_array 섹션이 있다. 이들을 수정하면 각각 main 함수 호출 직전과 직후에 코드를 삽입할 수 있다.

Hijacking GOT Entries

GOT entry hijacking은 기존 라이브러리 함수 등을 대체하여 삽입한 함수를 반복적으로 호출하는 방법이다.

GOT는 공유 라이브러리 함수들의 포인터 테이블로 동적 링킹에 활용된다. 이 테이블 내의 값들을 조작해 LD_PRELOAD 기법과 유사한 동작으로 제어권을 획득할 수 있다.

.got.plt 섹션은 코드 섹션과 달리 실행 중에 수정이 가능해서 실행 시점에 수행할 수 있다.

Hijacking PLT Entries

PLT entry hijacking도 기존에 존재하는 라이브러리 함수를 조작하는 기법이다.

다만 GOT 탈취와 달리 코드 섹션인 PLT를 직접 바꿔줘야 한다.

따라서 바이너리의 동작이 수행 중인 시점에서 수정해야 할 때는 적합하지 않은 방법이다.

Redirecting Direct and Indirect Calls

직접/간접 호출 조작(redirecting direct/indirect call)은 디스어셈블러를 이용해 수정하려는 함수 호출 부분을 찾아 직접 변조하는 방법이다.

GOT나 PLT 엔트리는 라이브러리 함수 조작에만 국한되므로 라이브러리 함수가 아닌 함수를 조작할 때 사용할 수 있다.

이때 간접 호출을 변조하는 방법은 간접 점프를 직접 점프로 바꿔주는 것이다.

그러나 인코딩 결과가 간접 호출 명령어보다 큰 경우에는 불가능한데, 이 경우 함수를 간접적으로 호출하는 부분을 찾아야 한다. 대체할 함수의 주소를 찾았다면 .rodata 섹션에서 그 주소를 찾아야 한다. 그리고 해당 주소로 점프하는 그 함수 포인터를 덮어쓰면 된다.

만약 찾지 못했다면 그 포인터는 실행 시점에 계산되는 것일 수도 있다. 이 경우 복잡한 헥스 편집이 필요하다.


참고 및 인용

[1] https://practicalbinaryanalysis.com/

[2] http://www.acornpub.co.kr/book/binary-analysis

[3] https://hexterisk.github.io/blog/posts/


Comments