| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- 백엔드
- 위상 정렬
- Practical Malware Analysis Labs
- React
- 루키즈31기
- 루키즈
- sk 쉴더스 루키즈
- 예술의 전당
- 프리코스
- 웹개발
- sk 쉴더스 루키즈 31기
- 우아한테크코스
- 깃
- 악성코드 분석
- c
- 자바
- 진입차수
- 코리안챔버오케스트라
- 레나튜토리얼
- 알고리즘
- 서울청년문화패스
- linux
- 우테코
- 프랑스어 #프랑스어배우기 #프랑스어독학 #델프인강 #시원스쿨프랑스어 #delf독학 #델프 #프랑스어기초 #프랑스어공부
- 루키즈 31기
- webhacking
- Dreamhack
- SK쉴더스루키즈
- 동적분석
- sk쉴더스 루키즈
- Today
- Total
yon11b
[SK 쉴더스 루키즈] Practical Malware Analysis Lab 07-01 풀이 본문
1. 이 프로그램은 어떤 방식으로 컴퓨터가 재시작할 때마다 실행(지속매커니즘)을 보장하는가?

createserviceA로 Malservice를 services에 자동실행되게 등록함으로써 보장한다.
함수 인자 몇 가지만 보고 가자.
lpBinaryPathName
path 쪽에 bp를 건 다음 실행해서 path를 확인해보면,

BinaryPathName = "C:\Documents and Settings\Windows\Desktop\malware_analysis_samples\Practical Malware Analysis Labs\BinaryCollection\Chapter_7L\Lab07_01.exe"
.exe 파일 경로가 제대로 들어간 것을 확인할 수 있다.
이제 이 경로가 services에 등록이 된 것이다. 직접 확인해보면 다음과 같다.

automatic으로 되어 있기 때문에, 재부팅을 할 때마다 저 .exe파일이 실행될 것이다.
dwStartType
dwStartType==2이면 재부팅 시 자동실행이다.

2. 이 프로그램은 왜 뮤텍스를 이용하고 있는가?
컴퓨터가 재시작할 때마다 실행하는데 중복 실행하지 않기 위해서.
3. 이 프로그램을 탐지할 때 호스트 기반으로 좋은 시그니처는 무엇인가?
1. 뮤텍스: HGL345

2. 악성파일: Malservice
4. 이 악성코드를 탐지할 때 네트워크 기반으로 좋은 시그니처는 무엇인가?
http://www.malwareanalysisbook.com
정적분석을 통해서 wininet.dll을 사용하고 있음을 알아냈다. 즉, 네트워크 통신이 있다는 것이다.

ida에서 http를 입력하면 외부 url이 검색된다.

두 번 클릭한 다음 ctrl+x를 눌러서 따라가 보자.

url을 InternetOpenUrlA로 실행한다.
근데 그러고 바로 또 jmp 해서 위로 올라가 해당 코드를 반복한다.
즉, 무한 반복하는 것이다. → DoS 공격일 가능성!
5. 이 프로그램의 목적은 무엇인가?
DDoS 공격
6. 이 프로그램은 언제 실행을 종료하는가?
답: 종료를 안 함
InternetOpenUrlA 쪽에서 그래프를 그려보면 startaddress가 있다.
StartAddress란?
스레드가 시작할 함수의 주소를 의미한다.
CreateThread()의 3번째 인자인 lpStartAddress에 들어간다. CreateThread 메인 쓰레드 하나, StartAddress 서브 쓰레드 하나 또 실행 되는 것이므로 멀티 쓰레드라고 할 수 있다.

상위 코드로 가보자.
맨 첫번째 줄에 커서를 두고 ctrl+x를 눌러준다.

이 화면이 나온다. CreateThread를 0x14번 반복한다.

mov esi, 14h> esi에 0x14h 할당
dec esi > esi를 하나씩 감소시키면서 반복문 돎
중간에 보이는 lpStartAddress로 인해 아까 위에서 봤던 StartAddress 함수가 실행되어 URL 무한반복 호출 DoS 공격이 일어나게 되는 것이다.
그 밑에 코드를 보면, FFFF FFFF 만큼 sleep을 한다. 이건 메인 스레드를 무한 sleep 상태로 둔다는 말이다.

즉, 저 위치에서 Sleep 하는 건 메인 스레드이고, 이미 생성된 20개 스레드는 각자 StartAddress 안의 코드를 계속 실행한다.
정리
무한 sleep하는 이유?
⇒ 프로세스 종료 방지+작업 스레드들이 계속 동작하게 유지하기 위해서
치명적인 버그: 타이머 설정부터 무한 sleep까지 흐름 살펴보기

설명

SystemTime 구조체 값 할당
xor edx, edx =⇒ edx = 0
2100년 0월 0일 00:00:00
SystemTime -> FileTime 변환: SystemTimeToFileTime
SystemTimeToFileTime(&SystemTime, &DueTime);
SetWaitableTimer에서 시간을 FILETIME 형식으로 받기 때문에 변환하는 것이다.
SYSTEMTIME
2100-01-01 00:00:00
↓
FILETIME
Windows 내부 시간 형식
Waitable Timer 생성: CreateWaitableTimerA
hTimer = CreateWaitableTimerA(NULL, FALSE, NULL);
이름 없는 Waitable Timer 객체 생성
자동 리셋 타이머
보안 속성 기본값
이 함수의 반환값은 eax에 들어간다. (밑에서 mov esi, eax 로 eax 쓰임)
타이머 설정: 지정된 시간까지 기다리는 용도: SetWaitableTimer
push 0 ; fResume
push 0 ; lpArgToCompletionRoutine
push 0 ; pfnCompletionRoutine
lea edx, [esp+410h+DueTime]
push 0 ; lPeriod
**push edx ; lpDueTime**
push esi ; hTimer
call ds:SetWaitableTimer
- lPeriod 0 -> 반복 주기 없음
- lpDueTime까지 기다림
lpDueTime은 SystemTimeToFileTime의 출력값이다. 따라서 lpDueTime=2100년 0월 0일 00:00:00이 된다.
hHandle(hTimer)가 신호상태가 될 때까지 무한 대기: WaitForSingleObject
push 0FFFFFFFFh ; dwMilliseconds
push esi ; hHandle
call ds:WaitForSingleObject
타이머가 신호 상태가 될 때까지 무한 대기(2100년이 되기 전까지는 아무 실행도 안 하고 그냥 잠잠히 대기만 타고 있음)
⇒ 조건이 충족되면 깨어남
++제일 마지막 코드(아까 봤던 부분) - 무한 sleep

만약 2100년이 되었다면, 위의 조건이 충족되어 깨어나고, 다음 코드(지금 여기)를 실행하게 된다.
여기서는 쓰레드를 0x14개(20개) 생성하고 무한 sleep을 걸게 된다.
이 무한 sleep은 탈출 조건도 없어서 진짜 영원히 thread가 돌아가게 된다.
SYSTEMTIME 문서
지금 SystemTime 구조체 값이 2100년 0월 0일 00:00:00으로 할당되어 있다.
그런데 SystemTime 구조체에 대한 msdn 문서를 살펴보면 month값과 day은 1부터 시작한다고 나와있다.



SystemTime 구조체가 잘못된 날짜를 나타내고 있는 것이다.
SystemTimeToFileTime 문서
SystemTime → FileTime을 변환을 위해 사용되는 SystemTimeToFileTime 함수 문서도 살펴보면, SystemTime이 잘못된 날짜를 나타내면 반환값으로 False 를 전달한다고 나와있다.


그런데! 코드를 보면 SystemTimeToFileTime 함수 반환값에 대한 검사(test eax, eax)를 하지 않고 있다.
그래서 잘못된 값이 그대로 SetWaitableTimer로 넘어가게 된다.
SetWaitableTimer 문서

사실 여기서는 잘못된 날짜가 들어왔을 때 어떻게 반응하는지에 대한 내용은 나와있지는 않다.
그럼 그냥 실행을 시켜보자. 어떤 문제가 일어날까?
답부터: CreateThread 부분이 바로 실행이 된다!
SystemTimeToFileTime
SystemTimeToFileTime에서 일단 systemtime → filetime 값이 어떻게 나오는지 보자.

systemtime(0x0012F868)은 2100년 0월 0일 0시 0분 0초로 나오고 (34 08 ==2100년이다.)
filetime(0x0012F878)은 0년 0월 0일 0시 0분 0초로 나온다.
systemtime자체가 잘못된 값이니까 filetime에도 제대로 값이 잘 안 들어간 것이다.
SetWaitableTimer

arg2는 lpDueTime을 나타낸다. 그 주소로 가보면 여전히(당연히) 0년 0월 0일 0시 0분 0초 값이 저장되어 있다.
BOOL SetWaitableTimer(
[in] HANDLE hTimer,
[in] const LARGE_INTEGER *lpDueTime,
[in] LONG lPeriod,
[in, optional] PTIMERAPCROUTINE pfnCompletionRoutine,
[in, optional] LPVOID lpArgToCompletionRoutine,
[in] BOOL fResume
);
그리고 쭉 내려가보면 createThread 밑으로 내려가진다. 즉, createThread가 실행이 된다는 것이다.

JNZ에서 다시 PUSH 0으로 올라와 반복문을 실행한다.


1월 1일로 바꿔서 실행하면?
0월 0일

1월 1일

갑자기 F878값도 바뀌었다. 우리는 이미 이 주소가 pFileTime 주소인걸 위에서 봤다.

pFileTime는 pSystemTime이랑 다르게 1601-01-01 00:00:00 UTC부터 흐른 100나노초 단위 카운터 값을 메모리에 나타낸다. 1601-01-01 부터0x022F716377640000나노초 만큼 흐른 날짜가 언제인지를 계산하는 파이썬 코드는 다음과 같다.
from datetime import datetime, timedelta, timezone
filetime = 0x022F716377640000
epoch = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = epoch + timedelta(microseconds=filetime // 10)
print(dt.strftime("%Y-%m-%d %H:%M:%S UTC"))
결과: 2100-01-01 00:00:00 UTC

잘 변환되어 들어간 것을 확인할 수 있다.
그리고 계속 내려가서 WaitForSingleObject까지 가면 이 이상으로는 더 내려갈 수가 없다.

2100년이 될 때까지 계속 wait timer가 돌고 있는 것이다.
(이해에 도움을 주신 복습방 스터디 멤버 조**와 김**님 감사합니다^^)
