<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # Chapter 6 scanf() 現在我們來使用scanf()。 ``` #!bash #include <stdio.h> int main() { int x; printf ("Enter X: "); scanf ("%d", &x); printf ("You entered %d... ", x); return 0; }; ``` 好吧,我承認現在使用scanf()是不明智的,但是我想說明如何把指針傳遞給int變量。 ## 6.1 關于指針 這是計算機科學中最基礎的概念之一。通常,大數組、結構或對象經常被傳遞給其它函數,而傳遞它們的地址要更加簡單。更重要的是:如果調用函數要修改數組或結構中的數據,并且作為整體返回,那么最簡單的辦法就是把數組或結構的地址傳遞給函數,讓函數進行修改。 在C/C++中指針就是某處內存的地址。 在x86中,地址是以32位數表示的(占4字節);在x86-64中是64位數(占8字節)。順便一說,這也是為什么有些人在改用x86-64時感到憤怒——x64架構中所有的指針需要的空間是原來的兩倍。 通過某種方法,只使用無類型指針也是可行的。例如標準C函數memcpy(),用于把一個區塊復制到另外一個區塊上,需要兩個void*型指針作為輸入,因為你無法預知,也無需知道要復制區塊的類型,區塊的大小才是重要的。 當函數需要一個以上的返回值時也經常用到指針(等到第九章再講)。scanf()就是這樣,函數除了要顯示成功讀入的字符個數外,還要返回全部值。 在C/C++中,指針類型只是用于在編譯階段進行類型檢查。本質上,在已編譯的代碼中并不包含指針類型的信息。 ## 6.2 x86 ### 6.2.1 MSVC MVSC 2010編譯后得到下面代碼 ``` #!bash CONST SEGMENT $SG3831 DB ’Enter X:’, 0aH, 00H $SG3832 DB ’%d’, 00H 35 6.2\. X86 CHAPTER 6\. SCANF() $SG3833 DB ’You entered %d...’, 0aH, 00H CONST ENDS PUBLIC _main EXTRN _scanf:PROC EXTRN _printf:PROC ; Function compile flags: /Odtp _TEXT SEGMENT _x$ = -4 ; size = 4 _main PROC push ebp mov ebp, esp push ecx push OFFSET $SG3831 ; ’Enter X:’ call _printf add esp, 4 lea eax, DWORD PTR _x$[ebp] push eax push OFFSET $SG3832 ; ’%d’ call _scanf add esp, 8 mov ecx, DWORD PTR _x$[ebp] push ecx push OFFSET $SG3833 ; ’You entered %d...’ call _printf add esp, 8 ; return 0 xor eax, eax mov esp, ebp pop ebp ret 0 _main ENDP _TEXT ENDS ``` X是局部變量。 C/C++標準告訴我們它只對函數內部可見,無法從外部訪問。習慣上,局部變量放在棧中。也可能有其他方法,但在x86中是這樣。 函數序言后下一條指令PUSH ECX目的并不是要存儲ECX的狀態(注意程序結尾沒有與之相對的POP ECX)。 事實上這條指令僅僅是在棧中分配了4字節用于存儲變量x。 變量x可以用宏 `_x$` 來訪問(等于-4),EBP寄存器指向當前棧幀。 在一個函數執行完之后,EBP將指向當前棧幀,就無法通過`EBP+offset`來訪問局部變量和函數參數了。 也可以使用ESP寄存器,但由于它經常變化所以使用不方便。所以說在函數剛開始時,EBP的值保存了此時ESP的值。 下面是一個非常典型的32位棧幀結構 ... ... EBP-8 local variable #2, marked in IDA as var_8 EBP-4 local variable #1, marked in IDA as var_4 EBP saved value of EBP EBP+4 return address EBP+8 argument#1, marked in IDA as arg_0 EBP+0xC argument#2, marked in IDA as arg_4 EBP+0x10 argument#3, marked in IDA as arg_8 ... ... 在我們的例子中,scanf()有兩個參數。 第一個參數是指向"%d"的字符串指針,第二個是變量x的地址。 首先,`lea eax, DWORD PTR _x$[ebp]` 指令將變量x的地址放入EAX寄存器。LEA作用是"取有效地址",然而之后的主要用途有所變化(b.6.2)。 可以說,LEA在這里只是把EBP的值與宏 `_x$`的值相乘,并存儲在EAX寄存器中。 `lea eax, [ebp-4]` 也是一樣。 EBP的值減去4,結果放在EAX寄存器中。接著EAX寄存器的值被壓入棧中,再調用`printf()`。 之后,`printf()`被調用。第一個參數是一個字符串指針:"`You entered %d …` "。 第二個參數是通過`mov ecx, [ebp-4]`使用的,這個指令把變量x的內容傳給ECX而不是它的地址。 然后,ECX的值放入棧中,接著最后一次調用`printf()`。 ### 6.2.2 MSVC+OllyDbg 讓我們在OllyDbg中使用這個例子。首先載入程序,按F8直到進入我們的可執行文件而不是ntdll.dll。往下滾動屏幕找到main()。點擊第一條指令(PUSH EBP),按F2,再按F9,觸發main()開始處的斷點。 讓我們來跟隨到準備變量x的地址的位置。圖6.2 可以右擊寄存器窗口的EAX,再點擊"堆棧窗口中跟隨"。這個地址會在堆棧窗口中顯示。觀察,這是局部棧中的一個變量。我在圖中用紅色箭頭標出。這里是一些無用數據(0x77D478)。PUSH指令將會把這個棧元素的地址壓入棧中。然后按F8直到scanf()函數執行完。在scanf()執行時,我們要在命令行窗口中輸入,例如輸入123。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec30bbff1.png) 圖6.1 命令行輸出 scanf()在這里執行。圖6.3。scanf()在EAX中返回1,這意味著成功讀入了一個值。現在我們關心的那個棧元素中的值是0x7B(123)。 接下來,這個值從棧中復制到ECX寄存器中,然后傳遞給printf()。圖6.4 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec30cd490.png) 圖6.2 OllyDbg:計算局部變量的地址 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec30ee35d.png) 圖6.3:OllyDbg:scanf()執行 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec3114e29.png) 圖6.4:OllyDbg:準備把值傳遞給printf() ### 6.2.3 GCC 讓我們在Linux GCC 4.4.1下編譯這段代碼 GCC把第一個調用的printf()替換成了puts(),原因在2.3.3節中講過了。 和之前一樣,參數都是用MOV指令放入棧中。 ## 6.3 x64 和原來一樣,只是傳遞參數時不使用棧而使用寄存器。 ### 6.3.1 MSVC ``` #!bash _DATA SEGMENT $SG1289 DB ’Enter X:’, 0aH, 00H $SG1291 DB ’%d’, 00H $SG1292 DB ’You entered %d...’, 0aH, 00H _DATA ENDS _TEXT SEGMENT x$ = 32 main PROC $LN3: sub rsp, 56 lea rcx, OFFSET FLAT:$SG1289 ; ’Enter X:’ call printf lea rdx, QWORD PTR x$[rsp] lea rcx, OFFSET FLAT:$SG1291 ; ’%d’ call scanf mov edx, DWORD PTR x$[rsp] lea rcx, OFFSET FLAT:$SG1292 ; ’You entered %d...’ call printf ; return 0 xor eax, eax add rsp, 56 ret 0 main ENDP _TEXT ENDS ``` ### 6.3.2 GCC ``` #!bash .LC0: .string "Enter X:" .LC1: .string "%d" .LC2: .string "You entered %d... " main: sub rsp, 24 mov edi, OFFSET FLAT:.LC0 ; "Enter X:" call puts lea rsi, [rsp+12] mov edi, OFFSET FLAT:.LC1 ; "%d" xor eax, eax call __isoc99_scanf mov esi, DWORD PTR [rsp+12] mov edi, OFFSET FLAT:.LC2 ; "You entered %d... " xor eax, eax call printf ; return 0 xor eax, eax add rsp, 24 ret ``` ## 6.4 ARM ### 6.4.1 keil優化+thumb mode ``` #!bash .text:00000042 scanf_main .text:00000042 .text:00000042 var_8 = -8 .text:00000042 .text:00000042 08 B5 PUSH {R3,LR} .text:00000044 A9 A0 ADR R0, aEnterX ; "Enter X: " .text:00000046 06 F0 D3 F8 BL __2printf .text:0000004A 69 46 MOV R1, SP .text:0000004C AA A0 ADR R0, aD ; "%d" .text:0000004E 06 F0 CD F8 BL __0scanf .text:00000052 00 99 LDR R1, [SP,#8+var_8] .text:00000054 A9 A0 ADR R0, aYouEnteredD___ ; "You entered %d... " .text:00000056 06 F0 CB F8 BL __2printf .text:0000005A 00 20 MOVS R0, #0 .text:0000005C 08 BD POP {R3,PC} ``` 必須把一個指向int變量的指針傳遞給scanf(),這樣才能通過這個指針返回一個值。Int是一個32位的值,所以我們在內存中需要4字節存儲,并且正好符合32位的寄存器。局部變量x的空間分配在棧中,IDA把他命名為var_8。然而并不需要分配空間,因為棧指針指向的空間可以被立即使用。所以棧指針的值被復制到R1寄存器中,然后和格式化字符串一起送入scanf()。然后LDR指令將這個值從棧中送入R1寄存器,用以送入printf()中。 用ARM-mode和Xcode LLVM編譯的代碼區別不大,這里略去。 ## 6.5 Global Variables 如果之前的例子中的x變量不再是本地變量而是全局變量呢?那么就有機會接觸任何指針,不僅僅是函數體,全局變量被認為anti-pattern(通常被認為是一個不好的習慣),但是為了試驗,我們可以這樣做。 ``` #!cpp #include <stdio.h> int x; int main() { printf ("Enter X: "); scanf ("%d", &x); printf ("You entered %d... ", x); return 0; }; ``` ### 6.5.1 MSVC: x86 ``` #!bash _DATA SEGMENT COMM _x:DWORD $SG2456 DB ’Enter X:’, 0aH, 00H $SG2457 DB ’%d’, 00H $SG2458 DB ’You entered %d...’, 0aH, 00H _DATA ENDS PUBLIC _main EXTRN _scanf:PROC EXTRN _printf:PROC ; Function compile flags: /Odtp _TEXT SEGMENT _main PROC push ebp mov ebp, esp push OFFSET $SG2456 call _printf add esp, 4 push OFFSET _x push OFFSET $SG2457 call _scanf add esp, 8 mov eax, DWORD PTR _x push eax push OFFSET $SG2458 call _printf add esp, 8 xor eax, eax pop ebp ret 0 _main ENDP _TEXT ENDS ``` 現在x變量被定義為在_DATA部分,局部堆棧不允許再分配任何內存,除了直接訪問內存所有通過棧的訪問都不被允許。在執行的文件中全局變量還未初始化(實際上,我們為什么要在執行文件中為未初始化的變量分配一塊?)但是當訪問這里時,系統會在這里分配一塊0值。 現在讓我們明白的來分配變量吧" ``` #!bash int x=10; // default value ``` 我們得到: ``` _DATA SEGMENT _x DD 0aH ... ``` 這里我們看見一個雙字節的值0xA(DD 表示雙字節 = 32bit) 如果你在IDA中打開compiled.exe,你會發現x變量被放置在_DATA塊的開始處,接著你就會看見文本字符串。 如果你在IDA中打開之前例子中的compiled.exe中X變量沒有定義的地方,你就會看見像這樣的東西: ``` #!bash .data:0040FA80 _x dd ? ; DATA XREF: _main+10 .data:0040FA80 ; _main+22 .data:0040FA84 dword_40FA84 dd ? ; DATA XREF: _memset+1E .data:0040FA84 ; unknown_libname_1+28 .data:0040FA88 dword_40FA88 dd ? ; DATA XREF: ___sbh_find_block+5 .data:0040FA88 ; ___sbh_free_block+2BC .data:0040FA8C ; LPVOID lpMem .data:0040FA8C lpMem dd ? ; DATA XREF: ___sbh_find_block+B .data:0040FA8C ; ___sbh_free_block+2CA .data:0040FA90 dword_40FA90 dd ? ; DATA XREF: _V6_HeapAlloc+13 .data:0040FA90 ; __calloc_impl+72 .data:0040FA94 dword_40FA94 dd ? ; DATA XREF: ___sbh_free_block+2FE ``` 被`_x`替換了?其它變量也并未要求初始化,這也就是說在載入exe至內存后,在這里有一塊針對所有變量的空間,并且還有一些隨機的垃圾數據。但在在exe中這些沒有初始化的變量并不影響什么,比如它適合大數組。 ### 6.5.2 MSVC: x86 + OllyDbg 到這里事情就變得簡單了(見表6.5),變量都在data部分,順便說一句,在PUSH指令后,壓入x的地址,被執行后,地址將會在棧中顯示,那么右擊元組數據,點擊"Fllow in dump",然后變量就會在左側內存窗口顯示. 在命令行窗口中輸入123后,這里就會顯示0x7B 但是為什么第一個字節是7B?合理的猜測,這里會有一組00 00 7B,被稱為是字節順序,然后在x86中使用的是小端,也就是說低位數據先寫,高位數據后寫。 不一會,這里的32-bit值就會載入到EAX中,然后被傳遞給printf(). X變量地址是0xDC3390.在OllyDbg中我們看進程內存映射(Alt-M),然后發現這個地在PE文件.data結構處。見表6.6 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec312d1f3.png) 表6.5 OllyDbg: scanf()執行后 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec314bae0.png) 表6.6: OllyDbg 進程內存映射 ### 6.5.3 GCC: x86 這和linux中幾乎是一樣的,除了segment的名稱和屬性:未初始化變量被放置在_bss部分。 在ELF文件格式中,這部分數據有這樣的屬性: ``` ; Segment type: Uninitialized ; Segment permissions: Read/Write ``` 如果靜態的分配一個值,比如10,它將會被放在_data部分,這部分有下面的屬性: ``` ; Segment type: Pure data ; Segment permissions: Read/Write ``` ### 6.5.4 MSVC: x64 ``` #!bash _DATA SEGMENT COMM x:DWORD $SG2924 DB ’Enter X:’, 0aH, 00H $SG2925 DB ’%d’, 00H $SG2926 DB ’You entered %d...’, 0aH, 00H _DATA ENDS _TEXT SEGMENT main PROC $LN3: sub rsp, 40 lea rcx, OFFSET FLAT:$SG2924 ; ’Enter X:’ call printf lea rdx, OFFSET FLAT:x lea rcx, OFFSET FLAT:$SG2925 ; ’%d’ call scanf mov edx, DWORD PTR x lea rcx, OFFSET FLAT:$SG2926 ; ’You entered %d...’ call printf ; return 0 xor eax, eax add rsp, 40 ret 0 main ENDP _TEXT ENDS ``` 幾乎和x86中的代碼是一樣的,發現x變量的地址傳遞給scanf()用的是LEA指令,盡管第二處傳遞給printf()變量時用的是MOV指令,"DWORD PTR"——是匯編語言中的一部分(和機器碼沒有聯系)。這就表示變量數據類型是32-bit,于是MOV指令就被編碼了。 ### 6.5.5 ARM:Optimizing Keil + thumb mode ``` #!bash .text:00000000 ; Segment type: Pure code .text:00000000 AREA .text, CODE ... .text:00000000 main .text:00000000 PUSH {R4,LR} .text:00000002 ADR R0, aEnterX ; "Enter X: " .text:00000004 BL __2printf .text:00000008 LDR R1, =x .text:0000000A ADR R0, aD ; "%d" .text:0000000C BL __0scanf .text:00000010 LDR R0, =x .text:00000012 LDR R1, [R0] .text:00000014 ADR R0, aYouEnteredD___ ; "You entered %d... " .text:00000016 BL __2printf .text:0000001A MOVS R0, #0 .text:0000001C POP {R4,PC} ... .text:00000020 aEnterX DCB "Enter X:",0xA,0 ; DATA XREF: main+2 .text:0000002A DCB 0 .text:0000002B DCB 0 .text:0000002C off_2C DCD x ; DATA XREF: main+8 .text:0000002C ; main+10 .text:00000030 aD DCB "%d",0 ; DATA XREF: main+A .text:00000033 DCB 0 .text:00000034 aYouEnteredD___ DCB "You entered %d...",0xA,0 ; DATA XREF: main+14 .text:00000047 DCB 0 .text:00000047 ; .text ends .text:00000047 ... .data:00000048 ; Segment type: Pure data .data:00000048 AREA .data, DATA .data:00000048 ; ORG 0x48 .data:00000048 EXPORT x .data:00000048 x DCD 0xA ; DATA XREF: main+8 .data:00000048 ; main+10 .data:00000048 ; .data ends ``` 那么,現在x變量以某種方式變為全局的,現在被放置在另一個部分中。命名為data塊(.data)。有人可能會問,為什么文本字符串被放在了代碼塊(.text),而且x可以被放在這?因為這是變量,而且根據它的定義,它可以變化,也有可能會頻繁變化,不頻繁變化的代碼塊可以被放置在ROM中,變化的變量在RAM中,當有ROM時在RAM中儲存不變的變量是不利于節約資源的。 此外,RAM中數據部分常量必須在之前初始化,因為在RAM使用后,很明顯,將會包含雜亂的信息。 繼續向前,我們可以看到,在代碼片段,有個指針指向X變量(0ff_2C)。然后所有關于變量的操作都是通過這個指針。這也是x變量可以被放在遠離這里地方的原因。所以他的地址一定被存在離這很近的地方。LDR指令在thumb模式下只可訪問指向地址在1020bytes內的數據。同樣的指令在ARM模式下——范圍就達到了4095bytes,也就是x變量地址一定要在這附近的原因。因為沒法保證鏈接時會把這個變量放在附近。 另外,如果變量以const聲明,Keil編譯環境下則會將變量放在.constdata部分,大概從那以后,鏈接時就可以把這部分和代碼塊放在ROM里了。 ## 6.6 scanf()結果檢查 正如我之前所見的,現在使用scanf()有點過時了,但是如過我們不得不這樣做時,我們需要檢查scanf()執行完畢時是否發生了錯誤。 ``` #!bash #include <stdio.h> int main() { int x; printf ("Enter X: "); if (scanf ("%d", &x)==1) printf ("You entered %d... ", x); else printf ("What you entered? Huh? "); return 0; }; ``` 按標準,scanf()函數返回成功獲取的字段數。 在我們的例子中,如果事情順利,用戶輸入一個數字,scanf()將會返回1或0或者錯誤情況下返回EOF. 這里,我們添加了一些檢查scanf()結果的c代碼,用來打印錯誤信息: 按照預期的回顯: ``` #!bash C:...>ex3.exe Enter X: 123 You entered 123... C:...>ex3.exe Enter X: ouch What you entered? Huh? ``` ### 6.6.1 MSVC: x86 我們可以得到這樣的匯編代碼(msvc2010): ``` #!bash lea eax, DWORD PTR _x$[ebp] push eax push OFFSET $SG3833 ; ’%d’, 00H call _scanf add esp, 8 cmp eax, 1 jne SHORT $LN2@main mov ecx, DWORD PTR _x$[ebp] push ecx push OFFSET $SG3834 ; ’You entered %d...’, 0aH, 00H call _printf add esp, 8 jmp SHORT $LN1@main $LN2@main: push OFFSET $SG3836 ; ’What you entered? Huh?’, 0aH, 00H call _printf add esp, 4 $LN1@main: xor eax, eax ``` 調用函數(main())必須能夠訪問到被調用函數(scanf())的結果,所以callee把這個值留在了EAX寄存器中。 然后我們在"`CMP EAX, 1`"指令的幫助下,換句話說,我們將eax中的值與1進行比較。 JNE根據CMP的結果判斷跳至哪,JNE表示(jump if Not Equal) 所以,如果EAX中的值不等于1,那么處理器就會將執行流程跳轉到JNE指向的,在我們的例子中是$LN2@main,當流程跳到這里時,CPU將會帶著參數"What you entered? Huh?"執行printf(),但是執行正常,就不會發生跳轉,然后另外一個printf()就會執行,兩個參數為"`You entered %d…`"及x變量的值。 因為第二個printf()并沒有被執行,后面有一個JMP(無條件跳轉),就會將執行流程到第二個printf()后"XOR EAX, EAX"前,執行完返回0。 那么,可以這么說,比較兩個值通常使用CMP/Jcc這對指令,cc是條件碼,CMP比較兩個值,然后設置processor flag,Jcc檢查flags然后判斷是否跳。 但是事實上,這卻被認為是詭異的。但是CMP指令事實上,但是CMP指令實際上是SUB(subtract),所有算術指令都會設置processor flags,不僅僅只有CMP,當我們比較1和1時,1結果就變成了0,ZF flag就會被設定(表示最后一次的比較結果為0),除了兩個數相等以外,再沒有其他情況了。JNE 檢查ZF flag,如果沒有設定就會跳轉。JNE實際上就是JNZ(Jump if Not Zero)指令。JNE和JNZ的機器碼都是一樣的。所以CMP指令可以被SUB指令代替,幾乎一切的都沒什么變化。但是SUB會改變第一個數,CMP是"SUB without saving result". ### 6.6.2 MSVC: x86:IDA 現在是時候打開IDA然后嘗試做些什么了,順便說一句。對于初學者來說使用在MSVC中使用/MD是個非常好的主意。這樣所有獨立的函數不會從可執行文件中link,而是從MSVCR*.dll。因此這樣可以簡單明了的發現函數在哪里被調用。 當在IDA中分析代碼時,建議一定要做筆記。比如在分析這個例子的時候,我們看到了JNZ將要被設置為error,所以點擊標注,然后標注為"error"。另外一處標注在"exit": ``` #!bash .text:00401000 _main proc near .text:00401000 .text:00401000 var_4 = dword ptr -4 .text:00401000 argc = dword ptr 8 .text:00401000 argv = dword ptr 0Ch .text:00401000 envp = dword ptr 10h .text:00401000 .text:00401000 push ebp .text:00401001 mov ebp, esp .text:00401003 push ecx .text:00401004 push offset Format ; "Enter X: " .text:00401009 call ds:printf .text:0040100F add esp, 4 .text:00401012 lea eax, [ebp+var_4] .text:00401015 push eax .text:00401016 push offset aD ; "%d" .text:0040101B call ds:scanf .text:00401021 add esp, 8 .text:00401024 cmp eax, 1 .text:00401027 jnz short error .text:00401029 mov ecx, [ebp+var_4] .text:0040102C push ecx .text:0040102D push offset aYou ; "You entered %d... " .text:00401032 call ds:printf .text:00401038 add esp, 8 .text:0040103B jmp short exit .text:0040103D ; --------------------------------------------------------------------------- .text:0040103D .text:0040103D error: ; CODE XREF: _main+27 .text:0040103D push offset aWhat ; "What you entered? Huh? " .text:00401042 call ds:printf .text:00401048 add esp, 4 .text:0040104B .text:0040104B exit: ; CODE XREF: _main+3B .text:0040104B xor eax, eax .text:0040104D mov esp, ebp .text:0040104F pop ebp .text:00401050 retn .text:00401050 _main endp ``` 現在理解代碼就變得非常簡單了。然而過分的標注指令卻不是一個好主意。 函數的一部分有可能也會被IDA隱藏: 我隱藏了兩部分然后分別給它們命名: ``` #!bash .text:00401000 _text segment para public ’CODE’ use32 .text:00401000 assume cs:_text .text:00401000 ;org 401000h .text:00401000 ; ask for X .text:00401012 ; get X .text:00401024 cmp eax, 1 .text:00401027 jnz short error .text:00401029 ; print result .text:0040103B jmp short exit .text:0040103D ; --------------------------------------------------------------------------- .text:0040103D .text:0040103D error: ; CODE XREF: _main+27 .text:0040103D push offset aWhat ; "What you entered? Huh? " .text:00401042 call ds:printf .text:00401048 add esp, 4 .text:0040104B .text:0040104B exit: ; CODE XREF: _main+3B .text:0040104B xor eax, eax .text:0040104D mov esp, ebp .text:0040104F pop ebp .text:00401050 retn .text:00401050 _main endp ``` 如果要顯示這些隱藏的部分,我們可以點擊數字上的+。 為了壓縮"空間",我們可以看到IDA怎樣用圖表代替一個函數的(見圖6.7),然后在每個條件跳轉處有兩個箭頭,綠色和紅色。綠色箭頭代表如果跳轉觸發的方向,紅色則相反。 當然可以折疊節點,然后備注名稱,我像這樣處理了3塊(見圖 6.8): 這個非常的有用。可以這么說,逆向工程師很重要的一點就是縮小他所有的信息。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec316d085.png) 圖6.7: IDA 圖形模式 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec3186586.png) 圖6.8: Graph mode in IDA with 3 nodes folded ### 6.6.3 MSVC: x86 + OllyDbg 讓我們繼續在OllyDbg中看這個范例程序,使它認為scanf()怎么運行都不會出錯。 當本地變量地址被傳遞給scanf()時,這個變量還有一些垃圾數據。這里是0x4CD478:見圖6.10 當scanf()執行時,我在命令行窗口輸入了一些不是數字的東西,像"asdasd".scanf()結束后eax變為了0.也就意味著有錯誤發生:見圖6.11 我們也可以發現棧中的本地變量并沒有發生變化,scanf()會在那里寫入什么呢?其實什么都沒有,只是返回了0. 現在讓我們嘗試修改這個程序,右擊EAX,在選項中有個"set to 1",這正是我們所需要的。 現在EAX是1了。那么接下來的檢查就會按照我們的需求執行,然后printf()將會打印出棧上的變量。 按下F9我們可以在窗口中看到: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec319b108.png) 圖6.9 實際上,5035128是棧上一個數據(0x4CD478)的十進制表示! ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec31adf0c.png) 圖6.10 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec31cbd51.png) 圖6.11 ### 6.6.4 MSVC: x86 + Hlew 這也是一個關于可執行文件patch的簡單例子,我們之前嘗試patch程序,所以程序總是打印數字,不管我們輸入什么。 假設編譯時并沒有使用/MD,我們可以在.text開始的地方找到main()函數,現在讓我們在Hiew中打開執行文件。找到.text的開始處(enter,F8,F6,enter,enter) 我們可以看到這個:表6.13 然后按下F9(update),現在文件保存在了磁盤中,就像我們想要的。 兩個NOP可能看起來并不是那么完美,另一個方法是把0寫在第二處(jump offset),所以JNZ就可以總是跳到下一個指令了。 另外我們也可以這樣做:替換第一個字節為EB,這樣就不修改第二處(jump offset),這樣就會無條件跳轉,不管我們輸入什么,錯誤信息都可以打印出來了。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec31ed3e9.png) 圖6.12:main()函數 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec321b03d.png) 圖6.13:Hiew 用兩個NOP替換JNZ ### 6.6.5 GCC: x86 生成的代碼和gcc 4.4.1是一樣的,除了我們之前已經考慮過的 ### 6.6.6 MSVC: x64 因為我們這里處理的是無整型變量。在x86-64中還是32bit,我們可以看出32bit的寄存器(前綴為E)在這種情況下是怎樣使用的,然而64bit的寄存也有被使用(前綴R) ``` #!bash _DATA SEGMENT $SG2924 DB ’Enter X:’, 0aH, 00H $SG2926 DB ’%d’, 00H $SG2927 DB ’You entered %d...’, 0aH, 00H $SG2929 DB ’What you entered? Huh?’, 0aH, 00H _DATA ENDS _TEXT SEGMENT x$ = 32 main PROC $LN5: sub rsp, 56 lea rcx, OFFSET FLAT:$SG2924 ; ’Enter X:’ call printf lea rdx, QWORD PTR x$[rsp] lea rcx, OFFSET FLAT:$SG2926 ; ’%d’ call scanf cmp eax, 1 jne SHORT $LN2@main mov edx, DWORD PTR x$[rsp] lea rcx, OFFSET FLAT:$SG2927 ; ’You entered %d...’ call printf jmp SHORT $LN1@main $LN2@main: lea rcx, OFFSET FLAT:$SG2929 ; ’What you entered? Huh?’ call printf $LN1@main: ; return 0 xor eax, eax add rsp, 56 ret 0 main ENDP _TEXT ENDS END ``` ### 6.6.7 ARM:Optimizing Keil + thumb mode ``` #!bash var_8 = -8 PUSH {R3,LR} ADR R0, aEnterX ; "Enter X: " BL __2printf MOV R1, SP ADR R0, aD ; "%d" BL __0scanf CMP R0, #1 BEQ loc_1E ADR R0, aWhatYouEntered ; "What you entered? Huh? " BL __2printf loc_1A ; CODE XREF: main+26 MOVS R0, #0 POP {R3,PC} loc_1E ; CODE XREF: main+12 LDR R1, [SP,#8+var_8] ADR R0, aYouEnteredD___ ; "You entered %d... " BL __2printf B loc_1A ``` 這里有兩個新指令CMP 和BEQ. CMP和x86指令中的相似,它會用一個參數減去另外一個參數然后保存flag. BEQ是跳向另一處地址,如果數相等就會跳,如果最后一次比較結果為0,或者Z flag是1。和x86中的JZ是一樣的。 其他的都很簡單,執行流程分為兩個方向,當R0被寫入0后,兩個方向則會合并,作為函數的返回值,然后函數結束。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看