OD一段简单的C++代码

开始学习逆向,从熟悉的js、php跳跃到汇编,有点小凌乱 ...

 

先来OD一段简单的C++代码:

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

int funcA(int a, int b);

void main()
{
	int a;
	int b;
	int n;

	a = strlen("abc");
	b = 10;
	n = funcA(a, b);
}

int funcA(int a, int b)
{
	int c;
	char sBuff[10];

	c = (a + b) * a * b;

	sprintf(sBuff, "%d", c);
	MessageBox(NULL, sBuff, NULL, MB_OK);
	return c;
}
 

编译环境:VC6 ,release

 

这段代码逻辑上很简单,一共两个函数,main和funcA,funcA会在main中被调用一次。main函数的入口会被编译成call 00401000,00401000是虚拟的偏移量。虚拟地址也可写作“段:偏移量”的形式 ,一般来说可以不用关注段,因为同一个程序在不同的系统下,段值可能不相同。但是偏移量却是有着一些共性,对于同一个程序的某条指令,在不同系统中它的偏移量一般是相同的。按照VS的默认设置,编译出的exe文件基地址是00400000,dll文件基地址是10000000。

 

 

先来直接在main的入口处下断,然后逐步跟进。

 

00401154  |.  50                       push    eax
00401155  |.  FF35 0C994000  push    dword ptr [40990C]
0040115B  |.  FF35 08994000  push    dword ptr [409908]
00401161  |.  E8 9AFEFFFF       call    00401000

00401166  |.  83C4 0C             add     esp, 0C

 

这是一个典型的函数调用,只不过调的是main罢了。三个参数压栈,然后call,完了消栈,格式很清晰。

main中的汇编代码如下:

 

00401000  /$  57                       push    edi
00401001  |.  BF 30704000       mov     edi, 00407030                    ;  ASCII "abc"
00401006  |.  83C9 FF               or      ecx, FFFFFFFF
00401009  |.  33C0                    xor     eax, eax
0040100B  |.  F2:AE                   repne   scas byte ptr es:[edi]
0040100D  |.  F7D1                    not     ecx
0040100F  |.  49                        dec     ecx
00401010  |.  6A 0A                   push    0A
00401012  |.  51                        push    ecx
00401013  |.  E8 08000000       call    00401020                             ;  该处调用funcA函数
00401018  |.  83C4 08              add     esp, 8
0040101B  |.  5F                        pop     edi
0040101C  \.  C3                       retn

 

strlen函数并没有向funcA一样被编译为另外一个call,而是直接编译在了main函数之中。strlen的实现非常之精妙:

这是strlen()在VC优化编译模式下编译后的代码:

00401000  /$  57                       push    edi      ;  因为后面repne的时候要发生更改,所以先暂存edi

00401006  |.  83C9 FF               or      ecx, FFFFFFFF
00401009  |.  33C0                    xor     eax, eax
0040100B  |.  F2:AE                   repne   scas byte ptr es:[edi]
0040100D  |.  F7D1                    not     ecx
0040100F  |.  49                        dec     ecx

 

edi是指向字符串“abc”的指针,repne   scas byte ptr es:[edi] 指令会扫描字符串“abc”(在内存中为 61 62 63 00),从字母a开始,一直到字符串结束符,一共4个字符。

 

REPNE :用在CMPS、SCAS指令前,每执行一次串操作指令ECX减1,并判断ZF标志是否为1,只要CX=0或ZF=1,则重复执行结束。

SCAS :将附加段中的字节或字内容与AL/AX寄存器内容进行比较,根据比较的结果设置标志,每次比较后修改EDI寄存器的值,使之指向下一个元素。

 

这里需要扫描4次,每次扫描的字符与eax中的低八8位即00相比较,如果相同,则ZF标志位变成1,扫描结束;如果不同,则ecx减1并继续扫描。因此需要事先将eax置0,并且ecx置为-1。扫描完之后的ecx变为FFFFFFFB。取反后变成00000004,这是包含了字符串结束标志的长度,因此还需要减1.

 

随后将10、3入栈,准备调用funcA函数。函数的调用基本上都满足_stdcall约定 ,即参数从右向左,顺次压栈,函数调用的返回值保存在eax函数中。funcA的汇编码如下:

 

虽然有点长,但是代码还是很好理解的。

开始的两句mov是去栈中取操作数3、10,并且分别存放到eax、ecx中。

随后,sub esp,0xC是将栈顶向上移动,从而开辟出一块空的区域,可以用作预留用。

 

0040102C  |.  8D3408        lea esi,dword ptr ds:[eax+ecx]
0040102F  |.  0FAFF0        imul esi,eax
00401032  |.  0FAFF1        imul esi,ecx

这三句完成了 c = (a + b) * a * b ,运算结果的值保存在esi中。

随后调用的MessageBox的过程暂时忽略。

因为最后需要返回c的值,因此在retn之前,需要执行一句mov eax,esi。

 

这里的call调用都没有被编译成经典的:

PUSH ebp
MOV ebp esp

······

这个无所谓,值得注意的是需要确保栈的平衡性,即入栈一定要与消栈相对应。

 

 

 

### 华为OD面试C++手撕代码常见题目及解答 #### 1. **递增字符串** 此问题是华为OD机试中的高频题之一,目标是实现一个函数来判断给定的字符串是否可以经过若干次操作变为严格递增序列。以下是基于C++的解决方案: ```cpp #include <iostream> #include <string> using namespace std; bool canBeIncreasing(string s) { int count = 0; for (int i = 1; i < s.size(); ++i){ if(s[i] <= s[i - 1]){ count++; if(count > 1){ return false; } if(i >= 2 && s[i] <= s[i - 2]){ s[i] = s[i - 1]; }else{ s[i - 1] = s[i] - 1; } } } return true; } int main(){ string inputString = "abc"; cout << (canBeIncreasing(inputString) ? "Yes" : "No") << endl; } ``` 上述代码实现了`canBeIncreasing`函数[^2],该函数用于检测输入字符串能否通过一次删除字符的操作转换成严格递增字符串。 --- #### 2. **分发糖果问题** 这是一个经典的动态规划问题,在华为OD面试中也经常被提及。其核心在于分配最少数量的糖果使得满足特定条件。下面是C++版本的解决方法: ```cpp #include <vector> #include <algorithm> using namespace std; int distributeCandies(vector<int>& ratings) { int n = ratings.size(); vector<int> candies(n, 1); // 左到右遍历 for(int i=1;i<n;i++) { if(ratings[i]>ratings[i-1]) { candies[i]=candies[i-1]+1; } } // 右到左遍历 for(int i=n-2;i>=0;i--) { if(ratings[i]>ratings[i+1]) { candies[i]=max(candies[i],candies[i+1]+1); } } return accumulate(candies.begin(), candies.end(), 0); } // 测试用例 void testDistributeCandies() { vector<int> ratings = {1, 0, 2}; cout << "Total Candies Needed: " << distributeCandies(ratings) << endl; } int main(){ testDistributeCandies(); } ``` 这段代码展示了如何利用两次扫描完成最小糖果数目的计算过程[^3]。 --- #### 3. **背包DP问题** 背包问题是算法设计领域的重要经典问题之一,也是华为OD考试的重点考察对象。下面是一个简单的0/1背包问题实例及其对应的C++实现方案: ```cpp #include <bits/stdc++.h> using namespace std; const int MAX_W = 1e3 + 5; int dp[MAX_W]; int knapsackProblem(int W, vector<pair<int,int>> items){ memset(dp, 0, sizeof(dp)); for(auto &[w,v]:items){ for(int j=W;j>=w;j--){ dp[j] = max(dp[j], dp[j-w]+v); } } return dp[W]; } // 测试用例 void testKnapsackProblem(){ int capacity = 10; vector<pair<int,int>> items = {{2,6},{2,3},{6,5},{5,4},{4,6}}; cout << "Max Value in Knapsack: " << knapsackProblem(capacity, items) << endl; } int main(){ testKnapsackProblem(); } ``` 这里提供了标准的动态规划解法来处理0/1背包问题。 --- #### 4. **最长公共子序列(LCS)** LCS也是一个常见的华为OD面试考点,它涉及到两个字符串之间的匹配关系。以下提供了一个高效的C++实现方式: ```cpp #include <vector> #include <string> using namespace std; int longestCommonSubsequence(const string& text1, const string& text2) { int m = text1.length(), n = text2.length(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ if(text1[i-1]==text2[j-1]){ dp[i][j] = dp[i-1][j-1] + 1; } else{ dp[i][j] = max(dp[i-1][j], dp[i][j-1]); } } } return dp[m][n]; } // 测试用例 void testLongestCommonSubsequence(){ string str1="AGGTAB",str2="GXTXAYB"; cout<<"Length of LCS is "<<longestCommonSubsequence(str1,str2)<<endl; } int main(){ testLongestCommonSubsequence(); } ``` 以上程序定义并测试了寻找两串之间最大长度公共子序列的功能[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值