【文章标题】: IPScan算法分析
【文章作者】: Monster
【作者邮箱】: suphack@vip.qq.com
【作者主页】: www.hackerm.com.cn
【作者QQ号】: 389264167
【软件名称】: IPScan
【软件大小】: 508K
【下载地址】: 自己搜索下载
【加壳方式】: ASPack
【保护方式】: 密码
【编写语言】: Delphi
【使用工具】: OD
【操作平台】: winxp
【软件介绍】: 一个扫描器
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
无意中从12期的黑客X档案光盘上翻出了一个扫描器,叫ipscan,就顺手打开看了一下,需要注册,郁闷死,我可没钱去注册,随手输入一个注册码,提示“Registration is not valid,please try again”,哎,运气不太好啊,既然没猜对,那还是来坚持我的一贯作风吧(盗版万岁),先放到PEID里去检查一下,显示已经用ASPack加了壳,ASPack的壳很好脱,我们用ESP定律法就可以简单搞定,
这里就是OEP,看代码:
004C3C18 55 push ebp
004C3C19 8BEC mov ebp, esp
004C3C1B 83C4 F0 add esp, -10
004C3C1E 53 push ebx
004C3C1F B8 38384C00 mov eax, 004C3838
一看就知道是Delphi,在OD中点右键选择“Dump debugger process”把它Dump出来,因为是ASPack的壳,所以连修复都免了,放到PEID里看看,嘿嘿,我没猜错吧,现在再把脱壳后的载入OD,在 OD中点击右键选择“Ultra String Reference”→“Find ASCII”打开OD的字符串查找功能,看看有没有敏感字符串,结果却是什么都没有,现在许多朋友可能会想到对GetWindowText或 GetDlgItemText之类的函数下个函数断点,或者对“OK”按钮下个消息断点,如果大家这么想的话,那么大家就忽略一个问题,就是Delphi 的特性问题,Delphi在获取文本框中的文本时并不会调用Windows API函数,而只是向文本框发送一个WM_GETTEXT消息来获取文本框中的内容(没有用到消息函数,而是直接调用WndProc),我用Dede找出了注册窗口中点击“OK”按钮时所调用的事件,我们可以看到它的RVA是004C0070,打开OD载入程序后按Ctrl+G在弹出的窗口中输入 004C0070来跳到这里的代码,我们再这里按F2下一个断点,现在我们按F9让程序在OD中运行起来,这时我们随便输入个注册码,按“OK”按钮后程序就被断在了我们刚刚下断点的地方,我们来F8跟踪一下,接着就会来到这里,代码如下:
004C0099 mov eax, dword ptr [ebp-4] ; 取注册码
004C009C call 004C371C ; 检查注册码
004C00A1 test al, al ; 比较语句
004C00A3 jnz short 004C00E3 ; 关键跳转
有点破解经验的朋友都会发现,我们只需要把004C00A3 jnz short 004C00E3这一句代码的jnz改成jmp,这就是关键跳转,也就是我们常说的暴点,这样我们不管输入什么密码就都会提示注册成功了,把我们破解后的文件保存起来,运行一下,随便输入个注册码,就来到了主界面。
现在可以说已经暴破成功了,但是这样的话,心里很不爽,既然要搞,那就要搞到底,还是来分析一下它的算法吧。我们按Ctrl+F2重新载入,继续来跟踪,当遇到004C009C call 004C371C这一句的时候,我们按F7跟进,这里就是检查注册码的地方,代码如下:
004C3747 cmp eax, 0A ; 检查注册码长度是否为10
004C374A jnz short 004C37A2 ; 不等于则跳(不能跳)
004C374C mov eax, dword ptr [ebp-4] ; 取注册码
004C374F xor ecx, ecx ; ecx清零
004C3751 mov cl, byte ptr [eax] ; 取注册码第一位
004C3753 mov eax, ecx
004C3755 mov edx, dword ptr [ebp-4] ; 取注册码
004C3758 movzx esi, byte ptr [edx+4] ; 取注册码第五位
004C375C add eax, esi ; 第一位与第五位相加
004C375E sub eax, 60
004C3761 cmp eax, 8 ; 和值与8比较
004C3764 jle short 004C37A2 ; 不大于则跳(不能跳)
004C3766 mov eax, ecx
004C3768 mov edx, dword ptr [ebp-4] ; 取注册码
004C376B movzx edx, byte ptr [edx+2] ; 取注册码第三位
004C376F add eax, edx ; 和第一位相加
004C3771 add eax, esi ; 再和第五位相加
004C3773 sub eax, 90
004C3778 mov ecx, 0A ; ecx=10
004C377D cdq
004C377E idiv ecx ; 除以10 (eax=商,edx为余数)
004C3780 mov eax, dword ptr [ebp-4] ; 取注册码
004C3783 movzx eax, byte ptr [eax+3] ; 取注册码第四位
004C3787 sub eax, 30
004C378A cmp edx, eax ; 第四位和余数比较
004C378C jnz short 004C37A2 ; 不等于则跳(不能跳)
004C378E mov eax, dword ptr [ebp-4] ; 取注册码
004C3791 cmp byte ptr [eax+1], 35 ; 第二位和5比较
004C3795 jbe short 004C37A2 ; 不高于则跳(不能跳)
004C3797 mov eax, dword ptr [ebp-4] ; 取注册码
004C379A cmp byte ptr [eax+5], 32 ; 第六位与2比较
004C379E ja short 004C37A2 ; 高于则跳(不能跳)
从这里我们可以分析出以下几点:
1. 注册码的长度为10
2. 第一位与第五位的和大于8
3. 第一位,第三位与第五位的和的各位数等于第四位
4. 第二位大于5
5. 第六位小于2

知道了这些,我们就可以写出一个注册机了,大家看我写出的代码:
// testDlg.cpp : implementation file
//

#include "stdafx.h"
#include "test.h"
#include "testDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
char chkey[10];
int i;
void makekey()
{
int key[10],i;
key[2]=rand()%10;
key[6]=rand()%10;
key[7]=rand()%10;
key[8]=rand()%10;
key[9]=rand()%10;
do
key[0]=rand()%10;
while (key ==0);
do
key[4]=rand()%10;
while (key[0]+key[4]<=8);
key[3]=(key[0]+key[2]+key[4])%10 ;
do
key[5]=rand()%10;
while (key[5]>2);
do
key[1]=rand()%10;
while (key[1]<=5);
for(i=0;i<=10;i++)
chkey=char(key)+48;
}
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTestDlg dialog

CTestDlg::CTestDlg(CWnd* pParent /*=NULL*/)
: CDialog(CTestDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CTestDlg)
m_editt = _T("");
m_edit = _T("");
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CTestDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CTestDlg)
DDX_Text(pDX, editt, m_editt);
DDX_Text(pDX, edit, m_edit);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
//{{AFX_MSG_MAP(CTestDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDOK, Onright)
ON_BN_CLICKED(IDCANCEL, Onexit)
ON_BN_CLICKED(IDC_BUTTON1, Onhelp)
ON_BN_CLICKED(IDC_BUTTON2, OnButton2)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTestDlg message handlers

BOOL CTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();

// Add "About..." menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}

// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon

// TODO: Add extra initialization here

return TRUE; // return TRUE unless you set the focus to a control
}

void CTestDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}

// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.

void CTestDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}

// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CTestDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}

void CTestDlg::Onright()
{
makekey();
m_edit=chkey;
UpdateData(false);
}

void CTestDlg::Onexit()
{
exit(1); // TODO: Add your control notification handler code here

}

void CTestDlg::Onhelp()
{
MessageBox("扫描器IPScan的注册机","帮助");
}

void CTestDlg::OnButton2()
{
MessageBox("Copyright By IPScan Registry\nAuther:Monster\nBlog:www.hackerm.com.cn\nQQ:389264167","版权");

}


大家看下运行结果,,嘿嘿,效果不错。


但是这里还有个问题,软件有个限制,就是程序只允许运行一个当运行第二个时就会自动退出,很不爽,想同时扫两个都不行,还是给它破了吧。
我们前面在刚载入OD的时候,在入口点的下面发现有这样的几句代码:
004C3C29 push 0 ; /Title = NULL
004C3C2B push 004C3C9C ; |Class = "Free IP Scanner Made by Eusing Software"
004C3C30 call <jmp.&user32.FindWindowA> ; \FindWindowA
004C3C35 mov ebx, eax
004C3C37 test ebx, ebx
004C3C39 jbe short 004C3C52
004C3C3B push 0 ; /lParam = 0
004C3C3D push 0 ; |wParam = 0
004C3C3F push 2610 ; |Message = MSG(2610)
004C3C44 push ebx ; |hWnd
004C3C45 call <jmp.&user32.PostMessageA> ; \PostMessageA
004C3C4A push ebx ; /hWnd
004C3C4B call <jmp.&user32.SetForegroundWindow> ; \SetForegroundWindow

在这里,程序调用了一个函数FindWindowA,查找当前是否有一个叫“Free IP Scanner Made by Eusing Software”的窗口,如果存在就把他关了。

要破这个简单,004C3C39
jbe
short004C3C52这一句就是关键跳转,我们只把它也改成jmp
004C3C52就可以了,我们保存起来运行一下,成功了。