學習過計算機的讀者都知道,計算機中的各種資料都是以二進制形式進行存儲的,無論是文本檔案、圖檔檔案,還是音頻檔案、視訊檔案、可執行檔案等,統統都是由二進制檔案存儲的。學習過計算機的讀者在學習計算機基礎的時候一定學習過進制轉換,也一定學習過資料的表示方式等,大部分人在學習這部分知識時會覺得枯燥、無用,但是對于學習逆向知識和使用逆向工具,資料的存儲及表示形式是必須要掌握的。
本文借助OllyDbg這款調試工具來一起讨論資料的存儲及表示形式,讓讀者對于學習計算機的資料存儲及表示可以更加的感性,進而脫離純粹理論性的學習。
本文内容較為枯燥,但是着實是學習逆向的基礎知識,對于從來沒有接觸過逆向或者是剛開始接觸逆向的讀者,本文内容還是有一定幫助的。
本文關鍵字:進制 資料表示 資料轉換 資料存儲
1.1 進制及進制的轉換
了解進制的概念及進制的轉換是學習逆向的基礎,因為計算機使用的進制是二進制,它又不同于我們現實生活中使用的十進制,是以我們必須學習不同的進制及進制之間的轉換。
1.1.1 現實生活中的進制與計算機的二進制
我們在現實生活中會接觸到多種多樣的進制,通常見到的有十進制、十二進制和二十四進制等。下面分别對這幾種進制進行舉例說明。
十進制是每個人從上學就開始接觸和學習的進制表示方法。所謂的十進制,就是逢十進一,最簡單的例子就是9+1=10。這個無需過多解釋。
十二進制也是我們日常生活中常見的表示方法。所謂的十二進制,就是逢十二進一,例如12個月為1年,13個月就是1年1個月。
二十四進制也是我們日常生活中常見的表示方法。所謂的二十四進制,就是逢二十四進一,例如24小時為1天,25小時就是1天1小時。
介紹了以上現實生活中的例子後,我們再來說說計算機中的二進制。根據前面各種進制的解釋,我們可以想到,二進制就是逢二進一。這裡舉個不太恰當的例子,例如2斤就是1公斤。
在計算機中為什麼使用二進制呢?簡單說就是計算機用高電平和低電平來表示1和0最為友善和穩定,高電平被認為是1,低電平被認為是0,這就是所謂的二進制的來源。
由于二進制在閱讀上不友善,計算機又引入了十六進制來直覺地表示二進制。所謂的十六進制,就是逢十六進一。
是以在計算機中,我們常見的資料表示方法有二進制、十進制和十六進制。
1.1.2 進制的定義
在學習國小數學的時候我們就學習了十進制,十進制一共有十個數字,從0一直到9,9再往後數一個的時候要産生進位,也就是逢十進一。總結十進制的定義則是,由0到9十個數字組成,并且逢十進一。
舉一反一地來說,二進制的定義是,由0到1兩個數字組成,逢二進一。十六進制的定義是由0到9十個數字和A到F六個字母組成,逢十六進一。
由此,我們衍生出N進制的定義是,由N個符号組成,逢N進一。
表1-1所列為這三種進制的數字表。
表1-1 二進制、十進制和十六進制數字表
數 制 | 基 數 | 數 字 |
二進制 | 2 | 0 1 |
十進制 | 10 | 0 1 2 3 4 5 6 7 8 9 |
十六 進制 | 16 | 0 1 2 3 4 5 6 7 8 9 A B C D E F |
1.1.3 進制的轉換
在逆向當中,我們直接面對的通常是十六進制,而由于很多原因,我們需要将其當作十進制或二進制來檢視,當然也有可能需要根據二進制轉換成十六進制或十進制。是以,我們就需要掌握進制之間的轉換。
1.二進制轉十進制
二進制整數的每個位都是2的幂次方,最低位是2的0次方,最高為是2的(N-1)次方,我們通過一個例子進行說明。我們把二進制數10010011轉換成十進制數,計算方式如下:
10010011 = 1 × 27 + 0 × 26 + 0 × 25 + 1 × 24 + 0 × 23 + 0 × 22 + 1 × 21 + 1 × 20 = 128 + 0 + 0 +16 + 0 + 0 + 2 + 1 = 147
我們得出的結果是,把二進制10010011轉換成十進制後是147。我們用計算機進行驗算,如圖1-1和圖1-2所示。
從圖1-1和圖1-2中可以看出,我們的計算結果是正确的,由此讀者在計算二進制時按照上面轉換的例子進行轉換即可。

圖1-1 驗算二進制(一)
圖1-2 驗算二進制(二)
2.十六進制與二進制的轉換
由于一個簡單的數值用二進制表示需要很長的位數,這樣對于閱讀很不友善,是以彙編和調試器常用十六進制表示二進制。十六進制的每個位可以代表4個二進制位,因為2的4次方剛好是16。這樣,在二進制與十六進制之間就産生了一個很好的對應關系,如表1-2所列。
表1-2 二進制對應的十六進制與十進制數
二進制 | 十進制 | 十六進制 | 二進制 | 十進制 | 十六進制 |
0000 | 0110 | 6 | 6 | ||
0001 | 1 | 1 | 0111 | 7 | 7 |
0010 | 2 | 2 | 1000 | 8 | 8 |
0011 | 3 | 3 | 1001 | 9 | 9 |
0100 | 4 | 4 | 1010 | 10 | A |
0101 | 5 | 5 | 1011 | 11 | B |
1100 | 12 | C | 1110 | 14 | E |
1101 | 13 | D | 1111 | 15 | F |
根據此表,我們可以很快地把二進制和十六進制進行轉換,把上例的二進制10010011轉換成十六進制,轉換過程如下:
第一步,把10010011從最低開始按每四位分為一組,不足四位前面補0,劃分結果為1001 0011;
第二步,把劃分好的組進行查表,1001對應十六進制是9,0011對應的十六進制是3。
那麼,二進制10010011轉換成十六進制後的值是93。讀者可以通過電腦自行進行驗算。
在逆向中常用的就是二進制與十進制的轉換,或者是二進制與十六進制的轉換,其他的轉換方式讀者可以自行查找資料進行學習。關于十六進制和二進制需要記住的重要一點就是,一位十六進制數可以表示四位二進制數。
1.2 資料寬度、位元組序和ASCII碼
前面介紹了計算機中常用的進制表示方法和轉換,現在讀者知道了計算機存儲的都是二進制的資料,那麼接下來要讨論的是在計算機中資料存儲的機關以及資料是如何存儲在存儲空間的。
1.2.1 資料的寬度
資料的寬度是指資料在存儲器中存儲的尺寸。在計算機中,所有資料的基本存儲機關都是位元組(byte),每個位元組占8個位(位是計算機存儲的最小機關,而不是基本機關,因為在存儲資料時幾乎沒有按位進行存儲的)。其他的存儲機關還有字(word)、雙字(dword)和八位元組(qword)。
圖1-3 給出各個存儲機關所包含的位數。
在計算機程式設計中,常用的幾個重要資料存儲機關分别就是byte、word和dword,這幾個存儲機關稍後我們會使用到。
1.2.2 數值的表示範圍
在計算機中存儲數值時,也是要依據前面介紹過的資料寬度進行存儲的,那麼在存儲資料時由于存儲資料的寬度限制,數值的表示也是有範圍限制的。那麼byte、word和dword能存儲多少資料呢?我們先來計算一下,如果按位存儲的話,能存儲多少個資料,再分别來計算以上三種機關能夠存儲的數值的範圍。
計算機使用二進制進行資料存儲時,一位二進制最多能表示幾個數呢?因為是二進制數,隻存在0和1兩個數,是以一位二進制數最多能表示兩個數,分别是0和1。那麼,兩位二進制最多能表示幾個數呢?因為一位二進制數能表示兩個數,是以兩位二進制數則能表示2的2次方個數,即4個數,分别是0、1、10、11。進一步地,三位二進制數能表示的就是2的3次方個數,即8個數,分别是0、1、10、11、100、101、110、111。
上面的過程可以整理成表1-3。
表1-3 N位二進制位能夠表示的數
二進制位數 | 表示數的個數 | 表示的數 | 2的N次方 |
1 | 2 | 0、1 | 2的1次方 |
2 | 4 | 0、 1、 10、 11 | 2的2次方 |
3 | 8 | 0、1、10、11、100、101、110、111 | 2的3次方 |
根據表1-3計算的byte、word和dword三種資料存儲寬度能表示的資料的範圍如表1-4所列。
表1-4 無符号整數的表示範圍
存儲 機關 | 十進制範圍 | 十六進 制範圍 | 2的N次方 |
byte | 0~255 | 0~FF | 2的8次方 |
word | 0~65535 | 0~FFFF | 2的16次方 |
dword | 0~4294967295 | 0~FFFFFFFF | 2的32次方 |
2的8次方是256,為什麼數值隻有0~255個呢?因為計算機計數是從0開始,從0到255同樣是256個數,這裡的2的8次方表示能夠表示數值的個數,而不是能夠表示數值的最大的數。
1.2.3 位元組序
位元組序也稱為位元組順序,在計算機中對數值的存儲有一定的标準,而該标準随着系統架構的不同而不同。了解位元組存儲順序對于逆向工程是一項基礎知識,在動态分析程式的時候,往往需要觀察記憶體資料的變化情況,這就需要我們在掌握資料的存儲寬度、範圍之後,進一步了解位元組順序。
通常情況下,數值在記憶體中存儲的方式有兩種,一種是大尾方式,另一種是小尾方式。關于位元組序的知識,通過一個簡單的例子就可以掌握。
比如有0x01020304(C語言中對十六進制數的表示方式)這樣一個數值,如果用大尾方式存儲,其存儲方式為01 02 03 04,而用小尾方式進行存儲則是04 03 02 01,用更直覺的方式展示其差別,如表1-5所列。
表1-5 位元組順序對比表
大尾方式 | 小尾方式 | ||
資料 | 位址值 | 資料 | 位址值 |
01 | 00000000H | 04 | 00000000H |
02 | 00000001H | 03 | 00000001H |
03 | 00000002H | 02 | 00000002H |
04 | 00000003H | 01 | 00000003H |
從兩個位址列可以看出,位址的值都是一定的,沒有變化,而資料的存儲順序卻是不相同的。從表中可以得到如下結論。
大尾存儲方式:記憶體高位位址存放資料低位位元組資料,記憶體低位位址存放資料高位位元組資料;
小尾存儲方式:記憶體高位位址存放資料高位位元組資料,記憶體低位位址存放資料低位位元組資料。
通常情況下,Windows作業系統相容的CPU為小尾存儲方式,而Unix作業系統相容的CPU多為大尾存儲方式。在網絡中傳輸的資料的位元組順序使用的是大尾存儲方式。
1.2.4 ASCII碼
計算機智能存儲二進制資料,那麼計算機是如何存儲字元的呢?為了存儲字元,計算機必須支援特定的字元集,字元集的作用是将字元映射為整數。早期字元集僅僅使用8個二進制資料位進行存儲,即ASCII碼。後來,由于全世界語言的種類繁多,又産生了新的字元集Unicode字元編碼。
ASCII碼是美國标準資訊交換碼的字母縮寫,在ASCII字元集中,每個字元由唯一的7位整數表示。ASCII碼僅使用了每個位元組的低7位,最高位被不同計算機用來建立私有字元集。由于标準ASCII碼僅使用7位,是以十進制表示範圍是0~127共128個字元。
在程式設計與逆向中都會用到ASCII碼,是以有必要記住常用的ASCII字元對應的十六進制和十進制數。常用的ASCII字元如表1-6所列。
表1-6 常用ASCII碼表
字 符 | 十進制 | 十六進制 | 說 明 |
LF | 10 | 0AH | 換行 |
CR | 13 | 0DH | 回車 |
SP | 32 | 20H | 空格 |
0~9 | 48~57 | 30H~39H | 數字 |
A~Z | 65~90 | 41H~5AH | 大寫 字母 |
a~z | 97~122 | 61H~7AH | 小寫 字母 |
表1-6是經常使用到的ASCII字元,這些字元是經常會見到和用到的,希望讀者能将其儲存,以便使用之時可以快速查閱。
Unicode編碼是為了使字元編碼更進一步符合國際化而進行的擴充,Unicode使用一個字(也就是兩個位元組,即16位)來表示一個字元。這裡不做過多的介紹。
1.3 在OD中檢視資料
在逆向分析中,調試工具可以說是非常重要的。調試器能夠跟蹤一個程序的運作時狀态,在逆向分析中稱為動态分析工具。動态調試會用在很多方面,比如漏洞的挖掘、遊戲外挂的分析、軟體加密解密等方面。本節介紹應用層下最流行的調試工具OllyDbg。
OllyDbg簡稱OD,是一款具有可視化界面的運作在應用層的32位的反彙編逆向調試分析工具。OD是所有進行逆向分析人員都離不開的工具。它的流行,主要原因是操作簡單、參考文檔豐富、支援插件功能等。
熟悉OD
OD的操作非常簡單,但是由于逆向是一門實戰性和綜合性非常強的技術,是以要真正熟練掌握OD的使用卻并不是容易的事,單憑操作而言看似沒有太多的技術含量,但是其真正的精髓在于配合逆向的思路來達到逆向者的目的。
1.OD的選型
為什麼先介紹OD的選型,而不直接開始介紹OD的使用呢?OD的主流版本是1.10和待崛起的2.0。雖然它的主流版本是1.10,但是它仍然存在很多修改版。所謂修改版,就是由使用者自己對OD進行修改而産生的,類似于病毒的免殺。OD雖然是動态調試工具,但是由于其強大的功能經常被很多人用在軟體破解等方面,導緻很多作者的心血付諸東流。軟體的作者為了防止軟體被OD調試,加入了很多專門針對OD進行調試的反調試功能來保護自
己的軟體不被調試,進而不被破解;而破解者為了能夠繼續使用OD來破解軟體,則不得不對OD進行修改,進而達到反反調試的效果。
調試、反調試、反反調試,對于新接觸調試的愛好者來說容易混淆。簡單來說,反調試是阻止使用OD進行調試,而反反調試是突破反調試繼續進行調試。OD的修改版本之是以很多,目的就是為了能夠更好地突破軟體的反調試功能。
是以,如果從學習的角度來講,建議選擇原版的OD進行使用。在使用的過程中,除了會掌握很多調試技巧外,還會學到很多反調試的技巧,進而掌握反反調試的技巧。如果在實際的應用中,則可以直接使用修改版的OD,避免OD被軟體反調試,進而提高逆向調試分析的速度。
2.熟悉OD主界面
OD的發行是一個壓縮包,解壓即可運作使用,運作OD解壓目錄總的ollydbg.exe程式,就會出現一個分布恰當、有菜單有面闆和能輸入指令的看着很強大的軟體視窗,如圖1-4所示。
在圖1-4的OD調試主視窗中的工作區大緻可以分為6個部分,按照從左往右、從上往下,這6部分分别是反彙編視窗、資訊提示視窗、資料視窗、寄存器視窗、棧視窗和指令視窗。下面分别介紹各個視窗的用法。
反彙編視窗:該視窗用于顯示反彙編代碼,調試分析程式主要在這個視窗中進行,這也是進行調試分析的主要工作視窗。
資訊提示視窗:該視窗用于顯示與反彙編視窗中上下文環境相關的記憶體、寄存器或跳轉來源、調用來源等資訊。
資料視窗:該視窗用于以多種格式顯示記憶體中的内容,可使用的格式有Hex、文本、短型、長型、浮點、位址和反彙編等。
寄存器視窗:該視窗用于顯示各個寄存器的内容,包括前面介紹的通用寄存器、段寄存器、标志寄存器、浮點寄存器。另外,還可以在寄存器視窗中的右鍵菜單選擇顯示MMX寄存器、3DNow!寄存器和調試寄存器等。
棧視窗:該視窗用于顯示棧内容、棧幀,即ESP或EBP寄存器指向的位址部分。
指令視窗:該視窗用于輸入指令來簡化調試分析的工作,該視窗并非基本視窗,而是由OD的插件提供的功能,由于幾乎所有的OD使用者都會使用該插件,是以有必要把它也列入主視窗中。
圖1-4 OD調試主視窗
3.在資料視窗中檢視資料
前面已經介紹,OD是一款應用層下的調試工具,它除了可以進行軟體的調試以外,還可以幫助我們學習前面介紹的資料寬度、進制轉換等知識,而且能夠幫助我們學習彙編語言。本節主要介紹通過OD的資料視窗來觀察資料寬度。
為了能夠直覺地觀察記憶體中的資料,我們通過RadAsm建立一個沒有資源的彙編工程,然後編寫一段自己的彙編代碼,代碼如下:
在上面的代碼中,定義了10個全局變量。首先,var1、var2和var3分别定義了dword類型的3個變量,其中var1的值是十六進制的12h,var2的值是十進制的12,var3的值是2進制的11b。b1到b4四個變量是位元組類型的,w1和w2兩個變量是字類型的,d變量是dword類型的。
這10個全局變量就是我們要考察的關鍵。在RadAsm中進行編譯連接配接後,直接按下Ctrl + D這個快捷鍵,即可在RadAsm安裝時自帶的OD中打開。在OD調試器中打開該程式後,觀察它的資料視窗(如圖1-5所示)。
圖1-5 資料視窗中檢視變量
在圖1-5中,資料視窗一共有3列,分别是位址列、HEX資料列和ASCII列。這3個列,可以通過單擊滑鼠右鍵來改變現實方式和顯示的列數。在位址00403000處開始的4個位元組12 00 00 00是十六進制的12,也就是在彙編代碼中定義的var1;在位址00403004處的4個位元組0C 00 00 00是十六進制0C,也就是在彙編代碼中定義的var2,var2變量定義的值是十進制的12,也就是十六進制的0C;在位址00403008處的4個位元組03 00 00 00是十六進制的03,也就是在彙編代碼中定義的var3,var3變量定義的值是2進制的11,也就是十六進制的03。
這3個變量在我們定義的時候都是以dd進行的,都是dword類型的變量,分别各占用4位元組,是以在記憶體中,前3個變量分别是12 00 00 00、0C 00 00 00和03 00 00 00。
在位址0040300C處的值是11 22 33 44,這4個值分别是我們定義b1、b2、b3和b4 4個位元組型的變量,這4變量按照記憶體由低到高的順序顯示分别是11、22、33、44。
在位址00403010處顯示的值是66 55 88 77,這4個值分别對應我們定義的w1和w2兩個字型變量,但是我們定義的變量w1的值是5566h,w2的值是7788h,在記憶體中為何顯示的是6655和8877呢?這就是我們提到過的位元組順序的問題。我們的主機采用的是小尾方式存儲的資料,也就是資料的低位存放在記憶體的低位址中,資料的高位存放在記憶體的高位址中,是以在位址00403020中存放的是5566H的低位資料66,在位址00403021中存放的是5566H的高位資料55,在記憶體看時,順序是相反的。
在位址00403014處存放的是78 56 34 12,這是我們定義的最後一個變量d,它也是按照小尾方式存儲在記憶體中的。是以,在檢視記憶體時順序也是反的。
OD提供了多種檢視記憶體資料的方式,通過在資料視窗中單擊滑鼠右鍵,會彈出如圖1-6所示菜單。
當在資料視窗中選擇資料時,右鍵的菜單提供編輯、指派、查找、斷點功能,如圖1-7所示。
圖1-6 檢視資料方式的菜單選項
圖1-7 OD中對資料操作的菜單
4.通過指令視窗改變資料視窗顯示方式
在圖1-4中的最下方可以看到有一個輸入指令的編輯框,在此處可以輸入OD的相關指令以提高調試的速度。本小節就介紹如果通過指令視窗來改變資料視窗的顯示方式。
在上面代碼中定義變量時,使用了db、dw和dd三種類型,在OD的指令視窗中也同樣可以使用者3個指令,其格式分别如表1-7所列。
表1-7 指令視窗改變資料顯示指令格式
命 令 | 格 式 | 說 明 | 舉 例 |
db | db address | 按位元組的方式檢視 | db 403000 |
dw | dw address | 按字的方式檢視 | dw 403000 |
dd | dd address | 按雙字的方式檢視 | dd 403000 |
将表1-7中的指令在指令視窗中進行輸入,資料視窗的變化和數值顯示的變化分别如圖1-8、圖1-9和圖1-10所示。
圖1-8 dd指令顯示的資料視窗
圖1-9 dw指令顯示的資料視窗
圖1-10 db指令顯示的資料視窗
從圖中可以看出不同方式下資料視窗顯示的樣式,但是無論使用哪種方式顯示資料,位址列總是會顯示在最前面的,隻要我們知道資料的位址,就
可以直接在指令視窗中輸入顯示資料的格式來檢視指定記憶體中的資料。
1.4 程式設計判斷主機字元序
程式設計判斷主機位元組序是更進一步掌握位元組序的方式,本小節給出兩種對主機的位元組序進行判斷的方式。
1.4.1 位元組序相關函數
在TCP/IP網絡程式設計中會涉及關于位元組序的函數,TCP/IP協定中傳遞資料是以網絡位元組序進行傳輸的,網絡位元組序是指網絡傳輸相關協定所規定的位元組傳輸的順序,TCP/IP協定所使用的網絡位元組序與大尾方式相同。而主機位元組序包含大尾方式與小尾方式,是以在進行網絡傳輸時會進行相應的判斷,如果主機位元組序是大尾方式則無需進行轉換即可傳輸,如果主機位元組序是小尾方式則需要轉換成網絡位元組序(也就是轉換成大尾方式)然後進行傳輸。
常用的位元組序涉及的函數有如下幾個:
在這4個函數中,前兩個是将主機位元組序轉換成網絡位元組序,後兩個是将網絡位元組序轉換為主機位元組序。關于更多的位元組序的函數可參考MSDN。
1.4.2 程式設計判斷主機位元組序
“程式設計判斷主機位元組序”是很多防毒軟體公司或者安全開發職位的一道面試題,因為這個題目比較基礎。通過前面的知識,相信讀者能夠很容易地實作該程式。這裡給出筆者自己對于該題目的實作方法。筆者認為,完成該題目有兩種方法,第一種方法是“取值比較法”,第二種方法是“直接轉換比較法”。
1.取值比較法
所謂取值比較法,是首先定義一個4位元組的十六進制數。因為使用調試器檢視記憶體最直覺的就是十六進制,是以定義十六進制數是一個操作起來比較直覺的方法。而後通過指針方式取出這個十六進制數在“記憶體”中的某一個位元組,最後與實際數值中相對應的數進行比較。
由于位元組序的原因,記憶體中的某位元組與實際數值中對應的位元組可能不相同,這樣就可以确定位元組序了。
代碼如下:
以上代碼中,定義了0x01020304這個十六進制數,其在小尾方式記憶體中的存儲順序為04 03 02 01。取(BYTE )&dwSmallNum記憶體中的低位址位的值,如果是小尾方式的話,那麼低位址存儲的值為0x04;如果是大尾方式的話,則低位址存儲的值為0x01。
2.直接轉換比較法
所謂直接轉換比較法,是利用位元組序轉換函數将所定義的值進行轉換,然後用轉換後的值與原值進行比較。如果原值與轉換後的值相同,說明是大尾方式,否則為小尾方式。
代碼如下:
這種方式比較直接,其前提是網絡位元組序是固定的,就是大尾方式。因為是比較,是以就要有一個參照物。如果原值轉換後的結果與原值相同,就說明該主機是大尾方式存儲,反之則是小尾方式。
1.5 總結