x64dbg로 파일 열어주기

문자열 찾기로 main부분 찾기

main부분의 그래프도 살펴봐주었다.

앞에 풀어본 문제와 동일하게 문자열 입력을 받고 맞으면 correct 틀리면 wrong을 출력해주는 문제이다.

문자열을 비교해주는 부분을 가본다.

sar eax,4부분에서 eax에 첫번째 입력값을 넣고 오른쪽으로 4비트를 shift를 한다. 

shl ecx,4부분에서 ecx에 첫번째 입력값을 넣고 왼쪽으로 4비트를 shift를 한다.

and ecx,F0부분에서 and연산을 한다.

or eax,ecx부분에서 or연산을 하고 eax에 값을 저장한다.

주소가 7FF7DC8B300인 src값을 변형해서 일치시키는 문제였다.

위 에 적은 계산 순서대로 코드를 짜서 답을 얻을 수 있었다.

참고

https://esyeonge.tistory.com/71

notepad 32bit를 다운 받고 HDX와 PEview로 열어서 확인해 보았다.

 

중간에 보이는 PE부분이 PE파일의 header부분이다.

헤더부분에는 파일이 실행 되기 위한 필요한 모든 정보들이 있고 구조체 형식으로 저장되어 있다.

 

DOS header부분부터 Section header까지를 PE헤더, Section들을 합쳐서 PE바디라고 한다.

VA는 프로세스 가상 메모리의 절대주소를 말하고

RVA는 어느 기준 위치에서부터의 상대주소를 가리킨다.

RVA + ImageBase = VA 수식으로 나타난다. 프로세스가 메모리에 로딩되는 순간 이미 다른 파일이 로딩되어 있는 경우 relocation을 통해 다른 위치에 로딩하기 위한 것이다.

 

1) DOS_HEADER

PE File Format을 만들 당시에 사용되던 DOS 파일에 대한 하위 호환성을 고려하여 만들어진 부분이다. IMAGE_DOS_HEADER 구조체로 구성되어 있다.

 

구조체의 크기는 40이고 중요 멤버는 e_magic, e_lfanew이다.

- e_magic

:  DOS의 signature를 나타내는 부분으로, MZ값이 와야한다. MZ의 아스키 값인 4D5A가 e_magic 부분에 나타난다.

 

- e_lfanew

: NT Header 구조체의 offset을 나타낸다. 000000E0는 e_lfanew 값으로 나타나는 것을 확인할 수 있다.

 

 

 

2) NT_Header

signature

: 5045000h (PE00)값을 갖는다.

 

3) file header

: IMAGE_FILE_HEADER 구조체에서는 Machine, NumberOfSections, SizeOfOptionalHeader,Characteristics 멤버가 중요하다

- Machine(CPU) : CPU별로 고유한 값이 부여된다.

- NumberOfSections : PE 파일에 존재하는 Section의 개수를 나타낸다.

- SizeOfOptionalHeader : IMAGE_NT_HEADERS의 마지막 멤버인 IMAGE_OPTIONAL_HEADERS32 구조체의 크기를 나타낸다. C언어 구조체이기 때문에 크기가 이미 정해져 있지만, Windows PE로드는 이 값을 보고 구조체 크기를 인식한다.

- Characteristics : 파일의 속성을 나타내는 값으로, 실행이 가능한 형태인지, DLL 파일인지 등의 정보가 OR 형식으로 조합되어 표기된다.

 

4) NT_Header-Optional_Header

PE헤더 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32이다.

 

- Magic : 32bit용 구조체인 경우 10B, 64bit용 구조체인 경우 20B를 갖는다.

- AddressOfEntryPoint : EP의 RVA 값을 나타낸다.

- ImageBase : PE 파일이 로딩되는 시작 주소를 나타낸다.

- SectionAlignment, FileAlignment : 각각 메모리에서의 최소 단위, 파일에서의 최소 단위를 나타낸다.

- SizeOfImage : 가상 메모리에서 PE Image가 차지하는 크기를 나타낸다.

- SizeOfHeader : PE 헤더의 전체 크기를 나타낸다.

- Subsystem : 시스템 드라이버(*.sys)인지, 일반 실행 파일인지(*.exe, *.dll)를 구분한다.

- NumberOfRvaAndSizes : IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타낸다.

- DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 지닌다. 각 항목은 다음과 같다.

 

 

5) Section Header

각 섹션의 속성(property)을 정의한 것이 섹션 헤더이다. 섹션 헤더는 각 섹션별로 IMAGE_SECTION_HEADER 구조체의 배열로 되어 있다.

- VirtualSize : 메모리에서 섹션이 차지하는 크기

- VirtualAddress : 메모리에서 섹션의 시작 주소(RVA)
- SizeOfRawData : 파일에서 섹션이 차지하는 크기

- PointerToRawData : 파일에서 섹션의 시작 위치

- Characteristics : 섹션의 속성(bit OR)

 

참고

https://maple19out.tistory.com/m/17

upx 패킹

upx -o : 생성될 패킹 파일 경로


upx 언패킹

upx -d -o "생성될 언패킹 파일 경로" "언패킹에 사용할 파일 경로"

 

notpadxp.exe파일을 upx패킹을 해주었다.

파일사이즈가 65KB에서 47KB로 줄어든 것을 확인할 수있다.

 

패킹 후의 notepad_upx.exe파일을 PE view로 확인해보면

Size of Raw Data의 크기가 0임을 알 수있다.

압축해제 코드와 압축된 원본 코드가 두번째 섹션에 존재하여 파일이 시작되는 순간 원본 코드를 첫번째 섹션에 압축해제 시키기 때문이다. 압축 해제 코드와 압축된 원본 코드는 두번째 섹션에 존재한다는 뜻이다. 파일이 실행되면 압축 해제 코드가 실행되어 원본 코드를 첫 번째 섹션에 해제시킨다. 그리고 이 압축해제가 끝나면 원본 EP 코드를 실행한다.


UPX로 압축딘 notepad 디버깅

EntryPoint의 주소는 01014280이다.

mov esi,notepad_upx.1010000은 esi레지스터를 두번째 섹션 시작수소로 이동한다.

lea edi, dword ptr ds:[esi-F000]은 edi 레지스터를 첫번째 섹션 시작주소로 이동한다.

 

edx에서 한바이트씩 읽어서 edi에 쓰기

esi 값을 al로 옮기고 esi++,edi값을 al로 옮기고, edi++,edx++

0이 아니면 10142a1로 이동하는 모습을 통해 esi에서 값을 읽고 edi에 가져오는 것을 알 수 있다.

 

디코딩

이부분이 압축을 해제하는 코드이다.

두번째 섹션의 주소에서 차례대로 값을 읽어 연산을 거쳐 압축을 해제하고 edi가 가리키는 첫번째 섹션의 주소에 값을 써준다. 

 

 

원본 코드의 CALL/JMP명령어의 목적지 주소 복원

원본 코드의 call/jmp명령어의 목적지 주소를 복원시켜주는 코드이다.

 

IAT테이블 재구성 루프

IAT테이블을 세팅하는 루프이다. 저장되어 있는 원본 notepadxp.exe에서 사용되는 API이름 문자열이 끝날때까지 반복하면 원본 notepad.exe의 IAT복원 과정이 마무리 된다.

 

OEP 코드로 이동

UPX패커의 특징인 OEP코드로 가는 JMP 평령어는 popad명령어 바로 뒤에 나타난다.

1006 AE0이 원본 notepadxp.exe의 EP주소임을 확인 할 수 있다. 

 


참고 출처

https://jamielim.tistory.com/entry/Reversing-UPX-packing-unpacking

https://su0-0su.tistory.com/58

 

 

문자열 찾기로 또 main부분을 찾아줬다.

뭔가 또 비교하고 Correct나 Wrong이 출력되는 모습이다.

비교해서 correct,wrong이 나오기 전에 부분이 중요해 보여서 자세히 들어가 보았다.

test eax eax 부분에서 0이면 wrong이 나온다.

이번 문제는 도저히 모르겠었다...... 참고한 것들을 통해 문제를 풀어보았다.

lea rcx,qword ptr ds:[7FF78C4A3000]을 통해 rcx에 dump 주소값을 넣는것을 볼 수 있다.

movzx eax, byte ptr ds:[rcx+rax]를 통해 rax에 dump rcx+rax주소에 해당되는 값을 넣고 rax가 0이므로 rcx인 00007FF7458D3000값인 I가 들어간다.

movsxd rax,dword ptr ss:[rsp]부분을 통해 stack에서 000000A3932FF7B0주소 값을 찾아 rcx에 넣었고 이때 아무 값이 없어 0이 들어간다.

참고한 블로그에서 input값을 hihihi로 주었다.

그래서 hihihi는 rsp+20에 저장되어 있는 것을 확인 할 수 있다.

ecx에 덤프의 rdx+rcx주소에 있는 값의 제일 앞에 1바이트를 가져오는데 여기서는 h다.

rcx값과 스택에서 rsp주소에 해당하는 값 xor을 수행한다.

edx에 슽택에서 rspㅈ소에 해당하는 겂을 넣는것을 볼 수 있고 ecx에 dump에서 rcx+rdx*2한 주소에 있는 값을 넣는다.

참고한것에서 비교대상이 되는 값 I와 입력한 h를 비교하고 같지 않으면 return을 한다.

 

따라서 ds:[7FF78C4A3000]에 있는 값이 비교할 값인것을 확인 할 수 있었다.

xor로 1씩 늘어나고 *2로 2배가 되는 것을 주의해야 된다고 한다.

 

결론은 출력값 = (입력값 * ss:[rsp]) + rdx*2를 수행하는 것을 확인 할 수 있고 출력값을 알아서 입력값을 역으로 구하면 된다

입력값 = {출력값 - (rdx*2)}xor ss:[rsp]를 하면 구할 수 있다.

 

I는 ss:[rsp]값이 1이고 rdx*2의 값은 0이기 때문에 그대로 입력해도 된다고 한다.

rdx*2값은 2, ss:[rsp]의 값은 1이다. (96-2)xor1=95가 된다 그래서 '(96)에서 '일때 96에 해당되는 문자열인_을 입력하면 된다고 한다....

 

g(103)의 경우 rdx*2값이 4, ss:[rsp]의 값은 2이므로 (103-4) xor 2 = 99 xor 2 = 0110 0011 xor 0010 = 0110 0001 = 97잉 된다. 따라서 g일때 97에 해당하는 문자열인 a를 입력하면 된다고 한다...

 

이문제는 x64dbg에서만 확인하는 것이 아닌 코드를 짜서 쉽게 계산해 푸는 문제인 것을 확인 할 수있었다

블로그에서 참고하여 코드를 짰다..

I_am_X0_xo_Xor_eXcit1ng

밑에 참고 블로그를 통해 문제를 해결 할 수 있었다. 도저히 혼자 봐서는 문제를 풀 수없었다......

일단 블로그의 도움으로 이해를 하며 따라가고 더 공부를 하며 다시 혼자 풀어볼 예정이다..

 

참고

https://esyeonge.tistory.com/64

'보안 > 리버싱' 카테고리의 다른 글

[리버싱]pe 헤더 정리  (0) 2021.09.25
[리버싱]패킷 & UPX 문서화  (0) 2021.09.25
[리버싱]드림핵 rev-basic-2 풀이  (0) 2021.09.25
[리버싱]어셈블리명령어 정리  (0) 2021.09.25
[리버싱]crackme 1번문제 풀이  (0) 2021.09.11

+ Recent posts