본문 바로가기
모의해킹 침해대응 과정/본 과정

리버싱 이론 whit Hackme / day 49

by 알거음슴 2021. 6. 3.

리버싱을 통한 의사코드(Pseudo Code) 복원

포렌식 : 해킹사고가 발생시, 법적인 효력이 있는 자료를 추적.

 

1. 리버스 엔지니어링 (Reverse Enguneering)

이진코드로 되어있는 실행 파일을 분석하는 일련의 행위, 소프트웨어를 분석하고 동작을 해명해나가는것을 리버스 엔지니어링이라고 부른다, Malware 에 한정하지 않고 일반적인 소프트웨어 분석하는것을 말하기에 보안이상의 의미가 있다.

[참고]

리버싱 관련 프로그램

* 바이너리 에디터 : Stirling, BZ Editor, HxD

* 계산기 

* 디스어셈블러 : OllyDbg, IDA, Debugger, WinDbg

>기계어 0과 1을 어셈블리 코드로 변환하는 툴

* 디버거 : OllyDbg(windows GUI), Immunity Debugger, gdb(linux TUI), kdbg(linux GUI) ...

프로그램 실행모드에서 분석 Disassembler 기능 포함하며 프로그램을 진행시켜가며 하나씩 분석

 

2. 프로그램 분석 방법(1) (EX: 프로그램 실행 시 분석 유무)

 1) 정적 분석(Static) : 프로그램 실행하지 않고 분석 해당 malware 자체의 code 를 분석하여 동작원리를 파악하는 방식

 2) 동적 분석(Dynamic) : 프로그램 실행하고 break pointer(중간에 멈춤) 정해서 분석하는 방법 침해 전과 침해 이후를 기준으로 하여 비교분석으로 동작 과정 및 결과를 찾아나가는 방식

 

2-1. 프로그램 분석 방법(2) (EX: 소스 코드 존재 유무)

 1) 블랙 박스(Black Box)기법 : 바이너리 코드

 2) 화이트 박스(White Box)기법 : 바이너리 코드 + 소스 코드

 

3. CPU 및 Memory 구조

 1) CPU 기본 구조 

* 연산장치(ALU: Arithmetic and Logic Unit) : CPU(중앙 처리 장치)의 핵심 부분 중 하나로, 산술과 논리 연산을 수행하는  연산 회로 집합으로 구성

* 제어장치(Control Unit) : 입력, 출력, 기억, 연산 장치를 제어하고 감시, 주기억 장치에 저장된 명령을 차례로 해독하여  연산 장치로 보내 처리되도록 지시

* 레지스터(Register) : 처리중인 데이터나 처리 결과를 임시 보관하는 CPU 내의 기억 장치

2) Memory 기본 구조

* 스택(Stack) : LIFO(Last-In First Out) 방식에 의해 정보를 관리. TOP 이라고 불리는 스택의 끝 부분에서 데이터의 삽입과 삭제가 발생.

* 힙(Heap) : 프로그램 실행 중 필요한 기억 장소를 할당하기 위해 운영체제에 예약되어 있는 기억 장소 영역.

* 데이터 세그먼트(Data Segment) : 초기화된 외부 변수나 static 변수 등이 저장되는 영역. 보통 텍스트 세그먼트(Text Segment)와 데이터 세그먼트(Data Segment) 영역을 합쳐 프로그램이라 한다.

* BSS 세그먼트 : 초기화 되지 않은 데이터 세그먼트(Uninitalized data segment)라고 불리며, 프로그램이 실행될 때 0이나 NULL 포인터로 초기화. 외부 변수나 static 변수중 초기화 되지 않은 변수들이 정의될 때 저장.

* 텍스트 세그먼트(Text Segment) : CPU에 의해 실행되는 머신 코드가 있는 영역

 

4. 레지스터(Reguster)

더보기

어셈블리 언어로 프로그래밍을 하기 위해서는 CPU 내부에 있는 레지스터를 알아야한다 레지스터는 CPU가 접근할 수 있는 메모리중에서 가장 빠르게 동작하는 메모리, 여러 연산 등 처리하는동안 임시적으로 데이터를 보관하는데 사용된다.

1) 레지스터 종류

* 세그먼트 레지스터(Segment Register) : 현재 32, 64비트 컴퓨터에 사용되지 않는다.

* 범용 레지스터(General-Purpose Registers) : 일반적인 레지스터

* 명령어 포인터 레지스터(Instruction Point Register, IP) : 다음 실행할 코드의 주소를 담는 레지스터

* 포인터 레지스터(Pointer Register) : 스택 영역을 표시하기 위한 레지스터

* 인덱스 레지스터(Index Register) : 문자열의 시작주소나 기타 다른 데이터들을 저장하기 위해 사용되는 레지스터

2) 범용 레지스터 (General-Purpose Registers)

* EAX(Accumulator, 누산기) : 어큐물레이터(Accumulator) 모든 연산 명령에 사용되는 레지스터, 산술연산, 입출력, Translate 명령어, 주로 산술 연산에 사용(함수의 결과값 저장)

* EBX(Base Register, 베이스 레지스터) :임의의 번지 지정에 사용되는 어드레스 레지스터, offset, 특정 주소 저장(주소 지정을 확대하기 위한 인덱스로 사용)

* ECX(Count Register, 카운트 레지스터) :스트링 조작 명령이나 반복 루프의 계수기로 사용, 반복적으로 실행되는 특정 명령에 사용(루프의 반복 횟수나 좌우 방향 시프트 비트 수 기억)

* EDX(Data Register, 데이터 레지스터) :산술연산, EAX와 함께 자주 사용, 일반 자료 저장(입출력 동작에 사용)

3) 포인터 레지스터

 * EBP(Base Pointer) : 함수의 파라미터나 변수의 위치를 얻어오는데 간접적으로 사용(스택 메모리를 가리킴). 호출된 프로시저(Procedure)를 위한 스택 프레임 내의 고정 Reference point를 나타냄. 저장된 이전의 EBP 값을 SFP(Stack Function Flame Pointer)라고 함. SS 레지스터와 함께 사용되어 스택 내의 변수 값을 읽는 데 사용.

 * ESP(Stak Pointer) : 스택(stack)의 맨 꼭대기를 가리키는데 사용,그러나 프로그램 안에서 수시로 변경되기 때문에 특정 기준 시점을 잡아 ESP값을 EBP에 저장하여 EBP를 기준으로 변수나 패러미터에 접근. SS 레지스터와 함께 사용되며, 스택의 가장 끝 주소를 가리킴.

 * EIP(Instruction Pointer) : 현재 수행중인 코드를 가리킴. 다음 명령어의 오프셋(상대 위치 주소)를 저장하며 CS 레지스터와 합쳐져 다음에 수행될 명령의 주소 형성.

 

 

5. 어셈블리어

더보기

1) 구성

Windows : Intel 문법 사용 - Destination --> Source

Linux : AT&T 문법 사용 - Source --> Destination

2) 명령어의 종류

 (1) 산술 연산 명령 종류

* add(Addition) : 덧셈명령, 캐리를 포함하지 않는 덧셈. add $0x10,%esp (해석) esp += 10 /* eax에 10를 가산 */
* sub(Subtraction) : 캐리를 포함하지 않는 뺄셈(뺄셈 연산 처리에 사용).  sub $0xc,%esp (해석) esp -= 0xc

* inc(Increment) : 오퍼랜드 값을 1만큼 증가한다. inc eax (해석) eax++ /* eax에 1을 가산 */

* dec(Decrement) : 오퍼랜드 값을 1만큼 감소한다. dec eax (해석) eax-- /* eax에 1을 감산 */

* cmp(Compare) :

두 데이터를 비교하여 같으면 CF(Carry Flag)는 0, ZF(Zero Flag)는 1로 설정
CF가 0인 상태에서 두 데이터가 다르면 ZF는 0으로 설정
두 오퍼랜드 값이 같다는 것을 비교하는 방법은 오퍼랜드를 뺀값이 0이면 참, 아니면 거짓.
(예) cmp %ecx,%eax (해석) if (ecx == eax) ZF=1
else ZF=0

* mul(Immediate Multiplication) : 부호 없는 곱셈. EAX와 오퍼랜드를 곱셈 후 결과 EAX 저장한다.(곱셈 연산 처리에 

* div(Dividend) : 부호 없는 나눗셈 EAX의 내용을 오퍼랜드로 나누어 몫은 AL에 나머지는 EAH, EDX로 저장한다.

 (2) 데이터 전송 명령 종류

* mov(Move) : RAM --> CPU(Register) or CPU(Register) --> RAM
- 데이터 이동하는데 사용.
- RAM 끼리의 연산은 이루어 지지 않으므로 RAM의 데이터를 레지스터로 옮기거나
- 레지스터의 데이터를 RAM으로 옮기는 작업을 처리할 때 주로 사용한다.
(예) mov $0x0,%eax (해석) EAX = 0 /* EAX 값을 0으로 대입한다. */
(예) mov ecx,eax (해석) EAX = ECX /* ECX 값을 EAX에 저장 */

* push(Push) : --> RAM(stack)
- 오퍼랜드의 내용을 스택에 넣는다.
- 레지스터의 데이터 임시 보관등에 사용한다.
(예) push $0x8048680 (해석) Stack에 0x8048680 값을 저장

* pop(Pop) : <-- RAM(Stack)
- 스택으로 부터 값을 빼낸다.
- 레지스터의 데이터를 복원할 때 주로 사용한다.
(예) pop eax (해석) Stack 값을 EAX에 저장

* lea(Load Effective Address to Register) : RAM(주소의 값) --> CPU(Register)
- 유효주소를 레지스터로 로드(Load).
- lea 명령어는 데이터를 복사하는 것이 아니라 주소만을 복사한다.
- RAM에서 CPU에 있는 레지스터에 데이터를 옮길때 로드(Load)라는 단어를 사용,
- 반대로 레지스터에서 RAM으로 가는 경우 저장(Store)이라는 표현을 사용한다.
RAM -- Load --> CPU(Register)
RAM <-- Save -- CPU(Register)
(예) lea 0xffffffd8(%ebp),%eax (해석) RAM(주소(0xffffffd8))을 eax 저장
= lea DWORD PTR [%ebp-8],%eax

 (3) 논리 연산 명령 종류

* and(AND) : 논리 and 연산, 두수의 곱셈 연산 후 결과를 참과 거짓으로 표현하는 것으로 교집합의 형태와 동일하다.
- 대응되는 비트가 둘다 1일 때만 결과가 1이고, 그 이외에는 모두 0
* or(OR) : 논리 or 연산, 두수의 덧셈 연산 후 결과를 참과 거짓으로 표현하는 것으로 합집합의 형태와 동일하다.
- 대응되는 비트 중 하나만 1이어도 결과가 1이고, 둘 다 0인 경우에만 0
* xor(Exclusive OR) : 배타적 논리 합(OR) 연산, 두수가 같은 값을 가지면 거짓, 다른 값을 가지면 참을 반환하는 연산으로 초기화 연산이나 암호화 시에 주로 사용한다.
- 대응되는 비트 중에서 한 비트가 1이고 다른 비트가 0이면 1, 두개의 비트가 모두 0 또는 1일 때 0,
- 따라서 두개의 비트가 같으면 0, 다르면 1이다.
* not(Invert) : 오퍼랜드의 1의 보수, 각 비트별 반대의 값으로 변환하여 1의 보수화
* test(And Function to Flags, no result) :첫번째 오퍼랜드와 두번째 오퍼랜드를 AND 연산하여 결과가 0이면 ZF(Zero Flag)를 1로 설정한다.
- 일반적으로, NULL 체크할 때 주로 사용된다.
(예) test %eax,%eax (해석) if((%eax & %eax) == 0) ZF=1 (의역) if(%eax == NULL) ZF=1
else ZF=0 else ZF=0
(예) test %eax, %ecx
(연산전) eax(0000ffff), ecx(ffff0000), ZF(0)
(연산후) eax(0000ffff), ecx(00000000), ZF(1)

 (4) 제어 전송 명령 종류

* jmp(Unconditional Jump) : 무조건 점프 프로그램을 실행할 주소 또는 라벨로 이동한다.
- 목적지 주소로 주소로 이동하고 복귀 주소를 백업하지는 않는다.
* je(Jump Equal)/jz(Jump Zero) : je : (같을 경우 점프) ZF(Zero Flag)가 1일 경우 점프한다. 보통 CMP 명령어 뒤에 온다.
즉, 비교 결과가 같을 때 점프한다.
(점프조건) ZF == 1
* jne(Jump not equal)/jnz(Jump not zero) : jne : ZF가 0일 경우 점프한다. 보통 CMP 명령어 뒤에 온다. 즉, 비교결과가 다를 때 점프한다.
(점프조건) ZF == 0
* call(Call) : 함수(프로시저) 호출시 사용된다. PUSH(EIP) + JMP(jump)의 두가지 기능을 하는 것으로
- 함수 호출시 그 함수의 복귀 주소를 push로 stack에 저장 시키며 jmp를 이용하여 그 함수로 이동한다.
(예) call 0x80483a8 <printf> (해석) 0x80483a8 번지의 printf() 호출(push + jump)

ret(Return) : 함수에서 호출(call)한 위치로 돌아갈때 사용한다. POP(EIP) + JMP(jump)의 두가지 기능을 하는 것으로 함수 지역으로 복귀한다.

 (5) 스트링 명령 종류

 (6) 프로세스 제어 명령 종류