Windows程序的调试器有很多,比如windbg、ollydbg 等等,但是大多数搞逆向的人都喜欢使用ollydbg来动态调试程序,因为它的快捷键丰富,众多的插件,以及稳定的调试功能,但是还有一个最为重要的原因就是它的界面非常友好,不同的指令显示不同的颜色,能一眼很容易区分出来,不伤眼,所以打造自己的调试器界面也是最重要的一部分,下面我们就来讲解ollydbg 的界面如何实现。
首选我们要了解ollydbg的界面有哪些有点,如下图所示:
使用ollydbg的人都知道它的界面的一些特性:
1. 数据显示区域的任何窗体都是自绘的。
2. 移动窗体时界面不会闪动。
3. 显示汇编的时候颜色是不同的。
4. CPU数据窗体分割可使用鼠标左键互相连接移动。
接下来我们就具体分析它是如何实现的,用IDA打开Ollydbg,首选找到 RegisterClass函数来定位CPU窗体的类所对应的窗体函数WndProc的位置:
Ollydbg的CPU汇编显示的类叫ICPUASM, 它对应的窗口回调例程函数就是如图的AsmListView(我标注的函数名字),下面我们就进入这个函数去找一些关键的信息
函数内处理了一些消息,开始的一些消息并没有什么特殊性,继续往下找
看到了WM_PAINT消息的处理,这里我应该能猜测出来这里就是开始自绘客户Client区域了,继续进入Painttable函数看看是否是我们需要的,当我们一进入这个函数就发现了它的一些重要的与界面自绘相关的信息
它使用了CreateCompatibleDc、CreateCompatibleBitmap、 Bitblt双缓冲技术来实现绘图防止界面闪动。
第一步是打印Column的头header,首选在头的大小区域的底部大一条黑色的线
接着就是根据column的数量循环画每个column, 规定最大column数量为17个,先画每个 column的边线,然后在填充背景色,最后在打印column的字体
Listview头自绘完毕后就是开始画界面体了,下面这一大块划线打点的代码区域就是实现Hex 数据栏不同的跳转线的实现
接下来就是根据不同的结构数据的设置打印不同的区域内不同颜色的字体
如果这个区域内没有数据结构就直接填充默认背景颜色
关于它的界面的实现的一些原理其他细节我们只介绍到这里其他的细节读者自行去发现,下面最重要的工作就是自己去写代码实现,在实现过程中有几个关键的技术。
1. 防止界面闪动使用双缓冲绘图
在WM_PAINT里使用几个兼容位图的的函数实现双缓冲
hCompatibleDC = CreateCompatibleDC(hDC); if (hCompatibleDC) { hCompatibleBitMap = CreateCompatibleBitmap( //创建兼容位图 hDC,prcDirty->right,prcDirty->bottom); } else { hCompatibleDC = hDC; } if (hCompatibleBitMap) { hOldBitmap = (HBITMAP)SelectObject(hCompatibleDC,hCompatibleBitMap); }。。。。。。。。。。。。。 if (hCompatibleBitMap) { BitBlt( hDC, rcDirtyBody.left, rcDirtyBody.top, rcDirtyBody.right - rcDirtyBody.left, rcDirtyBody.bottom - rcDirtyBody.top, hCompatibleDC, HEADER_CX, nHeaderHeight, SRCCOPY); } } if (hCompatibleBitMap) { SelectObject(hCompatibleDC,hOldBitmap); DeleteObject(hCompatibleBitMap); DeleteDC(hCompatibleDC); }
2. 鼠标左键按下时使用SetCapture设置鼠标操控区域
使用SetCapture的好处就是当前整个界面区域都是属于当前这个句柄窗体的界面区域,但是有一点必须注意的是在鼠标左键释放时候一定要调用ReleaseCapture 去释放这个操作区域,同时在移动的分割线时同时要改变当前线的位置,使用InvalidateRect函数去刷新WM_PAIN消息重绘界面与线 代码如下
HRESULT CCustomView::CustomLButtonDown( WPARAMwp,LPARAMlp)
{
RECT Rect;
POINT MousePt;
BOOL bIsRoomed;
int PosHorz;
int ColMax;
int PrePosX;
ULONG MouseClickItem;
GetCursorPos(&MousePt);
ScreenToClient(m_hWnd, &MousePt);
GetClientRect(m_hWnd, &Rect);
SetCapture(m_hWnd);
//KillTimer(
m_hWnd,MOUSE_TIMER_MOVE_SELECT);
MouseClickItem= 0;
PrePosX = 0;
bIsRoomed = IsZoomed(m_hWnd);
PosHorz = GetScrollPos(m_hWnd,SB_HORZ);
ColMax = m_CustomHeader.GetMaxColumn();
if ( m_ParentSelectView != -1 && m_ParentSelectView != m_ViewNameFlags)
{
HWND hParent =GetParent(m_hWnd);
::SendMessage(hParent,WM_CPU_VIEW_LBUTTON,0×999,m_ViewNameFlags);
}
if ( Rect.top <=MousePt.y&&Rect.bottom >= MousePt.y&& Rect.left <= MousePt.x && Rect.right >= MousePt.x )
{
for (intcol = 0; col < ColMax; col++)
{
RECT rc = {0};
rc.left = HEADER_CX + PrePosX – PosHorz;
rc.right = rc.left + m_CustomHeader.GetColumnWidth(col);
PrePosX += m_CustomHeader.GetColumnWidth(col);
if (rc.left == MousePt.x | ( rc.left – 1) == MousePt.x )
{
m_bTrackLine = col;
m_ptTrackPos.x = MousePt.x;
m_ptTrackPos.y =MousePt.y;
HCURSOR hCur = LoadCursor(0,IDC_SIZEWE);
SetCursor(hCur);
break;
}
}
if ( m_bTrackLine < 0 )
{
int RowMin = 0;
int HeaderHeight = 0;
int ItemHeight = 0;
int RowMax = 0;
int PosHorz,PosVert;
int PosCursor = MousePt.y;
ULONG OldSelectItem = m_ViewPags.SelectMinPageItem;
PosHorz = GetScrollPos(m_hWnd, SB_HORZ);
PosVert = GetScrollPos(m_hWnd, SB_VERT);
HeaderHeight = m_CustomHeader.GetColumnHeight();
ItemHeight =m_CustomItem.GetItemHeigth();
if (ItemHeight)
{
//RowMin= Rect.top – nHeaderHeight + PosVert – (nItemHeight – 1);
RowMin = PosVert;
RowMin /= ItemHeight;
RowMax = (Rect.bottom- HeaderHeight + PosVert);
RowMax /= ItemHeight;
if( RowMax >= RowMin&& PosCursor > HeaderHeight )
{
RECT rcFlush;
PosCursor -= HeaderHeight;
PosCursor/= ItemHeight;
PosCursor+= RowMin;
rcFlush.left = HEADER_CX;
rcFlush.right = Rect.right – HEADER_CX;
if(PosCursor >= 0 && PosCursor < m_rowMax)
{
m_ViewPags.SelectMinPageItem =PosCursor;
m_ViewPags.SelectMaxPageItem = -1;
m_bLButtonDown= TRUE;
if(OldSelectItem >= 0)
{
rcFlush.left= MAX(Rect.left,HEADER_CX);
rcFlush.top = MAX(Rect.top, HeaderHeight);
rcFlush.right= Rect.right;
rcFlush.bottom = Rect.bottom;
::InvalidateRect(m_hWnd,&rcFlush,TRUE);
}
rcFlush.top = HeaderHeight+ PosCursor * ItemHeight- PosVert;
rcFlush.bottom = rcFlush.top + ItemHeight;
::InvalidateRect(m_hWnd,&rcFlush,TRUE);
}
}
}
}
}
return 0;
}
3. 处理子窗体的WM_ ERASEBKGND 消息
处理这个消息的目的是为了防止在移动父窗体的边界的时候,防止子窗体的背景被重新绘制,处理方式很简单,在子窗体的WM_ ERASEBKGND消息时直接返回,不经过默认处理。
case WM_ERASEBKGND:
{
return0;
}
4. 绘制不同的区域绘制不同颜色彩色文字
void CDisasmView::PaintAsmColumn( HDC hDC, PCustomItemHeadCustomItem, RECT rcColumn, int nColumn, BOOL bSelect){ if (!CustomItem) { return; } TCHAR* lpszText = NULL; if (nColumn == 0) { lpszText= CustomItem->lpAddress; SetBkMode(hDC, TRANSPARENT); if (bSelect) { if (m_ParentSelectView != m_ViewNameFlags) { SetTextColor(hDC,CustomItem-> NoneFocusFontColor); } else { SetTextColor(hDC,CustomItem-> SelFontColor); } FillRect(hDC, &rcColumn, (HBRUSH) CustomItem->SelBgkBrush); } else { SetTextColor(hDC,CustomItem-> NorFontColor); FillRect(hDC, &rcColumn, (HBRUSH) CustomItem->NorBgkBrush); } if (lpszText != NULL) { DrawText( hDC, lpszText, -1, &rcColumn, DT_EXPANDTABS| DT_VCENTER| DT_SINGLELINE); } } else if( nColumn < CUSTOM_MAX_COLUMN ) { if (bSelect) { FillRect(hDC, &rcColumn, (HBRUSH) CustomItem->ColumnData[nColumn -1].SelBgkBrush); } else { FillRect(hDC, &rcColumn, (HBRUSH) CustomItem->ColumnData[nColumn -1].NorBgkBrush); } RECTrcIcon1; rcIcon1.left = rcColumn.left; rcIcon1.top = rcColumn.top; rcIcon1.bottom = rcColumn.bottom; rcIcon1.right = rcColumn.left; if (CustomItem->ColumnData[nColumn - 1].Icon&& CustomItem->ColumnData[nColumn -1].IconCount ) { for( int nIndex =0 ; nIndex< CustomItem->ColumnData[ nColumn - 1].IconCount; nIndex++) { rcIcon1.right += m_FontWe; if(CustomItem->ColumnData[ nColumn - 1].Icon[nIndex].nIcon == 0) { SetTextColor(hDC, CustomItem->ColumnData[nColumn -1].Icon[nIndex].colorIcon); //SetBkMode(hDC,TRANSPARENT); SelectObject(hDC, m_CustomPen[kCustomBlackPen]); MoveToEx(hDC, rcIcon1.left + m_FontWe/2,rcIcon1.top+ m_FontHe / 2, 0); LineTo(hDC, rcIcon1.left + m_FontWe/2,rcIcon1.top+ m_FontHe / 2 + 2); MoveToEx(hDC, rcIcon1.left + m_FontWe/2+ 1, rcIcon1.top+ m_FontHe / 2, 0); LineTo(hDC, rcIcon1.left + m_FontWe/2+ 1, rcIcon1.top+ m_FontHe / 2 + 2); } rcIcon1.left = rcIcon1.right; } } rcIcon1.right += m_FontWe/2; if (CustomItem->ColumnData[nColumn - 1].Option ) { for(int i = 0; i < CustomItem->ColumnData[nColumn -1].OptionCount ; i++) { lpszText= CustomItem->ColumnData[ nColumn - 1].Option[i].lpText; intrcWidth = m_FontWe* CustomItem->ColumnData[nColumn - 1].Option[i].TextSize; if(CustomItem->ColumnData[ nColumn - 1].OptionCount> 1) { intkkk = 0; kkk++; } if(!bSelect) { SetTextColor(hDC, CustomItem->ColumnData[nColumn -1].Option[i].NorFontColor); } else { SetTextColor(hDC, CustomItem->ColumnData[nColumn -1].Option[i].SelFontColor); } if( m_ViewNameFlags != m_ParentSelectView) { SetTextColor(hDC, CustomItem->ColumnData[nColumn -1].Option[i].HasNonFocus); } if(CustomItem->ColumnData[ nColumn - 1].Option[i].FontBgColor == -1) { SetBkMode(hDC, TRANSPARENT); } else { SetBkMode(hDC,OPAQUE); SetBkColor(hDC, CustomItem->ColumnData[nColumn -1].Option[i].FontBgColor); } if(lpszText != NULL) { RECTrcText(rcColumn); rcText.left = rcIcon1.right; rcText.right = rcText.left + rcWidth; rcIcon1.right = rcText.right; if( i == 0) { rcIcon1.right += m_FontWe; } DrawText( hDC, lpszText, -1, &rcText, DT_EXPANDTABS | DT_LEFT | DT_SINGLELINE); } } } else { if (bSelect) { FillRect(hDC, &rcColumn, (HBRUSH)m_hNormalBrushB); } else { FillRect(hDC, &rcColumn, (HBRUSH)m_hNormalBrushA); } } } }
5. 使用RedrawWindow函数去刷新窗体,这个函数的好处就是根据不同的参数去刷新不同的消息的区域,防止界面闪动,这也是ollydbg界面不闪动的秘密之一。
m_MousePos.MousePt.x = MousePt.x;
m_MousePos.MousePt.y = MousePt.y;
RedrawWindow(m_hWnd,0,0,RDW_UPDATENOW| RDW_ALLCHILDREN | RDW_INVALIDATE);
经过以上一些关键的技术,在经过一些代码,看下我实现的效果:
结合ollydbg的界面实现IDA的静态反编译的界面:
多选择的Item状态
本篇文章就讲解到这里,下一次将讲解汇编引擎的选择与改造。在逆向时候我们需要一些工具,但是很多工具毕竟是别人写的,我们无法改变,一套好的工具就会事半功倍,如果我们自己实现一套自己的工具的话,那就更加完美了,读者如果有兴趣可以自己去实现一套全新的调试器。