[실습 - 컴파일 생성과정 파일 확인]

 

hello.c
0.00MB

 

1) gcc -save-temps -o hello ./hello.c

2) readelf -l ./hello 

 

 

 

 

  • ld-linux.so2 (Linux's Dynamic Loader)
    - 프로그램에서 실행해 필요한 라이브러리를 찾아 로드를 수행 
    - 프로그램실행 시 EntryPoint 가 아닌 ld-Linux.so 라이브러리의 함수를 먼처 호출 하여 필요한 라이브러리를 로드 한 후 다시 EntryPont가 호출 된다. 

[실습 -  ldd]

1) ldd ./hello 

2) nm -d /lib/i386-linux-gnu/libc.so.6

3) nm -d /lib/i386-linux-gnu/libc.so.6 | grep "read"

4) readelf -l ./hello 

5) readelf -h .hello 

 

6) gdb ./hello 

    - x/10i [entrypoint] 

 

정적라이브러리 생성

1) gcc -Wall -c sum.c

2) gcc -Wall -c sub.c

3) ar rcs libtest.a sum.o sub.o

    r - 새로운 오브젝트 파일 추가, 기존파일대체 

    c - 기존파일이 존재하지 않아도 경고 메시지 미 출력

    s - 아카이브 인덱스 생성 

    object 제거 

     ar ds [라이브러리파일이름] [오브젝프파일 ]

   object 추출

     ar xv [라이브러리파일] 

4) gcc -static calc.c -L. -ltest -o calc

 

 

  • 공유라이브러리  
    1. PLT (Procedure Linkage Table)
      • 외부프로시저를 여결해 주는 테이블 
      • 어떠한 GOT 영역의 주소를 참조할 지 표시 되어 있음
    2. GOT (Golbal Offset Table)
      • PLT가 참조하는 테이블로서 , 프로시저들의(실제 호출된 함수) 주소를 가지고 있음. 
      • 첫 호출 시 코드 영역의 벡터주수를, 두 번째 부터 함수의 실제 주소를 담고 있다.

        실습
        1) readelf -S ./hello
    3. 함수 호출 과정 
      • 첫 번째 호출시 PLT 내부의 jmp 문을 통해 GOT 부분으로 이동
      • GOT 는 다시 PLT의 주소 해석 로직으로 이동
      • 주소 해석 루틴에서 dl_runtime_resolver 함수를 호출 하여 라이브러리를 로딩 과 함수를 를 적재 
      • 실제 함수 호출 
        [첫 번째 호출]
      • 두 번째 호출 PLT 내부의 jmp 문을 통해 GOT로 점프 
      • GOT 내부에 저장된 함수 주소로 이동
        [두 번째 호출]
        [실습]

        1) gdb -q ./hello
        2) disas main
        3) b *0x0804842d
        4) r 
        5) si
        [PLT 영역, GOT 부분으로 점프 (ds:0x804a00c)]
        6) x/x 0x804a00c
        7) x/10i 0x080482f6
        [GOT 영역 다시 PLT 첫 부분으로 점프]
        8) x/10i 0x80482e0
        9) b *0x80482e6
        [GOT (Global_Offset_Table] 3번째 주소]
        10) x/x 0xb7ff24b0,    x/10i 0xb7ff24b0

        [실제 함수를 메모리에 올리는 dl_runtime_resolve ]

    4. 공유 라이브러리 생성
      실습)
         1) gcc -Wall -fPIC -c sub.c sum.c 
         2) readelf -h sub.o 
         3) gcc -shared -Wl,-soname,libcalc.so.1 -o libcalc.so.1.0 sum.o sub.o
         4) readelf -h ./libcalc.so.1.0
         5) nm ./libcalc.so.1.0
         6) nm -D ./libcalc.so.1.0
         7) ln -sf ./libcalc.so.1.0 ./libcalc.so.1
         8) export LD_LIBRARY_PATH=.

 

 

 

• 리눅스 인젝션

   • 리눅스 인젝션도 윈도우와 크게 다를 것이 없음

   • 크게 코드 인젝션과 라이브러리(.so) 인젝션으로 구분 가능

• 바이너리에 코드 인젝션: 윈도우에서 실습했던 코드 패치를 생각해 보자 ( ELF 파싱 → 제어 흐름 변경 )

• 공유 라이브러리 인젝션

   • LD_PRELOAD를 이용한 인젝션: export LD_PRELOAD

   • 쉘코드를 이용한 인젝션: open() + mmap() / dlopen()

   • VDSO 수정을 이용한 인젝션

   • 대부분 인젝션은 대표적인 디버깅 API인 ptrace를 이용

 

• ELF 파일에 코드 인젝션

• 인젝션 순서(코드 패치)

   • 세그먼트 영역 사이에 빈공간(패딩) 찾기

   • 실행할 코드를 패딩이 존재하는 세그먼트의 끝에 삽입

   • ELF 바이너리가 삽입한 코드를 먼저 실행하도록 패치(엔트리 포인트 수정)

   • 삽입한 코드 실행을 마치고 다시 엔트리 포인트로 실행 흐름을 이어주도록 패치

 

<실습>

 >  victim.c (hello 출력 코드 작성)
 > gcc  -o victim ./victim.c -m32 
 > gcc  -o injector ./injector.c -m32
 
 
 asm 작성
 
global start
section .text

_start:
        jmp MSG

GOBACK:
        mov eax, 0x4       ; sys_write
        mov ebx, 0x1       ; stdout
        pop ecx            ; message
        mov edx, 0x6       ; length
        int 0x80

        mov eax, 0x11111111
        jmp eax
MSG:
        call GOBACK
        db "Hello ", 0

> nasm -f elf32 -o payload.o payload.asm
> ld -m elf_i386 payload.o -o payload

 

 

 

• LD_PRELOAD를 이용한 인젝션

   • 공유 라이브러리 로드

   • 프로그램 실행 시 공유 라이브러리가 프로세스 공간에 로딩

   • 동일한 라이브러리는 가장 먼저 로드된 것만 사용

   • 로딩 순서: dynamic_section 중 need가 설정된 라이브러리 → LD_LIBRARY_PATH → /etc/ld.so.conf → 표준 라이브러리 경로

 

• LD_PRELOAD 환경 변수를 사용해 라이브러리 로딩 순서를 바꿀 수 있음

 

<실습>

> ltrace whoami


#define _GNU_SOURCE

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>



typedef int (*org_puts)(const char *str);


int puts(const char *str)
{
        org_puts original_puts;
        char new_str[16]={0, };
        original_puts = (org_puts)dlsym(RTLD_NEXT, "puts");
        if (strcmp(str, "stud") == 0)
        {
                strcpy(new_str, "root");
        }

        return original_puts(new_str);

}

> gcc -shared -fPIC -o new_put.so ./puts_over.c -ldl
> LD_PRELOAD=./new_puts.so whoami

 

 

 

 

• 쉘코드를 이용한 인젝션

• mmap()으로 실행 중인 프로그램에 코드 인젝션

  • 실행을 임의로 중단할 수 없는 데몬 같은 프로그램의 기능을 개선하고 싶다면?!

  • 바이러스는 어떻게 감염이 될까?

• 수행 과정

  • 실습에 사용할 코드 작성 및 컴파일: app.cpp / dynlib.hpp / dynlib.cpp / injection.cpp

  • open() + mmap()으로 인젝션 코드를 메모리에 로드

  

 

 

<실습>

 

> g++ -ggdb -Wall dynlib.cpp -fPIC -shared -o ./libdynlib.so
> g++ -ggdb -Wall ./app.cpp -ldynlib -ldynlib -L./ -o ./app
> gcc -Wall injection.cpp -c -o ./injection.o
gdb
>> call open("injection.o", 2)
>> call mmap(0, 1064, 1 |2 |4, 1,3, 0)

> cat /proc/25403/maps
> readelf -r ./app 



set *0x0804a010 = 0xb7768000 + 0x000034
 
 

Relocation section '.rel.text' at offset 0x408 contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000007  00000a02 R_386_PC32        00000000   print
0000000e  00000501 R_386_32          00000000   .rodata
00000013  00000b02 R_386_PC32        00000000   system

Relocation section '.rel.eh_frame' at offset 0x420 contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000020  00000202 R_386_PC32        00000000   .text



0xb76e500(base)

printf =  0xb77c179b 
system =  0xb7554310

0xb76e500 + 0x34 + 0x7(0xb76e53b) = 
print = 0xb77bf000 + 0x34 + 7 =  0xb76e779b -(0xb77bf000 + 0x34 + 7)-4
system = 0xb77bf000 + 0x34 + 0x13 =  0xb747a310 - (0xb77bf000 + 0x34 + 0x13)- 4   (offset + nextEIP)
.rodata = 





(injection base) 0xb7768000

print    = 0xb7768000 + 0x34 + 0x7 
system   = 0xb7768000 + 0x34 + 0x13
rodata   = 0xb7768000 + 0x4d + 0xe

(gdb) set * (0xb7768000+0x000034+0x00000007)=0xb776a79b-(0xb7768000+0x000034+0x00000007)-4
(gdb) set * (0xb7768000+0x000034+0x00000013)=0xb74fd310-(0xb7768000+0x000034+0x00000013)-4
(gdb) set * (0xb7768000+0x000034+0x0000000e)=0xb7768000 + 0x00004d


















 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

실습)

       file calcTest (파일정보)

       head a.txt   (맨앞에 데이터 읽어오기)

       tail a.txt (뒤쪽에 데이터 읽어오기)

       ldd calcTest (의존성 확인)
       base64 -d payload > decodePayload
       file -x ./decodePayload
       tar zxvf ./decodePayload

       grep 'ELF' *

xxd ./67b8601 | head -n 15

dd skip=52 count=64 if=./67b8601 of=elf_header bs=1

readelf -h elf_header

ELF 파일의 크기 

size = e_shoff + (e_shnum * e_shentsize)

8568 + (27+64) = 10296

dd skip=52 count=10296 if=./67b8601 of=libtest.so bs=1

 

4 주차)

readelf -hs ./lib5ae9b7f.so

nm -D --demangle ./lib5ae9b7f.so (c++filt 손상된이름)

export LD_LIBRARY_PATH=`pwd`

echo $?

 

strings ./ctf

strace ./ctf show_me_the_flag

ltrace -i -C ./ctf show_me_the_flag

GUESSME='mobile_sec2' ./ctf show_me_the_flag

 

objdump -s --section .rodata ./ctf
objdump -M intel -d ./ctf
(gdb) b *0x400dc0
(gdb) set env GUESSME=oms81
(gdb) run show_me_the_flag
GUESSME="Crackers Don't Matter" ./ctf show_me_the_flag

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

       

 

 

 

 

 

 

Radare2

- 유닉스기반 리버싱 엔지니어링 프리레임 워크

Introduction · Radare2 Book

radare.gitbooks.io

(https://radare.gitbooks.io/radare2book/content/)

설치

  • git clone https://github.com/radare/radare2.git
    cd radare2/sys
    sudo ./install.sh
  • 기본 명령어
    s - search
    pd/px - 디스어셈블 출력 / HEX DUMP\
    wa/wx - 어셈블 쓰기 / 헥스페어스
    v - 비주얼모드
    q - 종료
  • 명령어 사용법이 궁금하다면 명령어 뒤에 물음표(?)을 붙이자 - s?
  • 유용한 명령어
    S= : 프로그램의 세그먼트 영역 조회
    izz : 바이너리 내의 모든 문자열 검색 ( izz
    LOAD0 )
    ie : 바이너리 엔트리 포인트 조회
    (명령어)j
    {} : JSON 형식으로 결과 출력
    asl number : 시스템콜 번호로 함수 이름 찾기
    x 16 @ 0x400104 : 0x400104 주소부터 16바이트 크기만큼 헥스값 조회
  • r2 명령어 - 고급 명령어 •
    - 고급 명령어
    : 함수 분석, 기계어 분석, 정보 조회, 읽기, 쓰기, 복사/붙여넣기, 검색 등
    : afl, afr, ao, iI, il, ii ...
    : 모든 명령어는 영단어 앞글자를 따온 형태를 띄고 있음
  • • pd : print disasm ( 디스어셈 출력 ) • px : print hexdump ( 헥스덤프 출력 ) • af : analyze function ( 함수 분석 ) • is : info symbols ( 심볼 정보 조회 )

 

  •  r2 명령어 - 비주얼 모드\

    비주얼 모드(v)
    비주얼 모드 종료: q
    커서 이동: hjkl (왼쪽-아래쪽-위쪽-오른쪽)
    출력 모드 변환: p - 헥스, 디스어셈블, 문자열 스트림, 비트맵 이미지 등
    커서 모드 변경: c - 삽입(i), 어셈블리 인라인(a), 복사(y), 붙여넣기(p)
    주어진 오프셋으로 이동: o
    함수 그래프 보기: V ( v / p / tab / y / p / + / f)
    주석 편집 ( 추가 / 제거 ) : ;
    비주얼 모드 안에서 r2 명령어 실행 : :(콜론)
    점프 또는 호출 코드로 이동/복귀 : 엔터 / u

 

  • 동적 분석

    • r2 디버깅
    디버거 실행
    디버깅 모드로 바이너리 열기: r2 -d file_path
    디버거에 인자 전달: r2 -d program=file_path arg1=$(python test.
    실행 중인 프로세스 디버깅: r2 -d pid
    원격 디버깅: - r2 -e dbg.exe.path=/system/lib/ -D gdb
    gdb://192.168.0.27:4545 ./
    app_process
    디버깅 명령어 도움말 보기: d?

 

  • 디버깅 모드 명령어
    프로그램 재시작: do
    실행 지속: dc
    스텝 인/아웃: ds / dso
    브레이크 포인트 설정/해제: db addr / db -addr
    레지스터 값 조회: dr / drr
    레지스터 값 변경: dr reg=value
    백트레이스 정보 조회: dbt
    메모리 : dm
    비주얼 모드는 올리디버거랑 동일한 단축키 ( F2, F7, F8, F9 )

덤프 분석에서는 기본적으로 CORE 메모리 덤프 파일과 . 해당 덤프를 발생 시킨 실행 프로그램이 필요하다. 

sample_core.zip
8.08MB

아래내용은  App2D를 분석한 내용이다. 

 

 

gdb -c ./core -se ./App2D 

 

 

gdb 로 core 덤프 파일 열면 위 그림과 같이 procA() 함수에서 크래쉬가 발생 한 걸 확인 할 수 있다. 

 

 

info thread                           - 덤프 발생 시점에 수행중인모든 스레드를 보여준다. 

 

현재 6개의 스레드가 돌고 있고, 1,6번을 제외하고 4개의 스레드는 현재 중지 상태에 있는걸 확인 할 수 있다. 

 

 

thread 1        -  1번 스레드 선택 

bt                -  현재 스레드의 콜스택 

 

문제가 된 1번 스레드로 context를 맞춰 놓고 콜 스택을 보면 위 그림과 같다. 

 

x/i <address>                        - x 메모리 데이터 View, i<명령> 형식

x/i 0x0000000000400500

 

크래쉬 난 지점의 명령어를 보면  mov    DWORD PTR [rax],0x1

즉 rax 메모리지점에 0x1를 Write 하는 명령어 이다. 

info r $rax                 - info r 는 레지스터의 정보. 

rax 레지스터의 값을 보면 0x0 임을 확인 할수 있고, 즉 0번지에 0x1를 쓰려고 하다가 크래쉬가 발생 하였다. 

 

 

pdis procA            - pdis 는 디스어셈블 

 

procA 함수의 전체 코드를 디스어셈블 해 보면 위와 같다.

rbp는 베이스 레지스터로 위에 보면 mov rbp, rsp 로 함수 시작시 rsp의 값을 가지고 있는걸 확인 할 수 있다. 

위 그림은 스택 프레임 구성이 끝나후의 스택 메모리의 모습니다. 

위 어셈블리코드로 확인하면 mov rbp, rsp 까지 의 내용. 스택메모리는 높은 메모리에서 낮은 메모리쪽으로 수행 된다. 

여기서 [rbp-0x8]은 함수를 호출시 넘겨 온 첫 번째 인자이다. (호출 하는 코드에서 인자를 전달 하였다면)

 

하지만 여기 문제가 된 코드를 보면 rax는 위에  mov rax,QWORD PTR [rbp-0x8] 에서 가져온걸 확인 할 수 있고, 그위에 QWORD PTR [rbp-0x8], 0x0 으로 해당 스택에 0을 저장 하고 있다. (해당 덤프는 연습을 하기위에 일부러 CRASH  발생을 내기위한 예제 코드), 즉 보통 인자로 전달되는 영역에 0을 들어가므로서 크래쉬를 유발 시켰다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

덤프분석 및 디버깅 환경 구축 

 

PEDA (Python Exploit Development Assistance for GDB)

-  PEDA는 Linux 환경에서 Binary 분석 및 Exploit 를 도와주는  플러그인 

github.com/longld/peda

 

longld/peda

PEDA - Python Exploit Development Assistance for GDB - longld/peda

github.com

설치 및 설정 

 

git clone https://github.com/longld/peda.git ~/peda

echo "source ~/peda/peda.py" >> ~/.gdbinit

 

Dump on Linux

 

ulimit -c unlimited       :  프로세스가 Crash   발생하였을때 임시적으로 덤프를 기록 한다. 재부팅하면 다시 명령어를 

                                 수행해야한다. 

 

*  계속설정 

/etc/security/limits.conf 파일에 아래 내용 추가 

 

soft core unlimited    

root hard core 1000000

 

프로세스 덤프는 해당 프로세스의 실행 경로에 저장

 

인)  kill -s SIGQUIT PID , kill -s SIGABRT PD     (해당 프로세스 덤프가 남겼는지 확인)

1. LINUX MEMROY  ARCHITECTURE

 

[리눅스 가상 메모리 구조]

리눅스 메모리는 커널 메모리, 유저 메모리 그리고 접근할 수 없는 NULL Pointer 영역으로 나누어져 있다. 

유저메모리 영역과 커널 영역 메모리의 가장 큰 차이점은 코드 실행 권한에 있다. 커널 영역은 유저보다 높은 권한을 가진다.   이 말은 커널 코드는 유저 메모리 영역에 접근이 가능 하지만, 유저 모드에서 실행되는 코드는 커널 메모리 영역에 접근할 수 없다는 뜻이다. 

 

 

리눅스 가상메모리 

 

애플리케이션이 로드되었을 때, 모든 동적 라이브러리들은 가상 메모리 공간에 할당되게 된다. 

이때 같은 파일에 있는 다른 섹션들(Data, Code)들은 다른 부분에 할당되어진다. 하지만 윈도 모듈들은 순차적으로 같은 공간에 할당된다. 

프로세스가 실행하기 위한 준비가 끝나면 프로세스 ID 가 할당받게 된다. 만약 또 다른 프로세스를 실행한다고 다른른 독립적인 가상 메모리 공간을 할당받는다. 

 

 

 

 

프로세스 코어덤프를 저장하면 커널 영역을 제외한 유저 영역 부분이 저장된다. 하지만 실제로 저장된 덤프는 유저 영역만큼 큰 사이즈를 갖지 않는다.  이유는 코드와 데이터를 채워지지 않는 영역이 존재하기 때문이다. 

만약 어떤 영역이 페이지아웃 되어 있고, 해당 내용이 페이지 파일에 존재한다면 덤프를 저장하기 전에 해당 내용을 다시 메모리에 올려놓고 덤프 저장을 수행한다. 

 

스레드는 실행유닛(즉 코드를 실행하는 객체)이면 하나의 프로세스는 여러 개의 스레드가 존재할 수 있다. 

스레드는 프로세스가 관리하는 여러 객체(자원)들 중 하나이며, 코드를 실행하면서 프로세스가 제공하는 자원(메모리, 핸들, 소켓 등)을 사용한다. 

각각의 스레드는 자신만의 고유한 ID를 가지고 있으며, 리눅스에서는 전통적으로 libc 동적 라이브러리를 통해 커널 공간에 연결 된다. (Windows는 ntdll, MAC 은 libsystem_kernel)

 

 

모든 스레드는 자신이 실행된 함수의 히스토리 그리고 임시적으로 사용하는 변수(데이터)를 기록 해야하는데. 이 공간을 스레드 스택이라고 한다. 덤프 분석에서 가장 핵심은 이 스레드 스택 메모리의 복원이다. 앞으로 덤프 분석을 통해 이 스레드 스택을 복원하는 과정을 알아볼 것이다. 

 

 

+ Recent posts