C語(yǔ)言逆向分析語(yǔ)法超詳細(xì)分析
基本數(shù)據(jù)類型
在c中基本數(shù)據(jù)類型分為:char,short,int,long,float,double
以上數(shù)據(jù)類型除float和double外均可以分為有符號(hào)(singed)和無符號(hào)(unsigned)兩類
有符號(hào)時(shí)最高位為符號(hào)位,用來表示數(shù)據(jù)的正負(fù)
無符號(hào)情況下最高位為正常的數(shù)據(jù)位不做特殊含義
類型 | 占位 |
---|---|
Char | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
Double | 8 |
浮點(diǎn)數(shù)類型的存儲(chǔ)
浮點(diǎn)數(shù)類型是比較特殊的,首先他是交給專門的cpu來處理的,比如在80386中就引入了8087協(xié)處理器來專門處理浮點(diǎn)數(shù)的計(jì)算
C中的浮點(diǎn)數(shù)存儲(chǔ)方式采用了浮點(diǎn)實(shí)數(shù)存儲(chǔ)方式,也就是在全部二進(jìn)制位上選取一段用來表示實(shí)數(shù)另一段表示小數(shù)點(diǎn)的位置,如952.7可以分為9527和0.1
C的浮點(diǎn)數(shù)的編碼采用的是ieee標(biāo)準(zhǔn)編碼格式,
如float類型下將浮點(diǎn)數(shù)分為三部分:符號(hào)位(1bit)、小數(shù)位(8bit)、實(shí)數(shù)位(23bit)
double:符號(hào)位(1bit)、小數(shù)位(11bit)、實(shí)數(shù)位(52bit)
舉例:12.25f拆分:符號(hào)位:0
? 小數(shù)位:1000 0010
? 實(shí)數(shù)位:10001 后續(xù)均為0
字符類型的存儲(chǔ)
字符類型是根據(jù)字符的編碼格式將對(duì)應(yīng)字符的數(shù)字表示存儲(chǔ)為二進(jìn)制。
具體的字符編碼解析可以劃到底部
指針和引用類型
在C中用指針類型(TYPE*)來表示一個(gè)用來存儲(chǔ)一個(gè)地址的DWORD類型,用&符號(hào)來表示取一個(gè)變量的地址
如:int* a;此時(shí)a則會(huì)被認(rèn)為是一個(gè)指針類型,在對(duì)a進(jìn)行操作時(shí)則會(huì)被編譯器編譯為匯編中的間接操作
舉例:
int tmp = 10; int* a = &tmp; (*a)+=1; 對(duì)應(yīng)的匯編簡(jiǎn)單來寫如下: mov dword ptr [esp],0Ah; 將10存在棧中 lea eax,[esp] 取得tmp所在的地址 mov dword ptr [esp-4],eax 將tmp所在的地址存儲(chǔ)到棧中 mov ecx,dword ptr [esp-4] 取出tmp所在的地址 mov ecx,dword ptr [eax] add dword ptr ecx,1 將tmp所在地址所指向的內(nèi)容加一 mov dword ptr[eax],ecx
在c中以引用類型(type&)來表示一個(gè)操作的集合,每次對(duì)這個(gè)引用類型的操作都是取變量的內(nèi)容將內(nèi)容作為地址修改此地址中的數(shù)據(jù)并寫回的一個(gè)操作的集合
舉例:
int tmp = 10; int& a = &tmp; a+=1; 對(duì)應(yīng)的匯編簡(jiǎn)單來寫如下: mov dword ptr [esp],0Ah; 將10存在棧中 lea eax,[esp] 取得tmp所在的地址 mov dword ptr [esp-4],eax 將tmp所在的地址存儲(chǔ)到棧中 mov ecx,dword ptr [esp-4] 取出tmp所在的地址 mov ecx,dword ptr [eax] add dword ptr ecx,1 將tmp所在地址所指向的內(nèi)容加一 mov dword ptr[eax],ecx
可以看到引用類型和指針類型操作編譯為匯編其實(shí)是基本一樣的,區(qū)別就在于指針類型變量所存儲(chǔ)的地址也可以進(jìn)行算術(shù)運(yùn)算
舉例:
int tmp = 10; int* a = &tmp; a++; 對(duì)應(yīng)的匯編簡(jiǎn)單來寫如下: mov dword ptr [esp],0Ah; 將10存在棧中 lea eax,[esp] 取得tmp所在的地址 mov dword ptr [esp-4],eax 將tmp所在的地址存儲(chǔ)到棧中 mov eax,dword ptr [esp-4] add eax,4 此時(shí)加的不在是1而是當(dāng)前指針?biāo)硎绢愋偷拇笮? mov dowrd ptr [esp-4],eax
常量數(shù)據(jù)類型
常量類型表示在程序運(yùn)行前便已久可以確認(rèn)的數(shù)據(jù),一般存儲(chǔ)在只讀數(shù)據(jù)區(qū),這塊內(nèi)存在頁(yè)的屬性上便是不可寫只可讀,所以對(duì)這段內(nèi)存的寫操作都會(huì)拋出內(nèi)存訪問異常。
常量舉例:如define所定義的常量,或者char* str = “ABC”;這種方式所定義的字符串。
注意const修飾符所修飾的變量并不意味著是在內(nèi)存層面上的常量,他僅僅是編譯器會(huì)在編譯過程中進(jìn)行檢測(cè),在程序運(yùn)行中完全可以通過取地址并修改的間接修改方式對(duì)其內(nèi)存數(shù)據(jù)進(jìn)行修改。
函數(shù)
在內(nèi)存的識(shí)圖中并沒有函數(shù)這一個(gè)說法只存在段的層級(jí)每個(gè)段都有自己的內(nèi)存屬性可讀可寫可執(zhí)行等待,函數(shù)的目的便是能夠?qū)⒛骋欢蝺?nèi)存明確的用一種概念來分開,而不至于將全部的代碼片段都混雜在一段內(nèi)存中而沒有明確的一個(gè)分界和定義。
函數(shù)簡(jiǎn)單的來看便是將一塊代碼封裝到一起。下面直接反匯編一個(gè)函數(shù)的調(diào)用看一下
首先要說明的是ebp代表了棧底指針,esp代表了棧頂指針
c代碼
int test(int a,int b){ return a+b; } int _tmain(int argc, _TCHAR* argv[]) { int a=10,b=1; int res = test(a,b); printf("%d",res); return 0; } 簡(jiǎn)單匯編代碼: int test(int a,int b){ 009D1A50 push ebp //同樣是保存和初始化堆棧 009D1A51 mov ebp,esp 009D1A53 sub esp,0C0h 009D1A59 push ebx 009D1A5A push esi 009D1A5B push edi 009D1A5C lea edi,[ebp-0C0h] 009D1A62 mov ecx,30h 009D1A67 mov eax,0CCCCCCCCh 009D1A6C rep stos dword ptr es:[edi] return a+b; 009D1A6E mov eax,dword ptr [a] //取出將a,b做合 009D1A71 add eax,dword ptr [b] //此處的a是ebp+4h,b是ebp+8h } 009D1A74 pop edi 009D1A75 pop esi 009D1A76 pop ebx 009D1A77 mov esp,ebp 009D1A79 pop ebp //回退堆棧 009D1A7A ret //返回 int _tmain(int argc, _TCHAR* argv[]) { 009D1AF0 push ebp //保存ebp 009D1AF1 mov ebp,esp //將棧底指向當(dāng)前棧頂 009D1AF3 sub esp,0E4h //提升堆棧 009D1AF9 push ebx //保存寄存器 009D1AFA push esi 009D1AFB push edi 009D1AFC lea edi,[ebp-0E4h] //初始化堆棧內(nèi)容 009D1B02 mov ecx,39h 009D1B07 mov eax,0CCCCCCCCh 009D1B0C rep stos dword ptr es:[edi] int a=10,b=1; //這里開始進(jìn)入我們?cè)趍ain中寫的代碼 009D1B0E mov dword ptr [a],0Ah //a其實(shí)是ebp-4h,這里將10存入到ebp-4,也就是棧底的第 一個(gè)4字節(jié)內(nèi)存 009D1B15 mov dword ptr [b],1 //這里同上b是ebp-8h,將1放入棧底開始的第二個(gè)4字節(jié)中 int res = test(a,b); //下面要注意,下面壓棧是從esp開始?jí)簵?,前面的通過ebp 所操作的賦值語(yǔ)句是將內(nèi)容存放到開始提升堆棧所占有的內(nèi)存 009D1B1C mov eax,dword ptr [b] //這里是取出1到eax 009D1B1F push eax //將eax壓棧 009D1B20 mov ecx,dword ptr [a] //取出10到ecx 009D1B23 push ecx //ecx壓棧 009D1B24 call func (9D126Ch) //調(diào)用我們的test方法此時(shí)可以看做一個(gè) jmp詳細(xì)的后續(xù)再講 009D1B29 add esp,8 //平衡傳入?yún)?shù)時(shí)提升的堆棧 009D1B2C mov dword ptr [res],eax //eax便是返回值 printf("%d",res); 009D1B2F mov esi,esp 009D1B31 mov eax,dword ptr [res] 009D1B34 push eax 009D1B35 push offset string "%d" (9D774Ch) 009D1B3A call dword ptr [__imp__printf (9DA40Ch)] 009D1B40 add esp,8 009D1B43 cmp esi,esp 009D1B45 call @ILT+435(__RTC_CheckEsp) (9D11B8h) return 0; 009D1B4A xor eax,eax }
從上面的例子可見函數(shù)的調(diào)用便是從代碼段中的一塊跳轉(zhuǎn)到另一塊去執(zhí)行,在執(zhí)行結(jié)束后再返回,
函數(shù)的參數(shù)是通過棧來傳遞的,在函數(shù)結(jié)束后要重新保證棧回退到調(diào)用函數(shù)之前的狀態(tài)。
其次call命令可以分為兩個(gè)部分
- 壓入當(dāng)前地址作為函數(shù)調(diào)用結(jié)束后回退時(shí)用
- jmp到對(duì)應(yīng)的位置(如果是跨段調(diào)用則是jmp far)
函數(shù)調(diào)用的約定分為三類
- stdcall:標(biāo)準(zhǔn)的winapi調(diào)用約定平棧操作交給函數(shù)自行處理,通過ret arg來實(shí)現(xiàn)
- cdecl:c語(yǔ)言調(diào)用約定,平棧操作交給調(diào)用方實(shí)現(xiàn),也就是上面例子中的調(diào)用
- fastcall:參數(shù)通過寄存器傳遞,如eax,ebx
結(jié)構(gòu)體和類
結(jié)構(gòu)體就是將一系列數(shù)據(jù)整合到一起的一塊內(nèi)存,下面通過例子來看一下
struct test_struct{ int a; char b; int c; }; int _tmain(int argc, _TCHAR* argv[]) { struct test_struct s; s.a = 10; s.b = 11; s.c = 12; test(&s); return 0; }
首先建立了一個(gè)結(jié)構(gòu)體有三個(gè)參數(shù)
先來看一下結(jié)構(gòu)體在內(nèi)存中的存儲(chǔ)方式
int _tmain(int argc, _TCHAR* argv[]) { 。。。。。。。 struct test_struct s; s.a = 10; 00E524DE mov dword ptr [s],0Ah //這里的s可以簡(jiǎn)單看為ebp-4 s.b = 11; 00E524E5 mov byte ptr [ebp-0Ch],0Bh s.c = 12; 00E524E9 mov dword ptr [ebp-8],0Ch test(&s); 00E524F0 lea eax,[s] //lea為取地址的指令,前面我們也遇到過 00E524F3 push eax //將這個(gè)地址作為參數(shù)傳遞 00E524F4 call test (0E511B8h) 00E524F9 add esp,4 return 0; 00E524FC xor eax,eax 。。。。。 }
可以看出來結(jié)構(gòu)體在內(nèi)存中的存儲(chǔ)方式便是將數(shù)據(jù)按順序排放在內(nèi)存中并根據(jù)字段類型的大小計(jì)算偏移量來取得對(duì)應(yīng)的字段內(nèi)容
如果我們直接將struct關(guān)鍵字改為class看看會(huì)不會(huì)出錯(cuò)
class test_struct{ public: int a; char b; int c; }; void test(test_struct* s){ printf("%d",s->a); } int _tmain(int argc, _TCHAR* argv[]) { test_struct s; s.a = 10; s.b = 11; s.c = 12; test(&s); return 0; }
改后的代碼,完全可以運(yùn)行
并且如果看返匯編的話會(huì)發(fā)現(xiàn)匯編代碼也沒有變化
下面我們將函數(shù)放到class中看一下匯編是否會(huì)有變化
class test_struct{ public: int a; char b; int c; void test(test_struct* s){ printf("%d",s->a); } }; int _tmain(int argc, _TCHAR* argv[]) { test_struct s; s.a = 10; s.b = 11; s.c = 12; s.test(&s); return 0; } 匯編只看main這部分的代碼 int _tmain(int argc, _TCHAR* argv[]) { 。。。。。 test_struct s; s.a = 10; 00D3339E mov dword ptr [s],0Ah s.b = 11; 00D333A5 mov byte ptr [ebp-0Ch],0Bh s.c = 12; 00D333A9 mov dword ptr [ebp-8],0Ch s.test(&s); 00D333B0 lea eax,[s] 00D333B3 push eax 00D333B4 lea ecx,[s] 00D333B7 call test_struct::test (0D311D6h) return 0; 00D333BC xor eax,eax 。。。。。。。。 }
注意 lea ecx,[s] 這段代碼,這個(gè)ecx便是所謂的this指針,通過編譯器將結(jié)構(gòu)體自己的地址作為參數(shù)傳入函數(shù)這樣就可以通過this符號(hào)訪問結(jié)構(gòu)體自己了。其余的部分完全沒有變化,調(diào)用class的函數(shù)時(shí)也是通過地址調(diào)用的。
注意:數(shù)據(jù)在內(nèi)存中的存儲(chǔ)還取決于數(shù)據(jù)對(duì)齊,這部分的知識(shí)在我前面的筆記中有詳細(xì)解析
面向?qū)ο蟮奶匦?/h2>
面向?qū)ο蟮奶匦杂?/p>
- 封裝
- 繼承
- 多態(tài)
封裝在上一塊我們已經(jīng)看過了,便是將操作數(shù)據(jù)的算法和存放數(shù)據(jù)的結(jié)構(gòu)體封裝到一起來調(diào)用,真正的實(shí)現(xiàn)通過編譯器來實(shí)現(xiàn)。
下面說一下
字符編碼
計(jì)算機(jī)中的存儲(chǔ)是以字節(jié)為單位的,能反映的也僅僅是數(shù)字而已,為了能夠用數(shù)字將文字信息反映出來人們?cè)O(shè)計(jì)出了各種字符編碼表,將數(shù)字與文字對(duì)應(yīng)。
1、ASCI編碼
1.原始Ascii編碼
原始ASCI使用1到127(0X00~0X7F)來對(duì)應(yīng)常用的一些字母等文本,127到255則是擴(kuò)展到一些不常用的類似于=號(hào)這種內(nèi)容。
但原始ASCI所支持的字符僅僅能夠反映英文國(guó)家的使用場(chǎng)景。
2.ASCI擴(kuò)展編碼
對(duì)于某些地區(qū)是無法用原始ASCI編碼來反映當(dāng)?shù)氐恼Z(yǔ)言的,所以就有了ASCI擴(kuò)展編碼的這種形式。
擴(kuò)展ASIC編碼將不常用的127到255的(0x80~0xFF)位置采用兩個(gè)數(shù)字對(duì)應(yīng)一個(gè)文字的方式
如:可能128和129代表一個(gè)中,129和130代表一個(gè)國(guó)
呢么中國(guó)對(duì)應(yīng)的編碼就是:0x 8081 8182
例如國(guó)內(nèi)常用的GBK、GB2312和臺(tái)灣的big5等編碼方式都是采取的此類
但是這種編碼方式有一個(gè)問題,就是他占用了ASCI表的127到255的位置并且不同的地區(qū)這部分的編碼均不一樣,呢么國(guó)內(nèi)的中文文件發(fā)到國(guó)外采用了不同的編碼去讀取則會(huì)出現(xiàn)亂碼
2、Unicode編碼
Unicode編碼就是為了解決ASCii擴(kuò)展碼在不同地區(qū)的實(shí)現(xiàn)下解碼后對(duì)應(yīng)不同的文字這個(gè)問題
Unicode編碼將全世界常用的符合都構(gòu)建到一個(gè)表中,這個(gè)表的范圍是:0x10 FF FF到0
Unicode僅僅提供了一個(gè)表,他并沒有對(duì)存儲(chǔ)做過多的要求,而下面所說的utf-8和utf-16以及現(xiàn)在的utf-32則是對(duì)Unicode編碼存儲(chǔ)方式的不同實(shí)現(xiàn)
1.unicode編碼實(shí)現(xiàn)(utf-8)
utf-8較之utf-16在存儲(chǔ)上更為復(fù)雜,但是所占空間是更小的,這也是網(wǎng)絡(luò)傳輸大多為utf-8的格式的原因
存儲(chǔ)規(guī)則:
如果目標(biāo)符合在Unicode中為:0到00007F則會(huì)被編碼為:0xxx xxxx
如果目標(biāo)符合在Unicode中為:80到00007FF則會(huì)被編碼為:110xx xxx 10xx xxxx
如果目標(biāo)符合在Unicode中為:800到00FFFF則會(huì)被編碼為:1110 xxxx 10xx xxxx 10xx xxxx
如果目標(biāo)符合在Unicode中為:10000到10FFFF則會(huì)被編碼為:1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
2.unicode編碼實(shí)現(xiàn)(utf-16)
utf-16名如其意,就是以兩個(gè)byte為單位進(jìn)行存儲(chǔ)。
舉例:如果說在Unicode編碼中 中對(duì)應(yīng)0x10 61 62
那么采用utf-16存儲(chǔ)在文件中的byte就是 0x 0010 6162
到此這篇關(guān)于C語(yǔ)言逆向分析語(yǔ)法超詳細(xì)分析的文章就介紹到這了,更多相關(guān)C語(yǔ)言逆向分析語(yǔ)法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Linux/Manjaro如何配置Vscode的C/C++編譯環(huán)境
這篇文章主要介紹了Linux/Manjaro配置Vscode的C/C++編譯環(huán)境,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05C++實(shí)現(xiàn)LeetCode(129.求根到葉節(jié)點(diǎn)數(shù)字之和)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(129.求根到葉節(jié)點(diǎn)數(shù)字之和),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++中關(guān)于=default和=delete問題
這篇文章主要介紹了C++中關(guān)于=default和=delete問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07C++模板以及實(shí)現(xiàn)vector實(shí)例詳解
模板是為了實(shí)現(xiàn)泛型編程,所謂泛型編程,就是指編寫與類型無關(guān)的代碼,下面這篇文章主要給大家介紹了關(guān)于C++模板以及實(shí)現(xiàn)vector的相關(guān)資料,需要的朋友可以參考下2021-11-11C語(yǔ)言中的運(yùn)算符優(yōu)先級(jí)和結(jié)合性一覽表
這篇文章主要介紹了C語(yǔ)言中的運(yùn)算符優(yōu)先級(jí)和結(jié)合性一覽表,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02淺析C++中memset,memcpy,strcpy的區(qū)別
本篇文章是對(duì)C++中memset,memcpy,strcpy的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07C語(yǔ)言中關(guān)于sizeof 和 strlen的區(qū)別分析
本文通過示例簡(jiǎn)單分析了4種情況下C語(yǔ)言中sizeof 和 strlen的區(qū)別,算是個(gè)人經(jīng)驗(yàn)的一個(gè)小小的總結(jié),如有遺漏還請(qǐng)大家告知。2015-02-02