首页
社区
课程
招聘
[原创]调试器中不通过TF标记位实现单步操作的实现思路
发表于: 2010-8-21 17:27 14491

[原创]调试器中不通过TF标记位实现单步操作的实现思路

2010-8-21 17:27
14491
----besterChen       
//************************************************************************
// 函数名: GetNextAddr
// 权  限: public 
// 返回值: DWORD 返回下一条指令的地址
// 参  数: BOOL isStepOver  TRUE为步过,FALSE为步入
// 说  明: 用于计算下一条指令所在地址的函数
// 合  格:
//************************************************************************
DWORD CDebugLib::GetNextAddr(BOOL isStepOver)
{
    DWORD    dwNextAddr = 0;
    TCHAR    szCommand[CODE_SIZE] = {0};    // 用来接收指令
    TCHAR    szCmdLine[CODE_SIZE] = {0};    // 用来接收反汇编出来的汇编指令

    t_disasm da                = {0};

    if (m_pBreakPoint == NULL)
    {
        return FALSE;
    }

	PCONTEXT tagpContext = GetContextValue();

	if (!tagpContext || tagpContext->Eip == NULL)
	{
		return FALSE;
	}

	int nLen = GetAsmCode((LPVOID)tagpContext->Eip, &da);
    _tcscpy(szCmdLine, da.result);
    _stscanf(szCmdLine, _T("%[^ ]"), szCommand); // 分离出指令来作判断
    _tcsupr(szCommand);

    if (_tcsicmp(szCommand, _T("CALL")) == 0)
    {
        if (isStepOver != TRUE)
        {
            // 对于单步步入的情况,需要计算步入的地址
            #pragma chMSG(CALL 指令的单步步入情况还没处理....)
            return dwNextAddr;
        }
        // 对于CALL的步过情况,与普通指令相同,这里就不处理了。
    }
    else if (_tcsstr(szCommand, _T("J")) != 0)
    {
        // 需要判断跳转实现没有
        if(IsJmpSuccess(tagpContext, szCommand))
        {
            // 如果跳转实现了,需要自己计算目标地址。
            _tprintf(_T("跳转已经实现....\r\n"));
            #pragma chMSG(JCC 指令中跳转实现的情况还没处理....)

            return dwNextAddr;
        }

        // 如果没有实现,与普通指令相同,这里就不处理了。
        _tprintf(_T("跳转没有实现....\r\n"));
    }
    else if (_tcsstr(szCommand, _T("RET")) != 0)
    {
        // 需要手工计算步入的地址
        assert(m_pBreakPoint->ReadMemory((LPVOID)tagpContext->Esp, \
               &dwNextAddr, sizeof(DWORD)));

        return dwNextAddr;
    }

    // 剩下的指令都是 EIP + 当前指令长度了。
    return (DWORD)((BYTE*)tagpContext->Eip+nLen);
}
int nLen = GetAsmCode((LPVOID)tagpContext->Eip, &da); //得到当前指令的长度和反汇编的字符串
_stscanf(da.result, _T("%[^ ]"), tagCMDline.szCommand);		// 分离出指令部分
_stscanf(da.result, _T("%*[^ ] %[^,]"), tagCMDline.szParam1);	// 分离出指令的操作数1
_stscanf(da.result, _T("%*[^ ] %*[^,] , %[^,]"), tagCMDline.szParam2);	// 分离出操作数2
	[ebp+eax*4]
	DWORD GetNodeValue(opera* tagpOper)
	{
	    switch( tagpOper->type )
	    {
	    case Reg:
	        /* 知道此时栈节点的类型为关键字,就需要根据EDX在符号表中找到EDX的值 */
	        return dwEdx;  
	    /* ... */
	    }
			return -1;
	}

  • 检测当前指令是否是CALL或JCC类指令,如果不是,下一条指令就是 EIP + 当前指令长度
  • 检测指令是否为CALL类指令,如果指令是CALL类的指令就看下当前是步入还是不过,如果是步过,下条指令就是 EIP + 当前指令长度,如果是步入就继续步骤4。
  • 如果检测指令是JCC类指令,就如果是检测跳转是否实现,如果没实现下一条指令就是EIP + 当前指令长度;如果跳转实现,就继续步骤4。
  • 取出当前指令的操作数中中的所有的寄存器、立即数操作数以及操作符号。
  • 根据取出的内容,判断当前的转移指令是属于上面的哪个部分。
  • 对分离出的指令按照当前操作数中的规定进行运算得到目标地址

  • 取出 edx ,将其值存放到一个临时变量 A 中。
  • 取出+ 运算符,就想临时变量A的内容压栈。
  • 取出eax,将其值存放到一个临时变量A中。
  • 取出* 运算符,由于其优先级最高,就直接取下一个值。
  • 取出立即数 4, 将其与临时变量A的内容作乘法运算,并将结果再次存入临时变量A中。
  • 取出 + 运算符, 将临时变量A的值压栈。
  • 取出立即数 0x00401000,将其值存入临时变量A中。
  • 取值结束,将临时变量A的值压栈。
  • 依次求出栈中所有数据的和,得到最终的结果。

  • 检测当前指令是否是CALL或JCC类指令,如果不是,下一条指令就是 EIP + 当前指令长度
  • 检测指令是否为CALL类指令,如果指令是CALL类的指令就看下当前是步入还是不过,如果是步过,下条指令就是 EIP + 当前指令长度,如果是步入就继续步骤4。
  • 如果检测指令是JCC类指令,就如果是检测跳转是否实现,如果没实现下一条指令就是EIP + 当前指令长度;如果跳转实现,就继续步骤4。
  • 取出当前指令的操作数中中的所有的寄存器、立即数操作数以及操作符号。
  • 根据取出的内容,判断当前的转移指令是属于上面的哪个部分。
  • 对分离出的指令按照当前操作数中的规定进行运算得到目标地址

  • 取出 edx ,将其值存放到一个临时变量 A 中。
  • 取出+ 运算符,就想临时变量A的内容压栈。
  • 取出eax,将其值存放到一个临时变量A中。
  • 取出* 运算符,由于其优先级最高,就直接取下一个值。
  • 取出立即数 4, 将其与临时变量A的内容作乘法运算,并将结果再次存入临时变量A中。
  • 取出 + 运算符, 将临时变量A的值压栈。
  • 取出立即数 0x00401000,将其值存入临时变量A中。
  • 取值结束,将临时变量A的值压栈。
  • 依次求出栈中所有数据的和,得到最终的结果。
  • 检测当前指令是否是CALL或JCC类指令,如果不是,下一条指令就是 EIP + 当前指令长度
  • 检测指令是否为CALL类指令,如果指令是CALL类的指令就看下当前是步入还是不过,如果是步过,下条指令就是 EIP + 当前指令长度,如果是步入就继续步骤4。
  • 如果检测指令是JCC类指令,就如果是检测跳转是否实现,如果没实现下一条指令就是EIP + 当前指令长度;如果跳转实现,就继续步骤4。

  • [培训]科锐逆向工程师培训第53期2025年7月8日开班!

    上传的附件:
    收藏
    免费 7
    支持
    分享
    最新回复 (11)
    雪    币: 168
    活跃值: (157)
    能力值: ( LV11,RANK:180 )
    在线值:
    发帖
    回帖
    粉丝
    2
    难得今天上网,发现没有人讨论~
    反而钱老师给了一个精华~
    为了对得起这个精华,我就顺便贴下我的实现代码和效果图吧~(代码太挫……)
    关于代码的分离部分:
    //************************************************************************
    // 函数名: Lex_Split
    // 权  限: public 
    // 返回值: list<TCHAR*>*        返回一个字符串链表
    // 参  数: IN TCHAR * pszRegular
    // 参  数: IN TCHAR * pszSource
    // 说  明: 从指定的字符串中分离出各种子串
    // 合  格:
    //************************************************************************
    list<TCHAR*>* Lex_Split(IN const TCHAR* pszRegular, IN const TCHAR* pszSource)
    {
        int ntmpLen = 0;
        int nCount = 0; // 用来统计匹配的数量作为返回值
        match_results result; // 匹配结果
        list<TCHAR*> *pszResultList = new list<TCHAR*>;
        REGEX_FLAGS emRegEx*** = GLOBAL | ALLBACKREFS | NOCASE | SINGLELINE;
        rpattern pat((LPCTSTR)pszRegular, emRegEx***);  //正则模式及设置
        int iGroups = pat.cgroups();
        match_results::backref_type br = pat.match(pszSource, result );
    
        if (pszRegular == NULL || pszSource == NULL || br.matched == FALSE)
        {
            return NULL;
        }
    
    
        for( int i=0; i < result.cbackrefs(); i++ )
        {
            ntmpLen = _tcslen(result.backref(i).str().c_str());
            if( i%iGroups == 0 && ntmpLen > 0)
            {
                nCount++;
                
                TCHAR *pszResult = new TCHAR[ntmpLen+1];
                ZeroMemory(pszResult, ntmpLen+1);
                _tcscpy(pszResult, result.backref(i).str().c_str());
                pszResultList->push_back(pszResult);
            }
        }
    
        return pszResultList;
    }


    关于根据token获取值的部分:
    //************************************************************************
    // 函数名: GetRegTokenValue
    // 权  限: public 
    // 返回值: DWORD
    // 参  数: IN CONTEXT * tagpContext
    // 参  数: TCHAR * pszRegToken
    // 说  明: 获取 分离出的各种token代表的值
    // 合  格:
    //************************************************************************
    DWORD GetRegTokenValue(IN CONTEXT *tagpContext, TCHAR *pszRegToken)
    {
        for (int i = 0; i < 8; i++)
        {
            if (_tcsicmp(st_RegInfo[i].pszString, pszRegToken) != 0)
            {
                continue;
            }
    
            switch(st_RegInfo[i].tagType.e)
            {
            case REG_TYPE::REG_TYPE_EAX:
                 return tagpContext->Eax;
    
            case REG_TYPE::REG_TYPE_EBX:
                 return tagpContext->Ebx;
    
            case REG_TYPE::REG_TYPE_ECX:
                 return tagpContext->Ecx;
            case REG_TYPE::REG_TYPE_EDX:
                 return tagpContext->Edx;
            case REG_TYPE::REG_TYPE_ESP:
                 return tagpContext->Esp;
            case REG_TYPE::REG_TYPE_EBP:
                 return tagpContext->Ebp;
            case REG_TYPE::REG_TYPE_ESI:
                 return tagpContext->Esi;
            case REG_TYPE::REG_TYPE_EDI:
                 return tagpContext->Edi;
            default:
                ;
            }
        }
    
        return -1;
    }
    
    
    
    //************************************************************************
    // 函数名: GetRegTokenValue
    // 权  限: public 
    // 返回值: DWORD
    // 参  数: TCHAR * pszCstToken
    // 说  明: 获取 分离出的 常量 的值
    // 合  格:
    //************************************************************************
    DWORD GetCstTokenValue(TCHAR *pszCstToken)
    {
        DWORD dwResult = 0;
        if (pszCstToken == NULL)
        {
            return 0;
        }
    
        _stscanf(pszCstToken, _T("%x"), &dwResult);
    
        return dwResult;
    }


    整体上的模拟运算的流程如下:
    //************************************************************************
    // 函数名: CalcSimulator
    // 权  限: public 
    // 返回值: DWORD
    // 参  数: IN CONTEXT * tagpContext
    // 参  数: IN list<TCHAR * > * pTokenList
    // 说  明: 表达式计算
    // 合  格:
    //************************************************************************
    DWORD CalcSimulator(IN CONTEXT *tagpContext, IN list<TCHAR*>* pTokenList)
    {
        int nCount = 0;
        DWORD dwResult = 0; // 前一个操作数
        DWORD dwCurNum = 0;
        if (tagpContext == NULL || pTokenList == NULL)
        {
            return -1;
        }
    
        if (pTokenList->size() <= 1)
        {
            return -1;
        }
    
        OPERATE_STATE em_OperReg;
        OPERATE_STATE em_OperCst;
        OPERATE_STATE em_OperSign;
        OPERATE_STATE em_OperStart;
        OPERATE_STATE em_OperEnd;
    
        em_OperReg.e = OPERATE_STATE::STATE_OPER_REG;
        em_OperCst.e = OPERATE_STATE::STATE_CONST_NUM;
        em_OperSign.e = OPERATE_STATE::STATE_SIGN_TOKEN;
        em_OperStart.e= OPERATE_STATE::STATE_SIGN_START;
        em_OperEnd.e = OPERATE_STATE::STATE_SIGN_END;
    
        pTokenList = CalcMulti(tagpContext, pTokenList); // 先处理乘法
    
        if (pTokenList == (list<TCHAR*>*)-1)
        {
            return -1;
        }
    
        nCount = pTokenList->size();
    
        list<TCHAR*>::iterator it = pTokenList->begin();
        for ( int i = 0 ; i < nCount ; i++ )
        {
            TCHAR *pResultBuf = NULL;
            pResultBuf = *it;
    
            if (pResultBuf == NULL)
            {
                continue;
            }
            
            if (Is(em_OperStart, pResultBuf))
            {
                // 清空栈内容
                while (!g_WaitForCalcStack.empty())
                {
                    g_WaitForCalcStack.pop();
                }
            }
    
            if (Is(em_OperEnd, pResultBuf))
            {
                // 处理完成,开始计算栈中的数据
                int nSize = g_WaitForCalcStack.size();
                for (int i = 0; i < nSize; i ++)
                {
                    dwResult += g_WaitForCalcStack.top();
                    g_WaitForCalcStack.pop();
                }
    
                break;
            }
    
    
            if (Is(em_OperReg, pResultBuf))
            {
                // 如果是寄存器,就保存寄存器中的内容
                g_WaitForCalcStack.push(GetRegTokenValue(tagpContext, pResultBuf));
            }
    
            if (Is(em_OperCst, pResultBuf))
            {
                // 如果是立即数,就保存其值
                g_WaitForCalcStack.push(GetCstTokenValue(pResultBuf));
            }
    
            if (Is(em_OperSign, pResultBuf))
            {
                if (_tcsicmp(pResultBuf, _T("-")) == 0)
                {
                    it++;
                    //如果是减法,则需要将后面的数据取补保存
                    pResultBuf = *it;
    
                    if (Is(em_OperReg, pResultBuf))
                    {
                        // 如果是寄存器,就保存寄存器中的内容
                        dwCurNum = GetRegTokenValue(tagpContext, pResultBuf);
                        g_WaitForCalcStack.push(dwCurNum);
                    }
    
                    if (Is(em_OperCst, pResultBuf))
                    {
                        // 如果是立即数,就保存其值
                        dwCurNum = GetCstTokenValue(pResultBuf);
                        dwCurNum = (~dwCurNum)+1; // 求补码
                        g_WaitForCalcStack.push(dwCurNum);
                    }
                }
            }
    
            it++;
        }
    
        return dwResult;

        由于乘法在我们当前的环境下优先级最高,所以我将乘法的计算过程分离出去了:
    // 先计算乘法
    static list<TCHAR*>* CalcMulti(IN CONTEXT *tagpContext, IN list<TCHAR*>* pTokenList)
    {
        if (tagpContext == NULL || pTokenList == NULL)
        {
            return NULL;
        }
        TCHAR *pPreToken = NULL;
        TCHAR *pResultBuf = NULL;
        DWORD dwPreNum = 0; // 前一个操作数
        DWORD dwNextNum = 0; // 后一个操作数
    
        int   nCount = 0;
        OPERATE_STATE em_OperReg;
        OPERATE_STATE em_OperCst;
        OPERATE_STATE em_OperSign;
        OPERATE_STATE em_OperStart;
        OPERATE_STATE em_OperEnd;
    
        em_OperReg.e = OPERATE_STATE::STATE_OPER_REG;
        em_OperCst.e = OPERATE_STATE::STATE_CONST_NUM;
        em_OperSign.e = OPERATE_STATE::STATE_SIGN_TOKEN;
        em_OperStart.e= OPERATE_STATE::STATE_SIGN_START;
        em_OperEnd.e = OPERATE_STATE::STATE_SIGN_END;
    
        list<TCHAR*>* pResultList = new list<TCHAR*>;
        list<TCHAR*>::iterator it = pTokenList->begin();
        nCount = pTokenList->size();
        for ( int i = 0 ; i < nCount; i++ ) // 多循环了一次,需要看看
        {
            pResultBuf = *it;
    
            if (pResultBuf == NULL)
            {
                continue;
            }
    
            if (_tcsicmp(pResultBuf, _T("*")) != 0)
            {
                pResultList->push_back(pResultBuf);
            }
    
            if (i > 0)
            {
                it--;
                pPreToken = *it;
                it++;
            }
    
            if (pPreToken != NULL && _tcsicmp(pPreToken, _T("*")) == 0)
            {
                // 如果前一个是*也不保存当前的
                pResultList->pop_back();
            }
    
    
            if (Is(em_OperSign, pResultBuf) && _tcsicmp(pResultBuf, _T("*")) == 0)
            {
                pResultList->pop_back();
                // 先取出前一个操作数
                it--;
                pResultBuf = *it;
                if (Is(em_OperReg, pResultBuf))
                {
                    // 如果是寄存器,就保存寄存器中的内容
                    dwPreNum = GetRegTokenValue(tagpContext, pResultBuf);
                }
    
                if (Is(em_OperCst, pResultBuf))
                {
                    // 如果是立即数,就保存其值
                    dwPreNum = GetCstTokenValue(pResultBuf);
                }
    
                // 取出后一个操作数
                it++;
                it++;
                pResultBuf = *it;
                if (Is(em_OperReg, pResultBuf))
                {
                    // 如果是寄存器,就保存寄存器中的内容
                    dwNextNum = GetRegTokenValue(tagpContext, pResultBuf);
                }
    
                if (Is(em_OperCst, pResultBuf))
                {
                    // 如果是立即数,就保存其值
                    dwNextNum = GetCstTokenValue(pResultBuf);
                }
                
                TCHAR* pNewNum = new TCHAR[4];
                ZeroMemory(pNewNum, sizeof(TCHAR)*4);
                _stprintf(pNewNum, _T("%d"), dwPreNum*dwNextNum);
                pResultList->push_back(pNewNum);
                
                it--;
            }
    
            it++;
        }
    
        pTokenList->clear();
        if (pTokenList)
        {
            delete pTokenList;
            pTokenList = NULL;
        }
    
        pTokenList = pResultList;
        return pResultList;
    }


    主要的代码就这些了,也用不着我多说废话了,下面贴一下效果图:
    先贴个简单的立即数寻址的:


    CALL  reg形式的:


    再看下 call [addr]这种形式的:


    再看下return的效果:


    最后来个超级复杂的那种(我自己修改代码构造的测试程序):


    就到这里吧,希望各位师兄能多提点意见,也希望本文能对其它朋友有所帮助。
    上传的附件:
    2010-8-26 22:34
    0
    雪    币: 62
    活跃值: (112)
    能力值: ( LV5,RANK:70 )
    在线值:
    发帖
    回帖
    粉丝
    3
    要活得下一条指令的地址。。。。。。。。

    1.在本条指令处暂停,保存Context
    2.单步一下
    3.保持Context
    4.活得eip
    5.恢复Context
    2010-8-27 10:59
    0
    雪    币: 45
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    4
    LZ的意思好像是要实现你说的第二步,怎么单步一下……
    2010-8-27 12:42
    0
    雪    币: 2322
    活跃值: (573)
    能力值: ( LV9,RANK:200 )
    在线值:
    发帖
    回帖
    粉丝
    5
    楼主需要一个稍微好点的反汇编引擎.
    或者自己解析opcode都比二次解析方便得多
    2010-8-27 21:32
    0
    雪    币: 73
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    6
    楼主强大...吾等小弟...膜拜
    2010-8-28 18:17
    0
    雪    币: 334
    活跃值: (22)
    能力值: ( LV4,RANK:50 )
    在线值:
    发帖
    回帖
    粉丝
    7
    很强大!!

    不过有点绕弯了:把OPCODE反汇编成字符串,又把字符串分成了Token处理。
    为什么不直接处理OPCODE呢?这样代码会少很多效率也会有所提高

    像得到instuction [reg*4n + xxx]写一个GetModeRMValue函数即可了。

    还有一点就是在用OD的时候当用F7单步sysenter的效果,这个思路是做不到的。

    总体来说很强大了!100个支持
    2010-8-29 23:31
    0
    雪    币: 168
    活跃值: (157)
    能力值: ( LV11,RANK:180 )
    在线值:
    发帖
    回帖
    粉丝
    8
    [QUOTE=likunkun;852500]很强大!!

    不过有点绕弯了:把OPCODE反汇编成字符串,又把字符串分成了Token处理。
    为什么不直接处理OPCODE呢?这样代码会少很多效率也会有所提高

    像得到instuction [reg*4n + xxx]写一个GetModeRMValue函数即可了。

    还有一点就是在用OD的时候...[/QUOTE]

    恩,不过,说实话,OpCode是啥我现在都没搞明白……

    当时只是第一感觉是这样,我就这样做了。自己也感觉这样的做法不是很妥当,但是没有别的什么方法,所以发上来抛砖引玉,(*^__^*) 嘻嘻……

    至于效率问题,我没考虑过,我觉得,单步的话,不用太考虑效率,毕竟人按键盘也不会太效率……

    那个sysenter这个指令我还真没想到,我只想到了JCC, RETN, CALL……
    2010-8-30 05:38
    0
    雪    币: 422
    活跃值: (130)
    能力值: ( LV8,RANK:130 )
    在线值:
    发帖
    回帖
    粉丝
    9
    壮壮写的很强大啊。
    你用的是啥反汇编引擎啊?
    如果是OD的,他里面有指令类型,会告诉你当前指令是CALL,跳转之类的。还有寻址方式,好像有些也有说明。
    2010-9-15 08:53
    0
    雪    币: 883
    活跃值: (314)
    能力值: ( LV9,RANK:280 )
    在线值:
    发帖
    回帖
    粉丝
    10
    哎,前浪死在沙滩上,壮壮果然很强、很大!
    2010-9-23 09:11
    0
    雪    币: 206
    活跃值: (15)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    11
    我们也要写调试器了,学习先
    2010-11-4 23:20
    0
    雪    币: 45
    活跃值: (25)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    12
    mov [eax], 2;   eax = 0;
    div eax;
    ....
    ....

    且如果程序的异常处理中故意修改了eip..

    这样的话是让异常进调试器的异常处理,然后在调试器中遍历异常链表?

    如果异常链有100个handle,那就是先执行handle1,然后重新运行指令,如果该指令没修复,再执行handle2..?

    郁闷中....
    2010-11-25 15:36
    0
    游客
    登录 | 注册 方可回帖
    返回