Layer7 동아리 과제

리버싱 6차시 과제

msh1307 2022. 8. 2. 20:40

ELF

ELF?

ELF는 Executable and Linkable Format의 약자로 말 그대로 Linkable과 Executable의 포맷이다.

간단하게 말하면 Linux/UNIX 실행파일이다.

 

Linking View, Execution View

linking전에 object file들이 relocatable file일때가 linking view이다. 

linking 끝나고 executable이 되면 execution view다.

그냥 말그대로 executable(실행가능)이되서 executable file이라고 부른다.

둘 다 ELF다.

 

Executable header, Program header, Section, Section header

ELF Header(Executable Header)

이 파일이 실행될 때 시작되는 entry point 주소가 있다.

이 ELF파일이 어떤 파일인지가 적혀있다.

예를 들면 little endian인지, big endian인지, 64비트인지 32비트인지 등이 적힌다. 

readelf -h로 헤더 볼 수 있다.

 

Program Header

프로그램 헤더 테이블은 세그먼트를 정의한다.

readelf -l로 프로그램 헤더를 확인할 수 있다.

 

Section

섹션은 프로그램과 관련된 데이터 덩어리를 나타낸다.

섹션은 .text나 .data같은 메모리에 로드될 raw data일 수 있다.

또한 .symtab, .strtab이나 .rela.text같은 다른 섹션에 대한 메타 데이터가 될 수 있다.

이런 메타 데이터들은 링커에 의해 쓰이고 거의 메모리에 올라가지 않는다.

 

dynamically linked executable같은 경우에는 dynamic linker에 의해서 symbol table이 heap 같은데 잠깐 로드되고 symbol resolution 끝나고 free 되기도 한다.

 

디버거도 executable elf file에서 symbol table 찾아서 보여준다. 

그냥 일부 섹션은 메모리에 안올라간다고 알고 있으면 된다.

 

Segment

세그먼트는 메모리에 매핑될 수 있는 최소 단위를 나타낸다.

로더가 이 세그먼트를 보고 메모리에 잘 매핑시켜준다.

세그먼트는 섹션의 집합이다.

세그먼트들은 런타임 실행에 필요한 정보를 포함한다.

세그먼트는 OS에게 세그먼트를 가상 메모리에 어디에 로드하는지, 세그먼트의 권한 등을 알려준다.

섹션의 집합인 세그먼트대로 메모리에 매핑된다.

세그먼트가 있고 그 세그먼트에 들어있는 섹션들이 보인다.

PHDR 세그먼트는 프로그램 헤더 자기자신에 대한 세그먼트다. 

 

PT_INTERP, PT_LOAD, PT_GNU_STACK같은 세그먼트들은 메모리에 로드된다.

PHDR, DYNAMIC, NOTE, GNU_EH_FRAME같은 메모리에 로드되지 않고 무시되는 세그먼트도 존재한다.

물론 필요에 따라 달라질 수 있다.

 

기본적으로 LOAD 세그먼트는 메모리에 로드되는 섹션들이 모여있는데, .symtab이나 .strtab같은 심볼 관련된 섹션들은 없는 것을 볼 수 있다.

보면 .symtab이나 .strtab등이 있기는 한데 아까 메모리에 올라가는 세그먼트들을 찾아보면 이런 섹션은 안 올라가는 것을 알 수 있다.

 

대부분의 프로그램들이 dynamically linked 되는 경우가 많기 때문에 런타임에 shared libraries가 메모리에 적재되고 링크될 필요가 있다. 그리고 이 작업은 runtime linker에 의해 수행돼야 된다.

그 런타임 링커는 리눅스에서 ld가 있다.

아래에 ld가 잘 나와있다.

https://man7.org/linux/man-pages/man8/ld.so.8.html

 

ld.so(8) - Linux manual page

ld.so(8) — Linux manual page LD.SO(8) Linux Programmer's Manual LD.SO(8) NAME         top ld.so, ld-linux.so - dynamic linker/loader SYNOPSIS         top The dynamic linker can be run either indirectly by running some dynamically linked program o

man7.org

 

LWN에서는 runtime linker라고 하는데 찾아보면 dynamic linker/loader라고 한다.

아마 링커랑 로더 역할을 둘다 해주는데 그냥 링킹에만 초점을 맞춰서 그렇게 표현한 것 같다.

 

INTERP 세그먼트에는 .interp 섹션이 들어가 있고 이 INTERP 세그먼트에 사용할 인터프리터가 들어가 있다.

 

좀 더 자세한 정보는 아래를 참고

https://lwn.net/Articles/631631/

 

How programs get run: ELF binaries [LWN.net]

Did you know...?LWN.net is a subscriber-supported publication; we rely on subscribers to keep the entire operation going. Please help out by buying a subscription and keeping LWN on the net. February 4, 2015 This article was contributed by David Drysdale T

lwn.net

 

 

Section header table

섹션들의 대한 정보가 있다.

readelf -S로 확인할 수 있다.

 

 

PE

PE?

PE는 Portable Executable로 Windows에서 사용하는 실행파일 포맷이다.

.exe가 대표적이다. 

header와 body로 나뉜다.

 

header는 PE 파일이 어떻게 동작해야 되는지에 대한 규칙을 담고 있다.

body는 헤더에 적혀있는 규칙에 따라 실행되는 기계어들이 저장되어있다.

메모리에 올라오면서 alignment 때문에 저렇게 된다.

alignment는 호율을 위해서 쓴다. 

 

DOS Header, DOS Stub, NT Headers, Section Header(Section Table), Sections

DOS Header, DOS Stub

DOS Header는 DOS 운영체제가 윈도우용 PE 파일을 실행했을 때 적절한 오류 메시지를 보여주고 실제 윈도우용 PE 헤더의 위치를 가리키는 역할을 수행한다.

DOS Stub은 DOS 운영체제에서 윈도우용 PE 파일을 실행했을 때 보여줄 오류 메세지를 저장하고 있다. 

오류처리를 위한 작은 DOS 프로그램이라고 보면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct _IMAGE_DOS_HEADER {
      WORD e_magic;
      WORD e_cblp;
      WORD e_cp;
      WORD e_crlc;
      WORD e_cparhdr;
      WORD e_minalloc;
      WORD e_maxalloc;
      WORD e_ss;
      WORD e_sp;
      WORD e_csum;
      WORD e_ip;
      WORD e_cs;
      WORD e_lfarlc;
      WORD e_ovno;
      WORD e_res[4]; 
      WORD e_oemid;   
      WORD e_oeminfo;   
      WORD e_res2[10];
      LONG e_lfanew;         // offset to NT header 
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
 
 
cs

e_magic은 DOS 시그니처고 e_lfanew는 NT header를 가리킨다.

그리고 DOS Stub은 image dos header 다음부터 NT header 전까지다.

 

NT Headers

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
      DWORD Signature;
      IMAGE_FILE_HEADER FileHeader;
      IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
cs

4바이트 Signature와 File Header, Optional Header로 구성되어있다.

1
2
3
4
5
6
7
8
9
10
typedef struct _IMAGE_FILE_HEADER {
      WORD   Machine;
      WORD   NumberOfSections;
      DWORD TimeDateStamp;
      DWORD PointerToSymbolTable;
      DWORD NumberOfSymbols;
      WORD   SizeOfOptionalHeader;
      WORD   Characteristics;
} IMAGE_NT_HEADER, *PIMAGE_NT_HEADER;
 
 
cs

File Header는 PE 파일에 대한 정보를 가지고 있다.

Machine은 CPU의 고유한 값이다.

이런 식으로 되어있다.

characteristics는

이거 조합해서 나타낸 것이다.

 

나머지는 그냥 읽히는 그대로의 뜻이다.

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
31
32
33
typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
cs

Optional Header는 object 파일들이 안 가지고 있는 헤더라서 optional이라는데 exe 같은 이미지 파일들은 무조건 가지고 있어야 된다.

이 헤더는 OS loader에게 중요한 정보를 제공한다.

Image base나 Entry point 주소나 base of code, base of data 등 많은 정보를 제공한다.

 

Image base는 메모리에 PE 파일이 저장되는 메모리의 시작 주소다. 

PE 파일은 메모리에 로드되면서 Image base + RVA(Relative Virtual Address)에 저장된다.

그리고 image base + RVA를 VA(Virtual Address)라고 부른다. 

이런 식으로 된다.

 

Section Table

섹션 테이블은 Optional Header 바로 다음에 위치한다.

array of Image Section Headers다. 

각각의 Section header는 해당 섹션에 대한 정보를 포함하고 있다.

 

Sections

섹션들은 실제 파일의 content가 저장되는 곳이다.

프로그램의 데이터나 리소스가 들어간 곳이다, 

물론 코드도 있다.

섹션은 목적에 따라서 나뉜다.

 

pFile, RVA, VA

PE 파일에서는 크게 세 종류의 주소 형식을 사용한다.

pFile은 PE 파일 내부에서의 오프셋을 말한다.

RVA는 메모리에 로드되고 기준값에서의 상대적 위치를 말한다.

VA는 가상 메모리상에 젖아되는 실제 주소다.

 

pFile은 PE 파일이 물리적으로 디스크에 저장되었을 때 의미 있는 값이며, VA와 RVA는 메모리에 로드됐을 때 의미 있는 값이다.

 

IAT

IAT(Import Address Table)은 실행 파일 안에 어떤 라이브러리의 어떤 함수를 가져다 쓰는지 기록해놓은 정보다.

로더는 PE 파일을 메모리로 로드할 때 IAT에 기록된 API 이름을 참조해서 실제 주소를 찾아 IAT안에 API를 가리키는 주소를 넣어 놓는다.

코드에서 라이브러리를 참조하는 부분은 IAT 내부에 있는 함수 주소를 사용한다.

 

x64 디버거 단축키

F2 : breakpoint 걸기

F7 : step into

F8 : next instruction 

F9 : 프로그램 실행

+/- : 앞 뒤 이동

space : 어셈블리 코드 수정

F9 프로그램 실행시키고 F8로 이렇게 RIP 옮길 수 있다.

F7 눌러서 call 한 함수 안으로 들어갈 수 있다.

F2로 bp 걸면 이렇게 빨간색 표시 생긴다.

스페이스 누르면 이렇게 수정할 수 있다.

 

rev-basic-0

프로그램 실행시키면

이거 나오니까 이 문자열을 찾아서 main함수를 찾으면 된다.

string 찾기 해보면, 뭔가 벌써 flag를 찾아버린 것 같은데 그냥 해보겠다.

input 스트링 눌러서 가보면

코드로 갈 수 있다.

bp를 걸었다. 

실행시키고 bp에서 멈춰서 하나씩 보고 있다.

RCX, RDX, R8, R9 .. 순서로 인자 전달하는데 지금 "Input : "을 rcx에 넣고 어떤 함수를 call 한다.

다음 instruction 넘어갔더니 무언가가 출력된다.

이 함수는 아마 printf 같은 출력하는 함수라고 추측할 수 있다.

더 가서 다음 함수 call 직전에 보면, rcx에 "%256s"의 주소가 들어가고 rdx에 rsp+20가 들어간다.

입력을 받길래 아무거나 입력했다.

보면 rcx에 아까 입력했던 문자열의 주소를 넣고 어떤 함수를 call 한다.

그리고 그 함수의 리턴 값에 따라서 ZF가 set 되면 wrong이 출력되고 아니면 correct가 출력된다.

그래서 지금 call 하는 함수 안으로 들어가 보면 된다. 

f7이 step into니까 f7로 들어가면 된다.

rcx를 rsp+8에 저장한다.

그리고 rdx에 Compar3_the_str1ng 문자열의 주소 값을 넣고 strcmp를 호출한다.

strcmp를 호출한다.

strcmp로 비교하는 것을 보니까 Compar3_the_str1ng이 답인 것을 알 수 있다.

 

rev-basic-1

프로그램 실행시키니까 Input : 이 나온다. 

스트링 찾아보면 Input 볼 수 있다.

이걸로 main함수 쪽으로 가서 비교 루틴을 보면 된다.

위에 bp 걸었다.

Input : 스트링 넣고 call 하는 함수는 아마 출력하는 함수일 것이고, %256s는 scanf같은 입력받는 함수일 것이다.

test eax, eax 부분 다음을 보면 je로 ZF가 set 되면 wrong이 출력된다.

그러면 ZF가 set 안 되도록 해야 되니까 앞에 함수를 보면 된다.

bp에서부터 하나씩 갔다.

입력은 아무렇게나 줬다.

이 함수에 들어가서 확인하면 된다.

f7로 들어간다.

rcx로 넣은 "asdf" 스트링을 rsp+8에 넣고 

rax에 1 넣고 1*0, 1*1, 1*2 이런 식으로 imul로 한 바이트씩 비교한다.

cmp 부분을 아스키로 바꿔서 보면 된다.

옆에 문자로 바꾼 것도 뜨니까 그거 다 조합하면 된다.

Compar3_the_ch4ract3r 나온다.

Easy Crack

그냥 문자열부터 확인해봤다.

Congratulation 부분부터 확인해보겠다.

Congratulation !! 하는 부분을 보면 인자를 잘 넣고 MessageBoxA를 call 한다.

아래 보면 Incorrect Password가 보인다.

약간 위로 올려보니까 시작하는 부분이 보인다.

bp 걸고

막 입력하고 보면 된다. 

F8로 내리면 된다.

첫 번째 jne는 esp+5랑 비교하는데 dump로 보면

여기다.

두 번째 a가 운 좋게 맞았다.

jne는 wrong password 출력 부분이랑 연결되어있다.

 

esp+A가 push 된다. 그게 dfasdf부분이다.

그리고 5y 문자열 주소가 push된다.

그다음 어떤 함수가 call 된다.

그리고 그 함수의 리턴 값인 eax가 0이냐 아니냐에 따라서 맞고 틀린 지가 갈린다.

그 함수 들어가면 아까 push 한 2가 ecx에 들어간다.

repe cmpsb는 esi와 edi에 들어있는 문자열을 비교하는 명령이다.

esi와 edi가 1씩 증가되면서 비교된다.

repe는 repeat if equal이라 ZF가 1이면 계속 돈다.

만약 다르면 ZF가 0 돼서 멈춰진다. 

ecx가 0 이어도 멈춘다.

 

만약 edi esi의 문자열이 같으면 ZF는 계속 1이니 repe cmpsb가 돈다.

그런데 ecx가 2인 상태에서 1씩 줄어드니까 두 번 돈다.

그때 esi와 edi는 +2 된 거니까 esi-1과 edi-1을 비교해야 된다.

둘 다 같은 문자열이라면 esi-1과 edi-1은 같게 될 것이다.

 

esp+10 dump 한 모습이다.

1 바이트씩 비교한다. 

원래 여기서 ZF가 0이라서 jne가 가야 되는데 그냥 임의로 ZF를 set 해줬다.

ZF 부분 더블 클릭하면 1 된다.

보면 1 더 해서 또 똑같은 짓을 한다.

eax랑 esi를 2씩 더하고 1바이트씩 두 번 비교하고 cl에서 ZF가 set 되면 통과된다. 

이게 무슨 소리냐면 그냥 입력한 거 비교해서 test cl, cl로 만약 0 나와서 test 했을 때 ZF가 set 되면 사용자가 입력한 문자열의 끝이니 넘어간다는 의미다.

그리고 여기 보면 esp+4를 'E'랑 비교한다.

esp+4가 사용자가 입력한 문자열의 첫 번째 문자다.

 

그러면 아까 비교한 거 다 합쳐보면 Ea5yR3versing이 나온다.

Auth에 넣으면 풀린다.

'Layer7 동아리 과제' 카테고리의 다른 글

리버싱 8차시 과제  (0) 2022.08.10
리버싱 7차시 과제  (0) 2022.08.08
리버싱 5차시 과제  (0) 2022.07.31
리버싱 4차시 과제  (0) 2022.07.26
리버싱 3차시 과제  (0) 2022.07.23