ld-linux.so2 (Linux's Dynamic Loader) - 프로그램에서 실행해 필요한 라이브러리를 찾아 로드를 수행 - 프로그램실행 시 EntryPoint 가 아닌 ld-Linux.so 라이브러리의 함수를 먼처 호출 하여 필요한 라이브러리를 로드 한 후 다시 EntryPont가 호출 된다.
주소 해석 루틴에서 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
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
(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
기본 명령어 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 • 레지스터 값 변경: drreg=value • 백트레이스 정보 조회: dbt • 메모리 맵: dm • 비주얼 모드는 올리디버거랑 동일한 단축키 ( F2, F7, F8, F9 )
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을 들어가므로서 크래쉬를 유발 시켰다.
리눅스 메모리는 커널 메모리, 유저 메모리 그리고 접근할 수 없는 NULL Pointer 영역으로 나누어져 있다.
유저메모리 영역과 커널 영역 메모리의 가장 큰 차이점은 코드 실행 권한에 있다. 커널 영역은 유저보다 높은 권한을 가진다. 이 말은 커널 코드는 유저 메모리 영역에 접근이 가능 하지만, 유저 모드에서 실행되는 코드는 커널 메모리 영역에 접근할 수 없다는 뜻이다.
리눅스 가상메모리
애플리케이션이 로드되었을 때, 모든 동적 라이브러리들은 가상 메모리 공간에 할당되게 된다.
이때 같은 파일에 있는 다른 섹션들(Data, Code)들은 다른 부분에 할당되어진다. 하지만 윈도 모듈들은 순차적으로 같은 공간에 할당된다.
프로세스가 실행하기 위한 준비가 끝나면 프로세스 ID 가 할당받게 된다. 만약 또 다른 프로세스를 실행한다고 다른른 독립적인 가상 메모리 공간을 할당받는다.
프로세스 코어덤프를 저장하면 커널 영역을 제외한 유저 영역 부분이 저장된다. 하지만 실제로 저장된 덤프는 유저 영역만큼 큰 사이즈를 갖지 않는다. 이유는 코드와 데이터를 채워지지 않는 영역이 존재하기 때문이다.
만약 어떤 영역이 페이지아웃 되어 있고, 해당 내용이 페이지 파일에 존재한다면 덤프를 저장하기 전에 해당 내용을 다시 메모리에 올려놓고 덤프 저장을 수행한다.
스레드는 실행유닛(즉 코드를 실행하는 객체)이면 하나의 프로세스는 여러 개의 스레드가 존재할 수 있다.
스레드는 프로세스가 관리하는 여러 객체(자원)들 중 하나이며, 코드를 실행하면서 프로세스가 제공하는 자원(메모리, 핸들, 소켓 등)을 사용한다.
각각의 스레드는 자신만의 고유한 ID를 가지고 있으며, 리눅스에서는 전통적으로 libc 동적 라이브러리를 통해 커널 공간에 연결 된다. (Windows는 ntdll, MAC 은 libsystem_kernel)
모든 스레드는 자신이 실행된 함수의 히스토리 그리고 임시적으로 사용하는 변수(데이터)를 기록 해야하는데. 이 공간을 스레드 스택이라고 한다. 덤프 분석에서 가장 핵심은 이 스레드 스택 메모리의 복원이다. 앞으로 덤프 분석을 통해 이 스레드 스택을 복원하는 과정을 알아볼 것이다.