1. 程式人生 > >PC逆向之代碼還原技術,第一講基本數據類型在內存中的表現形式.浮點,指針尋址公式

PC逆向之代碼還原技術,第一講基本數據類型在內存中的表現形式.浮點,指針尋址公式

一次 char s 地址 們的 進行 發現 爆破 註冊碼 編碼轉換

目錄

  • 代碼還原技術
    • 一丶簡介代碼還原
    • 二丶代碼還原中的數據類型表現形式
      • 1.整數類型
      • 2.無符號整數
      • 3.有符號整數
      • 4.浮點數數據類型
      • 5.浮點編碼
      • 4.Double類型解析.
    • 三丶浮點匯編
      • 1.浮點棧
      • 2.浮點匯編
      • 3.使用內聯浮點匯編實現加法
    • 四丶布爾類型
    • 地址丶指針丶引用表達形式

代碼還原技術

一丶簡介代碼還原

  • 例子一:我們很多人都學習過匯編.但是匯編的核心知識就是我能看的懂.有人拿匯編去做外掛.比如我去追偏移.看著視頻去做.然後換一個遊戲依然這樣.但是終有一天,你可能發現沒意思了.因為這些知識都是死的.比如我們想看遊戲中,這段代碼做了什麽事情.這個時候就需要將匯編轉為高級代碼查看了. IDA的F5插件.一般能做到.但是很多是做不到的. 比如遊戲中.這段代碼你找到一個對象+多少偏移是什麽什麽功能.但是會逆向的人.這段代碼摳出來.轉為高級代碼.一看.原來這個意思.+多少是什麽作用.另外還實現了什麽功能.這個就是核心技術了.為什麽別人的外掛功能比較多.你的比較少.其核心就在這裏.
  • 例子二:算法逆向,如一個軟件.讓你追出註冊碼.你可能就爆破.但是如果你能把它算法逆出來.那麽是不是第一提升了自己,第二,自己可以寫註冊機專門為這個程序生成註冊碼了.
  • 例子三: 如果你是為公司工作.可能某一天,公司需要你進行逆向.發現xx軟件的一個功能比較好.此時你需要怎麽辦.完整的根據匯編去逆向出來這個功能.並且讓公司去做出這個功能.這個也是一個很好的例子.

二丶代碼還原中的數據類型表現形式

上面說了很多了,那麽真正的開始篇幅講解.

1.整數類型

C++中整數的基本數據類型有三種, int long short. 在 VC6.0中,int long所占內存都是4字節.
short兩個字節. 以16進制為例 int long 分別就是4個字節. short兩個字節. 一個字節是8位.

2.無符號整數

在內存中,無符號整數是用來表示數值的.如果32位下.那麽取值範圍是 0x00000000~0xFFFFFFF
10進制: 0~4294967295,因為無符號數,那麽最高位就是0填充.所以表示數值比較大.

3.有符號整數

有符號整數跟上面無符號整數一樣.只不過高位用來表示符號位,其余低位表示數值.這樣有符號的整數.表示的數值就只有31位了.範圍則是 0x80000000~0x7FFFFFFF 轉為十進制: -2147483648~ 2147483647
因為最高位是符號位,可以表示 負數. 例如 -3
在內存中負數都是補碼形式表示的

補碼規則: 補碼規則則是用0 - 去這個數的絕對值

例如: 0 - 3 的結果就是 -3在內存中的表現形式.
因為補碼高位為1,要轉為真值也是 0 - 補碼的形式. 但是一般計算機計算的話,通常都是用補碼取反+1進行獲得真值. 前邊帶上符號即可.

為什麽負數取值總比整數取值多一個值.
例如如上:
-2147483648~2147483647
原因:
對於四個字節補碼 0x80000000 代表的是-0. 但是對於0來講. 正負區分沒必要.所以0x800000000規定了就是4字節補碼最小值了.所以這也是負數比正數多一位的原因.

4.浮點數數據類型

關於浮點數存儲.科學上有很多爭議.有很多存儲實數(小數)的方式.不過很少用了.所以我們也不再介紹了
現在是不管如何存儲.都分為 定點實數存儲 跟 浮點數實數存儲 這兩種方式

  • 定點實數存儲
    定點實數存儲,就是約定整數位和小數位的長度.比如4個字節為例,高2個字節存儲整數.低兩個字節存儲實數.這樣的好處是計算的效率高,缺點是存儲不靈活.比如存儲65536.5 整數部分已經存儲不了65536了.
  • 浮點實數存儲
    浮點實數存儲就是用一部分二進制位存放小數點的位置信息,我們可以稱之為指數域其它的數據位用來存儲沒有小數點時的數據和符號,我們可以稱之為數據域丶符號域
    如:
    67.625 我們可以使用浮點實數存儲, 數據域 可以存放67625 小數位置可以記住位置為 10~-3次方 ,對這個數進行訪問的時候.只需要計算一下即可.
    優缺點: 優點缺點跟第一種是相反的. 80286CPU之前,程序員常常為實數的計算,傷腦筋.最後出來了浮點協處理器.可以協助CPU計算.程序員計算實數的效率就大大的提高了.於是現在 浮點存儲的方式就推廣了出來
    註意: 現在都是第二種方法進行存儲的.不是定點存儲方式了
  • C++中的浮點
    在C++當中,有浮點數 float 以及 double用來存儲浮點數. float 4個字節. double 8個字節.
    由於double空間大,所以精度高. 兩種數據類型在內存中同樣的是16進制存儲.但是與10進制的16進制不同. float dobule 的16進制比較大.
    原因:浮點類型並不是將一個浮點小數直接轉為二進制進行存儲的.而是將浮點小數轉換成二進制,重新編碼.再進行存儲.C/C++中的浮點數是有符號的.

值得註意 浮點數轉為整數,並不是四舍五入.而是向0取整. 也就是說舍棄小數位.轉為整數.
例如: a = 3.78; int b = (int) a; 此時b的值是3. 而不是傳統意義上的 4; 因為不是四舍五入.

5.浮點編碼

  • 浮點編碼轉換.
    我們上面說了,浮點數是重新進行編碼進行存儲的.所以我們只要搞明白了編碼.那麽就可以自己算出浮點數在內存中怎麽表示.或者反轉回來.16進制怎麽轉換為浮點數
  • 浮點編碼采用的是 IEEE規定的編碼.
    float double轉換方式一樣. 都是因為表示範圍不一樣.所以編碼方式有些特別.
  • 1.浮點編碼的編碼方式
    浮點編碼,會將一個浮點數轉為二進制數.以科學計數法進行區分.分為三部分
    1.符號域
    2.指數域
    3.尾數域
    如下圖所示:
    技術分享圖片

最高位是符號位,表示正負
去掉符號位往後數8位 是指數域.
最後的23位則表示尾數.

1.正數浮點轉為十六進制表示

  • 2.浮點數轉為16進制存儲
    現在我們要把浮點數轉為十六進制存儲在內存裏.轉換步驟
    1.將一個浮點數轉化為二進制

    例如:12.25 轉為2進制 = 1100.01
    整數直接轉為二進制即可. 小數不斷 * 2 取整.
    例如: 0.25
    0.25 * 2 = 0.5 取整 = 0
    0.5 * 2 = 1.0 取整就是1
    所有12.25 轉為二進制表示就是 1100.01

2.計算指數位
計算指數位首先移動小數點位置到符號位置除最高位為1的地方.
也就是符號位也好.不是符號位也好.移動到最高位為1的地方. 7.25 轉換之後是
0111.01 移動到最高位則是 1.1101.

1100.01 移動 1.10001 總共移動了3位.每次移動一位,指數+1
因為指數為移動了三位.所以 3 + 127(8位) = 130 轉為二進制 10000010 這個就是指數位.
也就是上圖中所說的符號位後面數8位是指數位. 我們上邊計算的就是指數位的值.
為什麽 +127.因為可能會出現負數.十進制127可以表示二進制的01111111. IEEE浮點編碼規定
當指數域< 0111111的時候,就是一個負數.如果大於01111111的時候就是一個正數. 所以01111111為0. IEEE浮點編碼規定的.所以只要記住即可. 127即可. 也可以理解為指數域是8位,表示的數值是128.但IEE規定了.所以-1 指數最大值 - 1即可.

3.計算尾數位
經過上面計算我們符號是1,但是符號位基本不變.因為是正數浮點.所以符號位為0:
指數位為: 130 10000010
現在計算尾數位. 尾數位就是我們移動小數點之後的數值

1.10001 尾數位就是 10001,但是他不組23位.所以我們補0填充.

1000 1000 0000 0000 0000 000 補0之後,我們需要從左到右,按照4個字節分開
100 0100 0000 0000 0000 0000 分開之後.
此時加上我們之前的符號位以及指數位

0100 0001 0100 0100 0000 0000 0000 0000 這是拼接好的.我們轉換為16進制進行存儲
0x41440000 那麽在內存中,我們的浮點數12.25 其實就是16進制 0x41 44 00 00 進行存儲的.
技術分享圖片

2.負數浮點轉為十六進制表示.

負數跟上邊一樣.一樣計算指數位.也是分為以下步驟
1.轉為科學計數法.
2.移動指數位.
3.計算指數位
4.尾數位補零到23位.
5.拼接進行二進制,並且二進制轉為16進制.

1.轉為科學計數法
-0.125 = 0.001
2.移動指數位
此時移動指數位是往小數點右邊移動,移動到最高位為1的地方.
0.001 =>1.0 移動了三位,計算 -3. 往右邊移動就是負數
1.0 則符號位是1代表負數. 指數位是負數.

3.計算指數位.
上面我們計算指數位是往小數點左邊移動.所以指數位去相加.現在是往右邊移動.所以相減
127-3 = 124 轉為二進制 = 01111100
4.尾數位補零
0000 0000 0000 0000 0000 000
5.符號位 指數位 尾數位 進行拼接

1011 1110 0000 0000 0000 0000 0000 0000 (總共32位)
轉為16進制
0xBE00 0000
所以-0.125 在內存中的16進制則是 0xBE000000

3.正數浮點16進制轉為浮點數解析

我們會轉換為16進制那麽也要回轉換回來
1.16進制拆分為2進制.
2.分出符號位 指數位 尾數位
3.求指數位是負數還是整數
4.移動指數位

比如我們的12.25f. 十六進制是x41440000
1.16進制轉為2進制
0100 0001 0100 0100 0000 0000 0000 0000
2.分出符號位 指數位 尾數位
0 10000010 10001000000000000000000
3.求出指數位
指數位位 10000010 > 01111111 所以可以判斷我們的小數是正數
10000010 - 01111111 = 130 - 127 = 3;
得出了我們要移動的位數
4.移動指數位
首先計算出的指數轉為2進制 3 = 0011;

然後反過來.尾數位右移動三位.如下.
100010 >> 3 = 100.010 尾數的最高位不需要.所以補零

然後加上符號位.
符號位為0.代表是正數.所以+1
1100.010
再舉個例子
7.25 在內存的16進制為0x40E8
1.16進制轉為二進制
0100 0000 1110 1000 0000 0000 .....
2.計算指數位,尾數需要往右移動紀委
10000001 - 01111111 = 129 - 127 = 2;得出移動2位
3.移動尾數位
因為高位為0,所以代表我們轉換的浮點數是正數.最後我們的高位要加上1才可以.
11010 ==>2位 = 11.010
符號位為0.所以高位補1
111.01 這個二進制在轉換為10進制得出7.25

小數轉為10進制:
.01是是兩位. 分別記位 2的-1次方 2-2次方.
第一位計算: 0 * 1/2-1次方.
第二位計算: 1 * 1/2-2次方即可.
最後結果相加.
如果有三位.那麽就是 用第三位數值 * 1/2-3次方即可.
01/2 + 1 1/4 = 0.25.所以我們可以推算出是0.25

4.Double類型解析.

double類型轉換跟float一樣.只不過指數位變成了11位. 剩余的42位表示尾數位.

2~11次方 - 1;就是用於計算的指數.也就是 1023.

三丶浮點匯編

1.浮點棧

因為有了浮點協處理器.所以浮點指令的操作有點不同.它是通過浮點寄存器來實現的.
浮點寄存器是通過棧結構來實現的.也稱作浮點棧. 由 st(0) - st(7); 其中寫st默認就是st(0)
操作任意浮點棧就需要加上序號 st(7);
值得註意的是浮點棧是循環棧. 也就是說st(0)出棧的數據.會放到st(7)中.這樣依次使用.

2.浮點匯編

針對協處理器.也提供的相應的匯編進行操作.
分別是 fld類指令 fst 指令. 以及 fcom fadd等指令
都是大寫

  • 壓棧指令
    FLD IN 將浮點數IN 壓入浮點棧
    FILD IN 將整數壓入浮點棧 mem32/64 80
    FLDZ 默認壓入0,浮點棧是0
  • 出棧指令
    FST OUT 將浮點棧頂(st(0))的值給OUT存儲. out可以是 mem32/64,但是不出棧
    FSTP OUT 同FST out保存值,但是會出棧.
    FISTP OUT 出棧,並且以整數的形式給OUT存儲.

  • 棧比較
    也可以進行棧中的值比較.用來更改標誌位.
    FCOM IN 將IN地址的內容.與浮點棧頂比較(st(0));
    FTST 比較棧頂(st(0)); 是否為空.
  • 浮點加法
    FADD IN 將St(0)的數據於in做加法. 值保存在 棧頂st(0);中.
    FADDP st(N),st 將st(n)棧中的數據於st(0)中的數據進行運算.浮點棧有7個.那麽N的取值就是0~7;
    先執行一次出棧冬棗.然後相加結果放在 st(0)中存儲.

3.使用內聯浮點匯編實現加法

  • 浮點做加法
int main(int argc, char* argv[])
{
    float a = 11.25;
    float c = 12.35;
    float d = 13.25f;
    float b = 0.0f;
    __asm{
        fld dword ptr[ebp - 0x4];
        fld dword ptr[ebp - 0x8];
        fld dword ptr[ebp - 0xc];
        faddp st(1),st(0)
        fstp  dword ptr[ebp - 0x10];
    }
    printf("%f \r\n",b);
    
    return 0; 
}

實現結果:
技術分享圖片

  • 浮點做返回值
float GetFloatValue()
{   
    return 12.25f;

}
int main(int argc, char* argv[])
{
    int value = GetFloatValue();
    
    return 0; 
}

觀看匯編,匯編分為兩層.一層是調用內.一層是調用外.
調用內: 也就是GetFloatValue()函數內部.

   push        ebp
   mov         ebp,esp
   sub         esp,40h
   push        ebx
   push        esi
   push        edi
   lea         edi,[ebp-40h]
   mov         ecx,10h
   mov         eax,0CCCCCCCCh
   rep stos    dword ptr [edi]
   fld         dword ptr [__real@4@4002c400000000000000 (00423fd0)]

主要看最後一樣. fld 內存的值. 其實就是把我們的浮點數轉為IEE編碼.放到內存中.
其實就是放到內存中.

外層調用: 就是調用完畢之後.

0040EB1D   call        __ftol (004010ec)
0040EB22   mov         dword ptr [ebp-4],eax

調用完畢之後,會使用 _ftol. 浮點數轉為整數進行轉化.下面的返回值放到我們的局部變量中
所以以後看到這樣操作.我們就要明白. 返回值是float或者double類型.進行了轉換.
_ftol內部

004010EC   push        ebp
004010ED   mov         ebp,esp
004010EF   add         esp,0F4h
;浮點異常檢查
004010F2   wait
004010F3   fnstcw      word ptr [ebp-2]
004010F6   wait
004010F7   mov         ax,word ptr [ebp-2]
004010FB   or          ah,0Ch
004010FE   mov         word ptr [ebp-4],ax
00401102   fldcw       word ptr [ebp-4]
;從str(0)中取出八個字節放到局部變量 ebp -och中. 所以後面是qword ptr代表8個字節.
;將st(0);從棧中彈出.
00401105   fistp       qword ptr [ebp-0Ch]
00401108   fldcw       word ptr [ebp-2]
;下方 eax edx同用,eax保存4字節的整數部分. edx則保存小數部分.
0040110B   mov         eax,dword ptr [ebp-0Ch]
0040110E   mov         edx,dword ptr [ebp-8]
平展返回.
00401111   leave
00401112   ret

內部則是進行浮點轉化.比較等等.

四丶布爾類型

布爾類型就是0 跟 1 表示.在內存中就是這樣的表示形式. 0就是 false 1就是true

地址丶指針丶引用表達形式

  • 地址
    在C++中,使用地址需要使用&取地址符號. 取一個變量所在的內存地址.
  • 指針
    指針的本質就是存儲地址的.只不過有類型一說.表示我已什麽方式存儲這個地址. 比如
    char szBuff[10] = {1,2,3...};
    char sz = szBuffer; 那麽sz保存的是szBuffer的地址.只不過a按照1個字節解釋.
    比如 sz++,因為是char類型.所以地址就是+1, 如果是int類型解釋+1就是+4個字節.
    如果對其去內容
    sz那麽此時的值是2.因為是char 類型解釋的地址 sz++ sz就是3
  • 引用
    在C++中,創建引用 TYPE & a = szBuffer; 創建引用的時候必須給變量給初始化.
    本質就是一個變量的別名.在內存中其實就是對地址 取內容的操作.

    1.指針的尋址方式

    關於指針.我們說過有不同的表達形式. 例如 BYTE * short ...
    因為指針有不同的表達形式.所以自增自減都會產生偏移計算.
    例如:
    mov eax,byte ptr[ebp - 0xc];
    mov ebx,byte ptr[ebp - 0xb];
    mov ecx,byte ptr[ebp - 0xa];
    ....
    所以我們可以總結一條尋址公式
    目的地址 = 首地址 + sizeof(type)
    n的值.

目的地址就是我們要進行尋址目的.
sizeof(type) 就是你的數據類型大小
n的值就是你的偏移量.
例如一個數組:
char szBuf[10] = {1,2...7,8,9,10};
我們想要得到下標為8的位置的的值怎麽獲得.
高級代碼: int a = szbuf[8];完了.
因為有公式,我們可以不用這樣寫.
寫成如下:
目的地址 = 首地址 + sizeof(type) *n; 套公式

szbuffer = szbuffer + sizeof(char) * 8;
此時szBuffer的地址就是指向數組下表為8的位置.我們對其取內容即可獲取其值.

int main(int argc, char* argv[])
{
    char szBuf[10] = {1,2,3,4,5,6,7,8,9,10};
    char *dst = szBuf + sizeof(char) * 8;
    printf("Value = %d\r\n",*dst);
    system("pause");
    
    return 0; 
}

技術分享圖片

PC逆向之代碼還原技術,第一講基本數據類型在內存中的表現形式.浮點,指針尋址公式