본문 바로가기

리버싱

윈도우리버싱 정리

/*
문서작성일: 2020-03-01
문서편집일: 2020-03-28 
*/

목차

    - 폰노이만의 컴퓨터 구조
        - 주저장장치, 보조저장장치, 중앙 처리 장치
        - 운영체제

    - 실행파일의 구조


    - 어셈블리 

    - WINAPI의 실체
        - PEB의 구조
        - WINAPI의 주소를 직접 구해보기


내용

----------------------------------------------------------------------------------------------------------------
    -주저장장치, 보조저장장치, 중앙 처리 장치
폰노이만의 컴퓨터 구조는, 2020년도에도 사용되는 매우 유서깊은 컴퓨터의 기본 구조를 칭하는 용어이다.
크게 3가지로 이루어진다.
(non-volatile memory) 보조 저장장치
(volatile memory) 주 저장장치
(CPU) 중앙 처리 장치

load-store 설계에 따르면, RAM에서 계산해야할 값을 가져와서 CPU에서 계산을 진행한 이후,
다시 RAM에 결과를 저장한다.
접근속도는 다음과 같다.
CPU > volatile memory > non-volatile memory 순인데, 용량은 반비례한다. 
따라서 연산을 진행하지않아도 되는 데이터는 non-volatile memory에 저장한다음 필요할때 운영체제를 통해서 
volatile memory에 옮긴다.
volatile memory에서는 연산을 진행중인 데이터를 임시로 저장한다.
필요한 연산은 운영체제가 선택하여 CPU의 레지스터에 접근하여 연산 진행이후 RAM에 결과값을 저장한다. 
----------------------------------------------------------------------------------------------------------------


----------------------------------------------------------------------------------------------------------------
    -운영체제
운영체제는 이전에 말한 연산을 진행하기 위한 일련의 과정을 통제하는 역할이다.

multi tasking은, 하나이상의 작업을 진행할수있는 기술이다.
예전의 운영체제에선, 오직 하나의 작업만 진행할수있었다.
하지만 현대의 운영체제는 어떠한가? 
메신저를 활용하며 인터넷브라우저를 통해서 정보를 검색하고, 때에 따라서는 문서작업까지 같이한다. 
당연해보이는 이 기술이 multi tasking이다.

인간이 보았을때, 모든 프로그램이 동시에 실행되고있는것처럼 보이지만, 이는 사실과 다르다.
운영체제가 아주 빠르게 context switching을 진행해서 동시에 실행되는 것 처럼 보이는 것 뿐이다.
Windows에선 multi tasking을 위해서 context switching이라는 기법을 사용한다.

context switching이란, OS의 스케쥴러가 진행하는 작업이다.
이는, CPU가 연산을 진행하다가, Interrupt가 발생된다면 CPU의 연산을 멈추고 다른 연산을 진행시키게 하는 기술이다.
다른 연산을 진행되게 하기위해서 구현된 구조가 있는데, 이름 PCB (Process Control Block) 이라고한다.
----------------------------------------------------------------------------------------------------------------


----------------------------------------------------------------------------------------------------------------
    -실행파일의 구조
Windows에선 executable file이라는 개념을 통해서 연산을 진행할 프로그램을 정의한다.
executable file은, 보조저장장치에 저장되어있다가 사용자가 어떠한 행동을 취했을때 메모리에 올라가서, 
load-store설계에 따라 연산을 진행하는것을 위해서 만들어진 개념이다.

모든 실행파일의 첫 2byte는 MZ로 시작하며, DOS헤더의 시작을 알린다.
DOS헤더에는 PE헤더의 위치가 저장되어있으며 
PE헤더에는 실행파일의 정보가 표시되어있다.
IAT, EAT, section info, entry address 등의 정보가 표시되어있다.

그렇기 때문에, 실행파일은 다음과 같이 읽으면된다.
DOS -> PE -> ...
이는 운영체제가 실행파일을 로딩할때, 실행파일이 다른 API를 매핑, 로딩할때 일어나는 과정에서 이루어진다.

IAT는 Import Address Table의 약자로, 해당 실행파일이 어떤 API를 호출하는지 RVA로 저장해놓은 테이블 
EAT는 Export Address Table의 약자로, 해당 실행파일이 어떤 API를 제공하는지 RVA로 저장해놓은 테이블

IAT/EAT를 계산하는 방법은 다음 순서대로 진행한다.

=====================================================
Name Pointer Table -> Ordinal Table -> Address Table
=====================================================

RVA란, Relative Virtual Address 의 약자이다.
절대주소로 메모리에 접근하면, 모듈이 재배치되었을때 잘못된주소에 접근 할 수 있기때문에, RVA를 사용하여 접근한다.
RVA + Module Base Address = 실제주소이다. 

Name Pointer Table의 RVA를 참조해서 API의 이름으로 검색한다.
API의 이름이 성공적으로 검색되었을경우, 몇번째로 검색했는지 인덱스의 개념으로 Ordinal Table을 역참조한다.
역참조가 성공적으로 진행된다면, Address Table에 접근할수있는 Ordinal이 나온다.
Address Table에 Ordinal을 사용하여 역참조를 진행하면, 원하는 API의 주소가 나온다.
----------------------------------------------------------------------------------------------------------------


----------------------------------------------------------------------------------------------------------------

    -어셈블리

필자가 부분적으로 알고있는 어셈블리는 x86, x64 두개이다.


x86의 주소는 4byte이며 int32자료형으로 주소를 표현할 수 있다.
x64의 주소는 8byte이며 int64자료형으로 주소를 표현할 수 있다.


x86에선 다음과 같은 레지스터를 사용하여 연산을 진행한다.
eax, ebx, ecx, edx, edi, esp, ebp

x86에서는 함수의 프롤로그가 존재하는데, 이는 스택을 확보하기 위해 존재하는 코드이다.
push ebp
mov ebp, esp 
sub esp, N
~~

함수호출규약은 stdcall, fastcall, cdecl, thiscall등을 사용합니다.

    -stdcall은, WINAPI가 주로 사용하는 방식으로 피호출자가 스택을 정리한다.
return value는 eax에 저장된다.

 

    -fastcall은, 레지스터를 주로 사용하여 스택의 사용을 피하는 방식이다.
따로 표준은 정해지지않았으며 각기 다른 호출 규약을 사용한다.

microsoft fastcall은 처음 두 개의 인수를 ECX, EDX로 사용한다.  (왼쪽에서 오른쪽으로)
나머지 인수들은 오른쪽에서 왼쪽 순으로 스택 위로 푸시된다.


    -cdecl은,  C의 함수가 주로 사용하는 방식으로 피호출자가 스택을 정리하지 않는다.

 

x64에선 다음과 같은 레지스터를 사용하여 연산을 진행한다.
rax, rbx, rcx, rdx, rdi, rsp, rbp, r8 ~ r15

windows x64 에선 fastcall을 우선적으로 레지스터를4개 사용하여 인자를 사용하고
그 이상의 인자를 받는 함수는 스택을 사용한다.
x64에선 함수의 프롤로그가 x86처럼 고정적이지않고, 한번에 스택을 확보한다음 작업을 진행한다.

mov [RSP + 8], RCX
push R15
push R14
push R13
sub RSP, fixed-allocation-size
lea R13, 128[RSP]

----------------------------------------------------------------------------------------------------------------

    -WINAPI의 실체

Windows에서 C와 같은 언어로 코딩하다보면, WINAPI라는 존재와 같이 있게 된다.

Windows에서는 kernel32.dll, ntdll.dll이 두개를 기본적으로 매핑시켜준다. 
kernel32.dll은 매핑되지않는 경우가 드물게 있지만, ntdll은 무조건적으로 매핑시켜준다.

*매핑이란, 두개 이상의 프로그램이 동일한 DLL을 사용할경우, DLL을 램에 한번만 로드하고,
다른 프로그램은 해당 DLL을 로딩하지않고 그대로 접근하여 사용하는 방법이다.
*매핑을 하는 이유는 2가지로, 메모리의 절약과 속도의 차이이다.


OS API는 프로그램이 API를 통해서 OS에게 IO등의 요청을 보내어서 
OS가 해당 요청에 대한 연산을 진행하기 위해 만들어졌다.

이렇게 된다면 이점이 생기는데,
지식이 부족한 본인은 두가지라고 생각한다. 

1. 보안적인 이점과, (모든 IO는 필연적으로 OS에게 허가받아야함)
2. 코드의 통일성, (다른 사람이 코드를 구현해도 기본적인 형태는 같다)


kernel32은 내부적으로 ntdll의 API를 호출하는 경우가 잦다. 
ntdll의 API는 인자값에 따라서 해야 할 일을 결정짓는데, 이렇게 설계한 이유는 멘토님이 답해주셨다!

다음 글은 멘토님의 설명을 인용했다.

/*
하부 미들웨어의 구현에 있어 중요하게 고려해야 할 요소들이 있다면
공통적인 프레임워크를 디자인하는 것이다.
완전히 서로 다른 리소스들이라도 상위부터 하위까지 처리해야 하는 호출 흐름의 깊이가
동일 할 수 있고, 처리해야할 리소스의 종류가 다르다면 어떤 리소스를 선택할것인지를 결정하는
제어 신호를 함께 전달해줘야 하는 경우라면
ntdll과 같은 처리 구조가 미들웨어의 변경을 최소화하면서 하부 리소스를 처리하는 컴포넌트만 
추가하면 되는 디자인이기 때문에!

객체 지향에서 다향성을 구현할 때의 그 맥락과 흡사하다.

모토는 "코드를 그대로 두고 데이터만 추가한다."

원래 IO REQUEST PACKET은 그런식으로 디바이스 IO 제어로 넘겨준다.
*/

'리버싱' 카테고리의 다른 글

reversing.kr (Easy_UnpackMe.exe)  (0) 2020.03.27
reversing.kr (Easy_KeygenMe.exe)  (0) 2020.03.27
reversing.kr (Easy_CrackMe.exe)  (0) 2020.03.27
프로세스 탐색 API구현  (0) 2020.03.03
윈도우 쉘코드  (0) 2019.12.17