新手學習Vmp之控制流程圖生成
控制流程圖的生成對于反混淆分析來說是非常重要的一步,這里記錄一下我研究的過程,以Vmp2為例子。
這里我的環境準備如下:
Visual Studio + IDA SDK + Capstone + Unicorn + Graphviz
IDA SDK插件環境,主要是有一些API可以調用,方便編寫代碼,X64Dbg插件環境可以替代之。
Capstone,一個很不錯的反匯編引擎,IDA自帶的反匯編引擎不太好用,用這個替代之。
Unicorn,指令模擬執行,用來跟蹤指令。
Graphviz,一個繪圖工具,可以將控制流程圖可視化。
要生成流程圖,首先使用unicron引擎對指令進行跟蹤,大致步驟如下:
1、使用uc_mem_map和uc_mem_write函數填充內存區域和堆棧
2、uc_hook_add設置監視函數,每次執行指令前檢查退出條件,例如當前指令位于text區段且上一條指令是ret的時候,基本上就是vmp結束的時候了。
3、uc_emu_start進行trace,拿到所有的指令跟蹤數組。
之后是根據這些地址動態生成控制流程圖,這里需要了解一下基本塊這個概念。
核心邏輯如下:
bool VmpTraceFlowGraph::GenerateBasicFlowData(std::vector<ea_t>& traceList)
{
if (!traceList.size()) {
return false;
}
cs_insn* curIns;
VmpTraceFlowNode* currentNode = createNode(traceList[0]);;
for (unsigned int n = 0; n < traceList.size(); ++n) {
const ea_t& curAddr = traceList[n];
if (!DisasmManager::DecodeInstruction(curAddr, curIns)) {
return false;
}
//不管是什么指令,都立即追加到當前基本塊
if (!currentNode->bTraced) {
currentNode->addrList.push_back(curAddr);
updateInstructionToBlockMap(curAddr, currentNode);
}
//判斷是否為終止指令
if (isEndIns(curIns)) {
//檢查是否為最后一條指令
if (n + 1 >= traceList.size()) {
break;
}
currentNode->bTraced = true;
//這里開始進行核心判斷
ea_t nextNodeAddr = traceList[n + 1];
VmpTraceFlowNode* nextNode = instructionToBlockMap[nextNodeAddr];
linkEdge(curAddr, nextNodeAddr);
//下一個節點是新節點
if (!nextNode) {
currentNode = createNode(nextNodeAddr);
}
//已訪問過該節點,且節點指向Block頭部
else if (nextNode->nodeEntry == nextNodeAddr) {
currentNode = nextNode;
}
else {
//節點指向已有區塊其它地址,需要對區塊進行分割
currentNode = splitBlock(nextNode, nextNodeAddr);
}
}
}
return true;
}
再進行節點合并優化,核心代碼是這樣的:
void VmpTraceFlowGraph::MergeNodes()
{
//已確定無法合并的節點
std::set<ea_t> badNodeList;
bool bUpdateNode;
do
{
bUpdateNode = false;
std::map<ea_t, VmpTraceFlowNode>::iterator it = nodeMap.begin();
while (it != nodeMap.end()) {
ea_t nodeAddr = it->first;
if (badNodeList.count(nodeAddr)) {
it++;
continue;
}
//判斷合并條件
//條件1,指向子節點的邊只有1條
if (toEdges[nodeAddr].size() == 1) {
ea_t fromAddr = *toEdges[nodeAddr].begin();
VmpTraceFlowNode* fatherNode = instructionToBlockMap[fromAddr];
//條件2,父節點指向的邊也只有1條
if (fromEdges[fromAddr].size() == 1) {
//條件3,子節點不能指向父節點
if (!fromEdges[nodeAddr].count(fatherNode->addrList[fatherNode->addrList.size() - 1])) {
executeMerge(fatherNode, &it->second);
bUpdateNode = true;
it = nodeMap.erase(it);
continue;
}
}
}
badNodeList.insert(nodeAddr);
it++;
}
} while (bUpdateNode);
}
最后是將流程圖轉換成dot語言,核心代碼如下:
std::string VmpTraceFlowGraph::DumpGraph()
{
std::stringstream ss;
ss << "strict digraph \"hello world\"{\n";
cs_insn* tmpIns = 0x0;
char addrBuffer[0x10];
for (std::map<ea_t, VmpTraceFlowNode>::iterator it = nodeMap.begin(); it != nodeMap.end(); ++it) {
VmpTraceFlowNode& node = it->second;
sprintf_s(addrBuffer, sizeof(addrBuffer), "%08X", it->first);
ss << "\"" << addrBuffer << "\"[label=\"";
for (unsigned int n = 0; n < node.addrList.size(); ++n) {
//測試代碼
if (n > 20 && (n != node.addrList.size() - 1)) {
continue;
}
DisasmManager::DecodeInstruction(node.addrList[n], tmpIns);
sprintf_s(addrBuffer, sizeof(addrBuffer), "%08X", node.addrList[n]);
ss << addrBuffer << "\t" << tmpIns->mnemonic << " " << tmpIns->op_str << "\\n";
}
ss << "\"];\n";
}
for(std::map<ea_t, std::unordered_set<ea_t>>::iterator it = fromEdges.begin(); it != fromEdges.end(); ++it){
std::unordered_set<ea_t>& edgeList = it->second;
for (std::unordered_set<ea_t>::iterator edegIt = edgeList.begin(); edegIt != edgeList.end(); ++edegIt) {
VmpTraceFlowNode* fromBlock = instructionToBlockMap[it->first];
sprintf_s(addrBuffer, sizeof(addrBuffer), "%08X", fromBlock->nodeEntry);
ss << "\"" << addrBuffer << "\" -> ";
sprintf_s(addrBuffer, sizeof(addrBuffer), "%08X", *edegIt);
ss << "\"" << addrBuffer << "\";\n";
}
}
ss << "\n}";
return ss.str();
}
得到文件后,調用dot命令行打印出流程圖
dot graph.txt -T png -o vmp2.png
最后得到的結果是這樣的
