Ⅰ. 개요
지난 게시글에 이어 CPU와 Memory에 대한 내용 중 부족했던 부분을 보완한다. PEView 프로그램을 활용해서 PE 구조를 살펴본다. Ollydbg 프로그램을 활용하여 컴파일된 프로세스의 흐름을 살펴보고 조작해본다.
Ⅱ. CPU와 MEMORY
CPU에 대한 조금 더 자세한 정리는 지난 게시글을 참조하길 바란다. CPU는 듀얼 코어, 헥사 코어 등으로 나뉠 수 있는데, 하나의 코어가 개별적인 Register를 지니고 있어 현대의 멀티 태스킹(Multi-Tasking)작업이 용이해졌다. 여기서 알아둘 점은 멀티 프로세서와 CPU의 듀얼 코어, 헥사 코어 등은 다른 개념이다. 프로세서는 CPU 같은 하드웨어 장치를 의미하기 때문에, 멀티 프로세서라면 CPU가 2개 이상인 것이고 코어는 CPU안에 소속된 것이다. 즉, 멀티 프로세서라면 2개 이상의 CPU에 각 CPU는 헥사 코어처럼 여러 개의 코어를 가질 수 있다.
메모리 구조를 다시 한 번 짚고 가자면,

이런 식으로 되어 있는데, 커널은 하드웨어랑 신호를 주고 받으며, 셸(Shell)을 통해 유저의 명령어가 들어오면 해당 장치를 작동시키는 역할을 가지고 있다. 그러므로 모든 운영체제는 셸을 통해 커널에 명령을 내려 컴퓨터의 스피커, 모니터, 랜카드 등의 여러 하드웨어를 작동시킨다. 이전에 리눅스에서
[SK쉴더스 SeSAC 성동1기] 가상머신, 리눅스, ubuntu
배운 내용인데 조금 더 자세히 정리했으니 참고하면 좋다. 보안과 안전을 이유로 하드웨어에 대한 직접적인 접근이 안된다. 즉, 커널을 통해서 드라이버로 접근하고 커널을 통하려면 셸을 통해야 한다. 셸은 일종의 껍데기로서 기계어를 모르는 우리 같은 유저가 커널과 하드웨어에 명령을 내릴 수 있게 도와준다. 이 셸은 부모, 자식의 관계를 가질 수 있는데, 만약 웹 개발을 해보신 분이라면 상속을 떠올리기 쉽다. 메인 셸이 있으며, 이 셸이 각종 프로그램 셸을 만드는데, 이건 메인 프로세스가 자식 프로세스를 만들어서 작동시키는 것이다. 윈도우에서 메인 셸은 '
'explorer.exe'로 우리가 브라우저를 열거나 워드, 파워포인트 등을 실행할 때 메인 셸을 거쳐 동작한다. 새로 생성한 셸은 자식 프로세스가 되고 그 안에 메인 스레드와 자식 스레드가 각각 프로그램의 기능을 동작시킨다. 메인 스레드는 프로세스의 전체를 관장하며, 자식 스레드는 개별적인 기능을 맡는다.
※ 윈도우 프로세스 관리 모듈 프로그램을 만들 때 유용한 Python 모듈이 있다. Python으로 wmi 모듈을 활용해보는 것도 재미있으니 해보는 걸 권장한다.
커널에 대한 이야기가 길어졌지만 다시 돌아와 메모리(Memory)는 크게 아래부터 '스택(Stack) 영역 - 힙(Heap) 영역 - 데이터(Data) 영역 - 코드(Code) 영역' 순서로 이어진다고 생각하면 된다. 사실 데이터 영역과 코드 영역은 순서가 중요하진 않기 때문에, 위 사진에선 저렇게 구성되어 있는데, 그 이유는 데이터 영역은 전역 변수나 정적 변수 등이 저장되어있고 코드 영역은 말 그대로 프로세스 실행과 관련한 코드가 저장되어있다. 그런데 스택 영역과 힙 영역은 순서가 중요하다. 메모리 주소는 아래가 높은 주소이고 위가 낮은 주소이다. 스택 영역은 힙 영역보다 아래에 위치하며 높은 주소에서 낮은 주소로 저장된다. 반대로 힙 영역은 낮은 주소에서 높은 주소로 저장되는 것이다. 스택 영역은 지역 변수나 매개 변수가 저장되고 힙 영역은 언제든지 참조할 수 있게 동적으로 할당되며 인스턴스가 저장되어 있다.
Ⅲ. PE(Portable Executable)
지난 게시글에도 잠깐 언급하긴 했지만, 윈도우의 실행 프로그램(프로세스)들은 PE 구조로 되어 있다. 여기서 구조체에 대해 알면 좋은데 구조체는 우리가 아는 리스트나 Json형태를 대신해서 바이트로 쪼개어 프로세스의 코드, 데이터 등을 보여준다. 이 구조를 어떻게 보여줄지는 각각의 Header에 달려있는데, zip파일의 형태이든 PE파일의 형태이든, MFT, VBR 등 모두 각기 다른 다양한 Header 구조를 가진다. (유사한 면이 있다.) Header는 바이트 별로 구분하여 몇 바이트부터 몇 바이트는 어떤 기능이나 역할을 하고 있는지, 실제 데이터는 무엇인지 등 자세히 분류되어 있다.

이미 간단히 설명되었던 부분이지만, 이렇게 PE구조를 보여주는 프로그램을 활용해서 Header의 종류와 분류를 살펴볼 수 있고 실제 주소값들을 알 수 있다. 고정되어 있는 Header 값도 있지만, 동적 Header의 값도 있어 분석할 때 몇 바이트가 무슨 내용인지 아는 것이 중요하며, 간단하게 프로세스의 흐름을 조작하는 실습을 해보고자 한다.
Zero Flag 조작
이 샘플 프로그램의 경우 실제 동작 메인 코드는 401000 주소부터 시작한다. 그래서 BreakPoint를 걸어놓고 F9 단축키(Break Point까지 한 번에 실행한다.)로 401000 주소로 이동한다. Zero Flag는 비교 연산할 때 같으면 1을 반환하고 같지 않으면 0을 반환한다.

저 빨간색 부분을 보면 어셈블리어(Assembly)의 CMP(비교 명령어)를 통해 JNZ(비교 연산 결과 값이 같지 않으면 다음 진행을 건너 뛴다.)를 수행한다. 그래서 실제로 F7, F8 단축키로 프로그램을 진행할 때

저렇게 1로 수정하게 되면 비교 연산 결과 값이 같다고 인식하게 되고 건너뛰지 않고 코드가 이어서 진행된다.
JNZ 명령어 변경
그렇다면 Zero Flag를 변경하는 것이 아니라 JNZ 명령어를 JE, JZ(비교 연산 결과가 같으면 다음 진행 코드를 건너뛴다.)로 변경할 수 없을까? 물론 가능하다.

이런 식으로 프로세스 흐름에서 건너뛰지 않고 잘 통과되는 모습을 볼 수 있다. 이렇게 명령어를 바꾸는 방법은 2가지가 있는데, 사진에서 보이는 것처럼 JE, JZ 명령어는 16진수로 75이다. 이 값을 74로 변경하거나 아니면 명령어 자체를 JE, JZ로 입력해서 바꾸면 된다.
프로세스 로직 주소 변경
위 사진들에서 자세히 보면 Sample_0 뒤에 00401079가 나온 모습을 볼 수 있다. 앞에 샘플은 프로그램 이름이고 뒤에 주소는 코드 주소로서 JNZ 명령어를 실행할 때 어떤 코드를 실행하는 지 주소로 나타낸다. 이 코드 주소를 만약 가장 처음인 401000으로 바꾼다면 어떻게 될 것인가?


위 사진처럼 401000부터 401048을 무한 반복하게 될 것이다.
※ 401000, 401048 등 이 주소들은 엄밀히 말하면, Offset(오프셋)이라고 하는데, 메모리에서 특정 주소로부터 상대적인 주소를 알려준다. 이 Offset을 많이 활용할테니 알아두면 좋다.
HxD를 통해 비교 연산 데이터 변경
개발자가 작성한 비교 연산의 값을 HxD를 통해 실제로 변경하는 것이다.

빨간색 부분이 실제 비교 값들인데, 위에 보면 GetLocalTime으로 현재 시간을 가져오고 있다. 위에 PE 구조에서 Base Code의 시작 지점이 1000이라는 것을 알았다. 그리고 위 사진에서 명령어와 함께 16진수 7D5가 401040 주소인 것을 알았다. 앞에 400000이 붙는 건 프로세스의 메모리 시작 지점이 포함된 것으로 실제 코드는 Base Code를 바탕으로 1000부터이기 때문에 HxD를 통해 1040 지점에 가보면

이렇게 있는 걸 볼 수 있다. 이제 이 값을 현재 연도로 바꾸고 나머지 0과 20을 찾아 현재 월과 날짜로 바꿔주면 된다.

이후 다시 OllyDbg를 실행시켜 동작시켜보면,

이렇게 값들이 변경되어 있고 무사히 CMP 비교 연산을 통과되는 모습을 볼 수 있다.

※ 프로세스는 메모리에 할당되어 주소 값들을 참조하여 변수를 사용하거나 함수를 사용하거나 데이터를 사용하면서 동작한다. 스택 영역은 높은 주소에서 시작하고 점점 낮은 주소로 쌓이는 LIFO(Last in First out) 구조이다. 그래서 코드의 시작 주소 지점을 보면 SUB 명령어로 ESP를 114만큼 빼고 있는데, ESP는 스택 영역의 바닥 부분(가장 마지막 주소 - 헷갈리겠지만 아래에서 위로 올라가니 위가 바닥이다.)을 114만큼 빼서 공간을 확보한다. 굉장히 헷갈릴 수 있는데, 위에서 아래로 진행되기 때문에, 그리고 높은 주소에서 낮은 주소로 가기 때문에 함수가 동작하는데 필요한 메모리 양(114)만큼 빼는 것이다.
후기
새롭게 시작한 애플리케이션 이해 및 활용은 지난 주의 어려웠던 네트워크보다 더 더 어려웠습니다....🤑 포렌식, 리버싱 최소한의 경력이 없는 이상 쉽게 다가갈 수 없을 만한 느낌을 받았고 비전공자로서 배우기엔 상당한 장벽이었지만, 어떻게든 이 일주일을 잘 끝냈다고 생각합니다...🤣 물론 이해했냐를 물어보면 다른 이야기겠지만😅, 앞으로 게시글 몇 개를 더 올리면서 복습 겸 배운 걸 정리하려고 합니다.
Log in