# CodeEngn Basic 16
Name이 CodeEngn일때, Serial을 구하시오
성공실패 문구 근처에 가봄
EAX 값에는 우리가 입력한 임의의 값인 111의 16진수 값이 들어가 있었음
시리얼 값이 들어있을 EBP-3C 값을 찾아보도록
EBP란?
- EBP : Pointer to data on the stack (in the SS segment)
EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가
함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 합니다.
(이것을 Stack Frame 기법이라고 하며, 리버싱에서 중요한 개념입니다.)
출처: https://asiatica-aramid.tistory.com/28 [Investigate and write! :D]
여기 4번 공간서 찾을 수 O
stack 임시저장공간
-> 함수 호출시, 필요 인자 전달하는 용도
EAX와 [EBP-3C]의 값을 비교하기
Address->Relative to EBP를 선택 -> 주소를 EBP 값을 기준으로 뜨도록 선택
EBP-3C의 값을 확인: E4C60D97임을 확인했음
답: 3838184855
# CodeEngn Basic 17 - 풀이자 장지원
Key BEDA-2F56-BC4F4368-8A71-870B 일때 Name은 무엇인가
힌트 : Name은 한자리인데.. 알파벳일수도 있고 숫자일수도 있고..
정답인증은 Name의 MD5 해쉬값(대문자)
이제까지 Name을 알고 Key를 찾는 문제가 많았는데, 좀 색다르다!
입력해보니, 맞지 않으면 어떤 효과도 주지 않는 심심한 프로그램이다.
근데 Name에 1~2 글자만 쓰면 문자를 더 입력해달라는 문구가 뜸
그 문구를 추적
엑스표 친 분기에 걸리면 좋은 결과를 얻을 수 없다.
따라서 동그라미 친 조건점프에 잘 걸려서 통과해야한다.
첫번째 동그라미는 3글자 이상 쓰면 되는 것과
두번째 동그라미는 30글자 이하 쓰면 되는 것..
답은 한 글자라고 했으므로, 여기를 일단 바꿔줘야 한다.. cmp eax, 3을 1로 바꾸면 될 듯?
이렇게 바꾸면 한 글자여도 문자열을 더 입력하라는 부분을 건너뛸 수 있다
표시한 분기를 따라가지 않아야 정답일텐데, 위에 따로 비교하는 것이 없이 바로 조건점프문이 나온다. 이는 바로 직전 Call에서 무언가 일이 수행되고, 그것이 BEDA-2F56-BC4F4368-8A71-870B랑 비교해서 아마 틀리면 점프해버리는 것이라 추측할 수 있다.
이 Call의 내부
ESI에 772를 곱한다
ESI의 제곱값을 EDX에 넣는다
ESI에 EDX를 곱한다
ESI에 474를 곱한다
ESI+ESI 연산
이 연산으로 시리얼이 생성되는건데
음.. 좀 시간이 걸릴 것 같아서, 다른 분들이 한 거 보니까 F가 이렇게 나온다고 합니다. (자동화 코드를 만들어서 푸는게 가장 보편적)
F의 MD5 해시값은 800618943025315F869E4E1F09471012
(사이트 https://www.convertstring.com/ko/Hash/MD5 이용)
# CodeEngn Basic 18
Name이 CodeEngn일때 Serial은 무엇인가
Name과 Serial을 입력받고 CodeEngn일 때의 시리얼 값을 찾아야한다.
디버거를 켜서 문자열을 검색해 정답 메시지와 오답 메시지를 출력하는 곳으로 이동해본다.
비교하기 전 주소에 BP를 걸고 Name에 CodeEngn을 입력해 어떤 값과 비교하는지를 살펴본다.
lstrcmpiA 함수를 호출해 문자열을 비교하고 있는데, 첫 번째 인자는 Serial에 임의로 입력한 A가 담겨있는 위치였고 두 번째 인자는 올바른 serial 값으로 이 둘을 비교하고 있다.
Name에 CodeEngn, Serial에 06162370056B6AC0를 입력하고 실행한다.
실행하니 JE를 통해 정답 메시지를 출력하는 주소로 이동했다.
# CodeEngn Basic 19
이 프로그램은 몇 밀리세컨드 후에 종료 되는가
아마도 timeout과 관련된 문제인 것 같다.
올리디버거로 열어주었더니 오랜만에 PUSHAD가 보여서 command 찾기로 POPAD를 찾아주었고, 점프 관련 명령어가 2개가 있어서 살펴보니 JNZ는 004AF38B로 가면서 루프를 돈다. 즉 OEP로 가는 점프명령어는 JMP 00417770이다. 해당 주소에 BP를 걸어주고 F9 -> F8을 누르면 실제 코드로 이동한다.
실제코드에서 딱히 단서가 될만한 것이 없어서 search for -> all intermodular calls 눌러서 우클릭 -> sort by -> destination 으로 함수 별 탐색을 하였다.
방향성을 못잡고 있어서 검색을 해 본 결과 IsDebuggerPresent 함수가 있으면 해당 부분을 NOP으로 채워야 한다고 한다.
위 함수가 존재를 해서 NOP으로 채워주었다.
함수를 내리다 보면 timeGetTime을 찾을 수 있는데, 이 함수는 현재 시간을 불러오는 함수이다. 시간 관련 함수이므로 문제의 답을 구하는 것과 관련이 있을 것이라 생각했다.
timeGetTime 부터 Sleep함수까지 살펴보면,
CALL EDI // EDI 호출 == timeGetTime함수 호출
MOV ESI, EAX // ESI = EAX : ESI에 EAX값 저장
JE 00444D54 // 0이면 00444D54로 JMP
CALL EDI // 다시 timeGetTime 함수 호출
CMP EAX, ESI // EAX - ESI
JNB 00444D38 // **Jump Not Below : 작지 않을 경우 jmp => EAX가 ESI보다 더 크면 JMP
SUB EAX, ESI // EAX = EAX - ESI
DEC EAX // EAX = EAX - 1
JMP 00444D3A // 해당 주소로 JMP
위에서 timeGetTime 함수를 2번 호출하고 있다. 즉 코드를 살펴보면 처음으로 호출한 부분에서 그때의 시각을 ESI에 저장하고, 2번 째 호출을 한 시각을 EAX에 저장을 해서 두 개를 비교를 하는데, 당연히 2번째 때 호출한 EAX가 ESI보다 클 수 밖에 없다. 그래서 JNB로 무조건 이동한다.
여기서 주목해야 하는 것은 JNB이다. EAX > ESI 면 해당 주소로 점프하라는건데 보통 타임아웃은 지정 시간이 현재시간보다 초과될 경우 발생한다. 그럼 EAX가 초과한 경우 해당 주소로 이동하라는 것 같아서 EAX가 경과된 시간이라고 추측했다. 그럼 00444D38주소에 가면 EAX가 어떻게 작동하는지 더 볼 수 있을 것이다.
SUB EAX, ESI // EAX = EAX - ESI
CMP EAX, DWORD PTR DS : [EBX+4] // EAX와 EBX+4주소의 데이터영역의 4바이트 값을 비교
정답으로 가는 KEY이다. 왜냐면 EAX - ESI는 2번째 호출 시각 - 1번째 호출 시각이기 때문이다. 아마 경과 시간이라고 생각된다. 근데 이 경과 시간을 비교하고 있으니 프로그램에서 지정해둔 타임아웃을 시간을 EBX+4주소의 4바이트가 가지고 있을 것이라고 추측된다.
BP를 걸고 실행시켜주면 해당 주소 DS 부분에 2B70이 쓰여져 있는 것을 볼 수 있다. ( 리틀엔디안이라서 HEX DUMP에서 오른쪽부터 읽어주어야 한다.)
2B70을 10진수로 바꾸면 11120이고 이 프로그램은 11120 밀리세컨드 후에 종료된다는 것을 알 수 있다.
ms = 10의 -3승 s이므로 11.12초 이후에 종료된다.
리버싱 이 정도는 알아야지
첫번째, 윈도우의 외부통신 (외부통신 관련 코드)
Windows가 외부와 통신할 때
- Win32 Internet API
- Win32 Socket API
사용, 이 중 Win32 Internet API에 관한 내용을 다룬다.
TCP 통신 방식 사용(양방향 통신)
책에 있는 주소에 들어가면 있음
procexp.exe : MS에서 제공하는 프로세스 모니터링 Tool
~procexp.exe 다운로드 과정~
InternetOpen( ) |
WinINet 초기화 Internet API를 사용하기 위한 준비작업 |
InternetOpenUrl( ) |
URL에 지정된 리소스 열기 가능 해당 함수 두번째 인자인 IpUrlPath에 URL 경로가 들어감 |
InternetReadFile( ) |
서버에서 데이터를 읽어옴 세 번째 인자인 dwSize에 크기 값이 들어감 밑 함수 사용 |
InternetQueryDataAvailable( ) |
우리는 어느 정도의 데이터를 읽어올 수 있는지 모르기 때문에 서버에 사용 가능한 데이터 크기를 먼저 물어봐야 함 이때 사용하는 API, 서버에서 받을 수 있는 데이터 크기가 위 함수의 dwSize로 들어감 |
bResult=InternetReadFile(hUrl, IpBuffer, dwSize, &dwRead); |
url 경로, 여기서는 procexp.exe를 받는 곳 |
파일 데이터 |
|
데이터 크기 값 |
|
다운로드 완료 여부 |
받은 데이터는 temp 경로에 있는 procexp.exe에 채워 넣음
이 작업은 procexp.exe 다운로드가 완료될 때까지 반복됨
다운로드 완료 여부는 dwRead값 (인터넷리드파일의 네번째 인자값) 으로 확인할 수 있음.
여기에는 읽은 데이터를 받을 변수의 포인터가 들어가는데, 만약 다운로드가 끝나거나 Error가 발생할 경우 0이 들어가고, 그러면 이 동작은 종료!
다운로드가 끝나면 핸들 반환
인터넷쿼리데이터어베일러블 함수가 얼마만큼의 데이터를 받을 수 있는지 알려주면 (dwSize) 그 만큼 데이터를 받아서 파일에 담으면 됨
인터넷리드파일의 두번째 인자인 IpBuffer에 파일 데이터가 들어옴
두번째, 내부통신 (Window 제어코드 학습 및 분석)
Windows 운영체제는 다중 실행 환경을 제공한다.
즉, 여러 개의 프로그램 또는 윈도우가 동시에 작업을 수행할 수 있는 것(멀티태스킹)
이런 환경속에서 서로 꼬이지 않고 뭐가 어느 순서에 이루어지는지 상호작용을 해야함
윈도우들 간에 동기화나 데이터 교환을 하기위해 서로를 알아내는 방법이 필요하다는 말!
이 방법을 찾고, 메시지를 전달하는 방법을 알아보는 2번째 파트.
“핸들” 윈도우에 메시지를 전달하고 제어하기위해 필요한 것
이것을 담당하는 함수 FindWindows( ) API
원하는 윈도우의 핸들 값을 얻고, 찾지 못하면 NULL 반환
첫 번째 인자에는 클래스명, 두 번째 인자에는 윈도우 캡션명을 의미함
클래스 : 윈도우에 대한 이름 | 캡션 : 부가적인 설명 또는 꼬리표
둘 다 옵션이어서 필요에 따라 넣거나 NULL값을 줄 수 있음
그렇지만 값을 모두 넣어주면 검색의 정확도는 올라간다.
FindWindowsEx( )는 더 추가된 기능을 제공, 종속된 윈도우도 찾을 수 있다.
이거는 4개의 인자
첫째 최상위 윈도우 핸들
두 번째는 동일한 레벨의 윈도우 중에서 검색 시점을 결정하는 요소
마지막 두개는 FindWindow( )인자와 동일
윈도우 클래스 이름이나 캡션명(정보)은 우리가 알 수 없는데, 이때 Spy++라는 도구를 사용한다. 비주얼 스튜디오에 있음
윈도우에 대한 다양한 정보 및 실시간으로 주고받는 메시지를 확인할 수 있음 (버프스위트, 와이어샤크 같은 늑김..)
윈도우에 URL 주소랑 Enter를 입력한 것과 같은 효과를 줄 수 있다
이는, 원격으로 PC를 조종할 수 있다는 것을 시사한다.
SPY++로 타깃 윈도우의 실제 주소 입력 윈도우 정보를 알아낸다.
여기서는 실제와 차이가 날 수 있으므로 주의한다. (책에서는 Spy++는 ToolbarWindow32라고 인식했지만 실제로는 Edit 이었음)
Windows에서 기본으로 설치되는 소프트웨어이기에 인터넷익스플로러는 설치 경로가 정해져 있다.
이 점을 안다면 무난하게 시작 경로를 설정할 수 있고
이제 자동으로 이동시킬 URL주소를 입력해야함
이 작업은 FindWindow( )와 FindWindowEx( ) API를 사용해서 인터넷익스플로러경로의 Edit 윈도우 핸들을 얻어서 한다.
Edit 윈도우에 URL을 입력 (SendMessage( ) 사용해서 윈도우에 메시지 보낼 수 있음)
Enter이벤트가 발생하도록 VK_RETURN 코드를 전달 (가상키 코드)
결과 : 특정 웹 페이지로 접속하게 됨. >> 책에서는 네이버 홈페이지였지만, 만약 해커가 악의적인 목적으로 접속만 해도 해를 입힐 수 있는 페이지로도 이동이 가능하다는 것을 시사.
공격 시나리오를 구상할 수 있는 장이었다.
TEB&PEB Windows에서의 API 직접 호출 방식
직접호출과 간접호출
- 호출 주소 정보를 IAT에 기록해놓고 참조하는지 직접 알아내는지의 차이
1. 간접 호출
실행 파일의 Import Address Table에 기록되어 있는 API 주소 정보를 참조해서 호출하는 방식
CALL DWORD PTR DS:[0x00405020] : 0x00405020 주소에 있는 4바이트를 호출
0x0040520 주소? Import Address Table이고, GetVersion() API 주소가 기록
Import Address Table
2. 직접 호출
EAT = 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 메커니즘
IAT = 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블
① kernel32.dll이 로드되어 있는 주소를 알아낸다. (Imagebase)
② Imagebase를 기준으로 +0x0000008D 주소에는 EXPORT TABLE의 RVA가 기록되어 있다.
③ EXPORT Table로 이동해서 해당 API의 호출 주소를 얻는다. 호출한다.
3.2 TEB(Thread Environment Block)
프로세스에서 실행되는 스레드에 대한 정보를 담고 있는 구조체
TIB(Thread Information Block)라고도 하며 스레드별로 하나씩 할당된다.
NtTib, ProcessEnvironmentBlock
* NtTib
TEB 구조체의 첫 번째 멤버로 NT_TIB 구조체
Self 멤버는 시작 주소인 NT_TIB 구조체의 셀프 포인터이며, TEB 구조체 포인터이기도 하다.
FS:[0x18]
* ProcessEnvironmentBlock
0x30에 위치한 멤버로, PEB 구조체의 위치 정보가 있다.
[요약]
3.3 PEB(Process Environment Block)
프로세스의 정보를 담고 있는 구조체, 운영체제 버전마다 조금씩 다르다.
PEB 구조체에서 Ldr 멤버는 PEB_LDR_DATA 구조체의 위치 정보를 담고 있다. 해당 구조체에는 프로세스에 로딩된 모듈을 찾아가는 과정에서 필요한 정보가 기록되어 있다.
3.4 PEB_LDR_DATA
InMemoryOrderModuleList 멤버에 LDR_DATA_TABLE_ENTRY 구조체의 위치 정보가 기록되어 있다.
양방향 연결리스트 메커니즘! *Blink 멤버 값으로 LDR_DATA_TABLE_ENTRY 구조체를 찾을 수 있다.
3.5 LDR_DATA_TABLE_ENTRY
프로세스에 로딩된 모듈에 대한 정보
6장. 실전 분석 | 어셈블리로 제작된 악성파일
Challenge 03.exe: 어셈블리로 작성된 Backdoor 파일
wininet.dll 로드 -> C&C 서버 연결 시도(93.174.95.82)-> 자신의 메모리 공간 할당
-> 할당 받은 메모리 영역에 데이터 다운로드 -> 데이터 코드 실행
2.1 Challenge 03.exe 동작 방식
‘CALL EBP’ : 각각의 호출이 무엇을 의미하는지 파악하기 어려움! -> API 직접 호출 방식 사용중
** API 직접 호출: 라이브러리에서 제공하는 API의 호출 주소를 직접 알아내고 호출하는 방식
2.2 인터넷 연결 및 정보 전송
Challenge 03.exe -> 공격자 서버와 통신을 시도, 인터넷 관련 API를 쓰기 위해 wininet.dll 로드
C&C 서버(93.174.95.82)가 닫혀 있기 때문에 HttpSendRequest() API 호출을 실패(리턴 0)
-> EAX(리턴 값)를 1로 수정하고 계속 분석
2.3 메모리 할당 및 데이터 다운로드
① VirtualAlloc() API를 사용해서 데이터 저장 공간을 할당
② InternetReadFile() API를 호출해서 서버로부터 데이터를 읽어옴
③ 데이터 다운로드가 끝나면 할당된 메모리 주소로 이동
④ 데이터가 실행 (서버와 통신X -> NULL값으로 채워져 있으며 동작하지 않음)
3. API 직접 호출 과정(LoadLibraryA())
<직접 호출 과정>
PEB 접근 -> PEB LDR Data -> LDR Module -> PE Header 파싱 ->
-> 모듈의 Export Table 접근(반복) <-> 모듈 내 API 탐색(반복) <-> 원하는 API 실행(반복) JMP EAX
[LoadLibrary() API 호출 코드]
00401090 | PUSH 74656E //찾고자 하는 API에 대한 해시 정보
00401095 | PUSH 696E6977
0040109A | PUSH EBP
0040109B | PUSH 726774C
004010A0 | CALL EBP | Challeng.00401006
@[0x00401006 주소 코드]
-> Challenge 03.exe는 자신이 원하는 API 호출 주소를 알아내기 위해 해시 값을 사용
Hash_01(Target API를 Export하는 Module 이름 문자열) + Hash_02(Target API 문자열)
Challenge 03.exe에 로드되어 있는 모든 Module의 Export API 해시 값을 구한 뒤에 동일한 값을 찾을 때까지 반복
3.1 모듈 이름 문자열 해시 값 계산
TEB와 PEB 구조체 정보를 활용해서 Module에 대한 이름 문자열을 얻는다.
PEB = TEB->ProcessEnvironmentBlock //PEB 시작 주소 획득
_PEB_LDR_DATA = PEB->Ldr //PEB LDR Data 시작 주소 획득
LDR Module =_PEB_LDR_DATA->InMemoryOrderModuleList //LDR Module 시작 주소 획득
ModuleName = LDR Module->FullDllName.Buffer //Module 이름 문자열 획득
ModuleNameLength = LDR Module->FullDllName.MaximumLength //문자열 길이 획득
마지막에 구한 Module 이름 문자열과 문자열 길이 값을 가지고 Hash 값으로 변환한다.
3.2 API 이름 문자열 해시 값 계산
함수 이름 문자열을 Hash로 변환해야한다.
-> Export 함수 Table에 대한 접근이 필요
<Module이 메모리에 로드되어 있는 주소를 기준으로 Export 함수 Table을 찾아가는 과정>
1. PE Header에서 Export Table의 RVA 값을 구한다.
2. 1에서 구한 주소 값과 Module이 로드되어 있는 주소를 더하면 Export Table의 실제 위치.
해당 주소 기준
0x18 : NumberOfNames
0x20 : AddressOfNames
API 문자열 정보가 기록된 Table 주소를 알아내고 이 값에 ‘NumberOfNames*4’를 더해서 Table의 끝으로 이동 -> 검색 대상 Module이 Export하는 API들 중에서 마지막부터 역순으로 검색함
이 때, API 이름 문자열을 Hash로 변환하고 Module 이름 해시 값을 더한 뒤에 원하는 API 해시가 맞는지 확인한다.
3.3 API 직접 호출
해시 값이 일치할 경우 원하는 API를 찾았다고 보고 API 주소 정보를 검색한다.
Ordinal Table -> API의 Ordinal 값을 구함 (Address Table에서 실제 API 주소 정보를 얻는데 사용)
* ordinal table -> ordinal: 순서수, (인덱스 .. ??)
API 호출 주소를 획득하면 다음과 같이 해당 API 주소로 이동하게 된다.
'2. Reversing (리버싱) > 1) Write UP' 카테고리의 다른 글
[2021.05.22] CodeEngn Advanced / xcz.kr & 교재 7장 (0) | 2021.05.22 |
---|---|
[2021.05.15] reversing.kr , suninatas, xcz & 교재 6장(dll), 8장 (0) | 2021.05.15 |
[2021.05.01] CodeEngn 11-15 & 교재 4장 (0) | 2021.05.01 |
[2021.03.27] CodeEngn Basic L08~L10 & 교재 3장 (0) | 2021.03.27 |
[2021.03.20] CodeEngn Basic L05-L07 & 교재 Chapter2 발표 (0) | 2021.03.20 |