linklab實驗記錄
實驗材料:https://github.com/qmj0923/NJU-ICS-linklab
實驗環境:debian-10.5.0-i386-netinst虛擬機
參考書目:
? 1.《計算機系統基礎(第2版)》(袁春風、余子濠 編著)第4章
? 2.《深入理解計算機系統(原書第3版)》第7章
網路課程參考:中國大學MOOC——計算機系統基礎(四):編程與除錯實踐
目錄
- linklab實驗記錄
- 修改ELF檔案的工具:hexedit
- phase1 靜態資料物件與ELF資料節
- 方式1
- 方式2
- 方式3
- 方式4
- phase2 指令與ELF代碼節
- 方法1
- 方法2
- 方法3
- 方法n
- phase3 符號決議
- phase4 switch陳述句與重定位
- phase5 可重定位目標檔案
- part1
- part2
- part3
- part4
- 修改結果
- *重定位的具體程序
- phase6 位置無關代碼
- part1 基礎知識
- part2 填補代碼節
- part3 填補重定位資訊
- 特別鳴謝
修改ELF檔案的工具:hexedit
安裝:sudo apt-get install hexedit
用戶手冊:man hexedit
運行:hexedit <filename>
保存:Ctrl+W或F2
保存并退出:Ctrl+X
不保存直接退出:Ctrl+C
選擇:Ctrl+Space
復制:Esc-w
粘貼:Ctrl+Y
游標向前移動一個字符,如果該字符進行過改動則將其復原:Backspace
撤銷所有操作:Ctrl+U
向前/后搜索:Ctrl+S/Ctrl+R
phase1 靜態資料物件與ELF資料節
直接gcc -no-pie -o lb1 main.o phase1.o,然后./lb1,列印出來一串奇奇怪怪的東西:
ddURHzFnm2mcxbehqVcVufpd68LdEePs lYPCnZfPLLbMLzV3iM1A97QVLg7j8zcmDlD0clCtKV0kgLRshaBQ3kCaGG YMbr9ELE31xt2fau4zX7bEMCVf qXOdnQ igVJcDsac1d9N7kSla5VLXAKDtjxAoNjW2tonwDzyASqLn5JKSf32EqapXP83B03NmDqUx
不急著分析這個奇怪的字串,我們先來研究一下從哪里可以找到這串東西,以此來粗略理解一下鏈接的原理,接下來我們會用多種方式來尋找這個字串,
方式1
反匯編一下剛剛鏈接生成的可執行程式lb1(objdump -d lb1 > lb1.s),發現do_phase函式做了這些事:
080491a2 <do_phase>:
80491a2: 55 push %ebp
80491a3: 89 e5 mov %esp,%ebp
80491a5: 83 ec 08 sub $0x8,%esp
80491a8: b8 da c0 04 08 mov $0x804c0da,%eax
80491ad: 83 ec 0c sub $0xc,%esp
80491b0: 50 push %eax
80491b1: e8 7a fe ff ff call 8049030
最后的call的0x8049030是函式puts,先不去管它,我們來看push %eax把啥東西存到堆疊里去了:
(gdb) x /s 0x804c0da
0x804c0da <tqdzfNje+26>: "ddURHzFnm2mcxbehqVcVufpd68LdEePs\tlYPCnZfPLLbMLzV3iM1A97QVLg7j8zcmDlD0clCtKV0kgLRshaBQ3kCaGG YMbr9ELE31xt2fau4zX7bEMCVf\tqXOdnQ igVJcDsac1d9N7kSla5VLXAKDtjxAoNjW2tonwDzyASqLn5JKSf32EqapXP83B03NmDqUx"
這樣,我們大概知道phase1的代碼干了這樣一件事:呼叫puts函式,列印這個奇怪的字串,
方式2
用hexedit查看phase1.o,我們可以直接用肉眼找到那個奇怪的字串:

方式3
接下來我們從鏈接與符號決議的角度來找這個字串,我們先反匯編一下phase1.o(objdump -d phase1.o > phase1.s),看看鏈接之前的do_phase函式:
phase1.o: file format elf32-i386
Disassembly of section .text:
00000000 <do_phase>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: b8 9a 00 00 00 mov $0x9a,%eax
b: 83 ec 0c sub $0xc,%esp
e: 50 push %eax
f: e8 fc ff ff ff call 10 <do_phase+0x10>
14: 83 c4 10 add $0x10,%esp
17: 90 nop
18: c9 leave
19: c3 ret
和上文鏈接之后的do_phase函式的反匯編代碼一比較,我們很容易就會產生兩個疑問:
1. 為什么call 10 <do_phase+0x10>是呼叫puts函式?這是如何定位的?
2. 為什么把0x9a入堆疊,作為puts函式的實參?這是如何定位到那個奇怪的字串的?
要解決這兩個問題,我們需要來看一下phase1.o的重定位資訊(readelf -r phase1.o):
Relocation section '.rel.text' at offset 0x354 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000007 00000301 R_386_32 00000000 .data
00000010 00000e02 R_386_PC32 00000000 puts
Relocation section '.rel.data' at offset 0x364 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000068 00000601 R_386_32 00000000 .rodata
00000160 00000d01 R_386_32 00000000 do_phase
Relocation section '.rel.eh_frame' at offset 0x374 contains 1 entry:
Offset Info Type Sym.Value Sym. Name
00000020 00000202 R_386_PC32 00000000 .text
我們知道,資料節中參考對應的重定位條目在.rel.data節中,代碼節中參考對應的重定位條目在.rel.text節中,我們所提出的兩個問題都源于代碼節中,因此我們只需要關注重定位資訊表的.rel.text節,這個節里有兩個表項,一個是puts,一個是.data,正好對應我們提出的兩個疑問,我們接下來只對鏈接之前的檔案進行分析,所以重定位表中的資訊只是用來定位符號,而不是用來對重定位的程序進行分析(重定位的程序會在phase5中提及,這里不會涉及到),
我們看Offset列,在.rel.text部分,Offset指的是相對.text節的偏移量,我們結合puts和.data兩個表項來進行具體說明,
puts的Offset為0x10,所以它是在相對于.text節偏移量為0x10的地方被參考的,我們來看phase1.s中偏移量為0x10的位元組所在的指令:
f: e8 fc ff ff ff call 10 <do_phase+0x10>
從偏移量為0x10起的四個位元組,所表示的值是-0x4,call指令的目標地址是相對地址,是下一條指令的偏移量加上它的運算元-0x4,下一條指令的偏移量為0x14,所以此處call指令的目標為0x10,這與puts的Offset值0x10是相互呼應的,
再來看.data表項,它Offset為0x7,所以它是在相對于.text節偏移量為0x7的地方被參考的,我們來看phase1.s中偏移量為0x7(相對.text節)的位元組所在的指令:
6: b8 9a 00 00 00 mov $0x9a,%eax
從偏移量為0x7起的四個位元組,所表示的數是0x9a,所以此處表示的實際地址是0x9a加上重定位表項.data的偏移地址,readelf -S phase1.o,我們來看一下節頭表,我們發現.data節在phase1.o中的偏移量為0x60:
There are 14 section headers, starting at offset 0x3e0:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00001a 00 AX 0 0 1
[ 2] .rel.text REL 00000000 000354 000010 08 I 11 1 4
[ 3] .data PROGBITS 00000000 000060 000164 00 WA 0 0 32
[ 4] .rel.data REL 00000000 000364 000010 08 I 11 3 4
[ 5] .bss NOBITS 00000000 0001c4 000000 00 WA 0 0 1
[ 6] .rodata PROGBITS 00000000 0001c4 000002 00 A 0 0 1
[ 7] .comment PROGBITS 00000000 0001c6 00001d 01 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 00000000 0001e3 000000 00 0 0 1
[ 9] .eh_frame PROGBITS 00000000 0001e4 000038 00 A 0 0 4
[10] .rel.eh_frame REL 00000000 000374 000008 08 I 11 9 4
[11] .symtab SYMTAB 00000000 00021c 000100 10 12 12 4
[12] .strtab STRTAB 00000000 00031c 000038 00 0 0 1
[13] .shstrtab STRTAB 00000000 00037c 000063 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
所以此處表示的實際地址為0x60+0x9a=0xfa,我們回頭看方式2中的圖片,發現那個我們列印出來的東西的起始位置正好是0xfa,定位成功,
方式4
這次我們來看符號表(readelf -s phase1.o),我們只需要關注.data節中的符號,即(Ndx=3)的表項:
Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS phase1.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 5
5: 00000000 104 OBJECT LOCAL DEFAULT 3 PJIvaY
6: 00000000 0 SECTION LOCAL DEFAULT 6
7: 00000080 223 OBJECT LOCAL DEFAULT 3 tqdzfNje
8: 0000015f 1 OBJECT LOCAL DEFAULT 3 uJGbRo
9: 00000000 0 SECTION LOCAL DEFAULT 8
10: 00000000 0 SECTION LOCAL DEFAULT 9
11: 00000000 0 SECTION LOCAL DEFAULT 7
12: 00000068 4 OBJECT GLOBAL DEFAULT 3 phase_id
13: 00000000 26 FUNC GLOBAL DEFAULT 1 do_phase
14: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts
15: 00000160 4 OBJECT GLOBAL DEFAULT 3 phase
在方式3中,我們從節頭表里發現.data節在phase1.o檔案中的偏移量為0x60,結合上面的符號表我們得知:
| 屬于.data節的符號名 | 在.data節中的偏移量(Value) | 大小(Size) | 在phase1.o中的位置 |
|---|---|---|---|
| PJIvaY | 0x0 | 0x68 | 0x60-0xc7 |
| tqdzfNje | 0x80 | 0xdf | 0xe0-0x1be |
| uJGbRo | 0x15f | 0x1 | 0x1bf |
我們再來看方式1列印出來的東西:
(gdb) x /s 0x804c0da
0x804c0da <tqdzfNje+26>: "ddURHzFnm2mcxbehqVcVufpd68LdEePs\tlYPCnZfPLLbMLzV3iM1A97QVLg7j8zcmDlD0clCtKV0kgLRshaBQ3kCaGG YMbr9ELE31xt2fau4zX7bEMCVf\tqXOdnQ igVJcDsac1d9N7kSla5VLXAKDtjxAoNjW2tonwDzyASqLn5JKSf32EqapXP83B03NmDqUx"
需要仔細關注的是這個:<tqdzfNje+26>,
這說明phase1列印的是符號tqdzfNje,但不是直接列印這個符號對應的東西,而是偏移了26(0x1a)個位元組進行列印的,我們計算要列印的東西在phase1.o中的總偏移量:.data節在phase1.o中的偏移量+符號tqdzfNje相對于.data節的偏移量+列印字串相對于tqdzfNje首地址的偏移量=0x60+0x80+0x1a=0xfa,這樣,我們就又一次定位到了那個奇怪的字串,
研究完這么多找那個奇怪字串的方式,我們大致對鏈接的原理和符號決議有了一些概念,然后就可以開始解決phase1了,從列印出來的那個字串的第一個字符開始,逐個修改學號每個數字對應的字符,最后加上00作為結束符,就完成了,

phase2 指令與ELF代碼節
這個階段我們只能修改phase2.o的.text節內容,objdump -d phase2.o > phase2.s,查看phase2.o的反匯編代碼,我們發現do_phase函式里面全是nop,那么很顯然,這個階段是要我們填充do_phase函式中的指令,從而列印出學號,
gcc -no-pie -o lb2 main.o phase2.o,objdump -d lb2 > lb2.s,我們先來研究一下鏈接之后main函式執行了些啥,
08049182 <main>:
8049182: 8d 4c 24 04 lea 0x4(%esp),%ecx
8049186: 83 e4 f0 and $0xfffffff0,%esp
8049189: ff 71 fc pushl -0x4(%ecx)
804918c: 55 push %ebp
804918d: 89 e5 mov %esp,%ebp
804918f: 51 push %ecx
8049190: 83 ec 04 sub $0x4,%esp
8049193: a1 28 c0 04 08 mov 0x804c028,%eax
8049198: 85 c0 test %eax,%eax
804919a: 74 09 je 80491a5 <main+0x23>
804919c: a1 28 c0 04 08 mov 0x804c028,%eax
80491a1: ff d0 call *%eax
80491a3: eb 10 jmp 80491b5 <main+0x33>
80491a5: 83 ec 0c sub $0xc,%esp
80491a8: 68 10 a1 04 08 push $0x804a110
80491ad: e8 8e fe ff ff call 8049040 <puts@plt>
80491b2: 83 c4 10 add $0x10,%esp
80491b5: b8 00 00 00 00 mov $0x0,%eax
80491ba: 8b 4d fc mov -0x4(%ebp),%ecx
80491bd: c9 leave
80491be: 8d 61 fc lea -0x4(%ecx),%esp
80491c1: c3 ret
......
08049253 <do_phase>:
8049253: 55 push %ebp
8049254: 89 e5 mov %esp,%ebp
8049256: 90 nop
......
8049296: 90 nop
8049297: 5d pop %ebp
8049298: c3 ret
經過一通gdb,我們發現0x804c028這個地址上存放的是do_phase函式的地址0x8049253,也就是說,main函式會呼叫do_phase函式,然后jmp跳過下面的call puts,直接return,所以如果嘗試./lb2,我們會發現這個程式啥也沒列印,
分析完程式大體上的流程,我們開始著手解題,
方法1
最簡單的思路,我們修改do_phase函式的回傳地址,讓它不回傳到main函式里的下一條指令jmp,我們設法讓do_phase函式回傳之后執行main函式里call puts的部分,把學號列印出來,
80491a8: 68 10 a1 04 08 push $0x804a110
80491ad: e8 8e fe ff ff call 8049040 <puts@plt>
閑來無事,我們看一下這個puts默認列印的東西是啥:
(gdb) x /s 0x804a110
0x804a110: "Welcome to this small lab of linking. To begin lab, please link the relevant object module(s) with the main module."
這顯然不是我們想要列印的學號,所以我們要在堆疊中提供好puts函式的實參,然后把do_phase函式的回傳地址改為call puts指令的地址(0x80491ad),
接下來我們來研究應該把攻擊代碼寫到哪里,
先看一下節頭表(readelf -S phase2.o),找到我們能修改的.text節:
There are 14 section headers, starting at offset 0x3b4:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 0000d7 00 AX 0 0 1
[ 2] .rel.text REL 00000000 000308 000020 08 I 11 1 4
[ 3] .data PROGBITS 00000000 00010c 000008 00 WA 0 0 4
[ 4] .rel.data REL 00000000 000328 000010 08 I 11 3 4
[ 5] .bss NOBITS 00000000 000114 000000 00 WA 0 0 1
[ 6] .rodata PROGBITS 00000000 000114 00000a 00 A 0 0 1
[ 7] .comment PROGBITS 00000000 00011e 00001d 01 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 00000000 00013b 000000 00 0 0 1
[ 9] .eh_frame PROGBITS 00000000 00013c 000078 00 A 0 0 4
[10] .rel.eh_frame REL 00000000 000338 000018 08 I 11 9 4
[11] .symtab SYMTAB 00000000 0001b4 000110 10 12 10 4
[12] .strtab STRTAB 00000000 0002c4 000043 00 0 0 1
[13] .shstrtab STRTAB 00000000 000350 000063 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
再來看符號表(readelf -s phase2.o):
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS phase2.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 5
5: 00000000 0 SECTION LOCAL DEFAULT 6
6: 00000061 48 FUNC LOCAL DEFAULT 1 kfSvKnbh
7: 00000000 0 SECTION LOCAL DEFAULT 8
8: 00000000 0 SECTION LOCAL DEFAULT 9
9: 00000000 0 SECTION LOCAL DEFAULT 7
10: 00000000 4 OBJECT GLOBAL DEFAULT 3 phase_id
11: 00000000 97 FUNC GLOBAL DEFAULT 1 OOtZxJJdNH
12: 00000000 0 NOTYPE GLOBAL DEFAULT UND strlen
13: 00000000 0 NOTYPE GLOBAL DEFAULT UND strcmp
14: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts
15: 00000091 70 FUNC GLOBAL DEFAULT 1 do_phase
16: 00000004 4 OBJECT GLOBAL DEFAULT 3 phase
與phase1中的計算方法相同,我們很快能定位到do_phase函式在phase2.o中的位置(0x34+0x91=0xc5),
hexedit phase2.o:

因為攻擊代碼需要對main函式的堆疊幀進行操作,所以do_phase函式中保存%ebp舊值(main函式中%ebp的值)的代碼是沒有必要的,所以我們的攻擊代碼會把上圖中選中的位元組碼也覆寫掉,
現在我們用gdb除錯,觀察一下call do_phase之前的堆疊幀情況:
linux> gdb lb2
(gdb) b *0x80491a1
Breakpoint 1 at 0x80491a1
(gdb) r
Breakpoint 1, 0x080491a1 in main ()
(gdb) i r
eax 0x8049253 134517331
ecx 0xbffff380 -1073745024
edx 0xbffff3a4 -1073744988
ebx 0x0 0
esp 0xbffff360 0xbffff360
ebp 0xbffff368 0xbffff368
esi 0xb7fb5000 -1208266752
edi 0xb7fb5000 -1208266752
eip 0x80491a1 0x80491a1 <main+31>
eflags 0x206 [ PF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) x /24wx 0xbffff320
0xbffff320: 0x00000000 0xb7fb5000 0xb7e0ccb9 0xb7fb8588
0xbffff330: 0xb7fb5000 0xb7fb5000 0x00000000 0xb7e0cdfb
0xbffff340: 0xb7fb53fc 0x00040000 0x00000000 0x080492e3
0xbffff350: 0x00000001 0xbffff414 0xbffff41c 0x080492bb
0xbffff360: 0xb7fe6520 0xbffff380 0x00000000 0xb7df5b41
0xbffff370: 0xb7fb5000 0xb7fb5000 0x00000000 0xb7df5b41
(gdb) si
0x08049253 in do_phase ()
(gdb) i r
eax 0x8049253 134517331
ecx 0xbffff380 -1073745024
edx 0xbffff3a4 -1073744988
ebx 0x0 0
esp 0xbffff35c 0xbffff35c
ebp 0xbffff368 0xbffff368
esi 0xb7fb5000 -1208266752
edi 0xb7fb5000 -1208266752
eip 0x8049253 0x8049253 <do_phase>
eflags 0x206 [ PF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) x /24wx 0xbffff320
0xbffff320: 0x00000000 0xb7fb5000 0xb7e0ccb9 0xb7fb8588
0xbffff330: 0xb7fb5000 0xb7fb5000 0x00000000 0xb7e0cdfb
0xbffff340: 0xb7fb53fc 0x00040000 0x00000000 0x080492e3
0xbffff350: 0x00000001 0xbffff414 0xbffff41c 0x080491a3
0xbffff360: 0xb7fe6520 0xbffff380 0x00000000 0xb7df5b41
0xbffff370: 0xb7fb5000 0xb7fb5000 0x00000000 0xb7df5b41
然后我們來寫匯編代碼:
vim a2_main.s,然后在里面寫入以下指令:
pop %eax
add $0xa,%eax # change return address
push $0x36
push $0x38303032
push $0x32313931 # push my id
mov %esp,%ecx
push %ecx # push the starting address of my id
push %eax # push return address
push %ebp # original do_phase here
mov %esp,%ebp
寫完后保存并退出,然后gcc -c a2_main.s,objdump -d a2_main.o,得到攻擊指令的位元組碼:
a2_main.o: file format elf32-i386
Disassembly of section .text:
00000000 <.text>:
0: 58 pop %eax
1: 83 c0 0a add $0xa,%eax
4: 6a 36 push $0x36
6: 68 32 30 30 38 push $0x38303032
b: 68 31 39 31 32 push $0x32313931
10: 89 e1 mov %esp,%ecx
12: 51 push %ecx
13: 50 push %eax
14: 55 push %ebp
15: 89 e5 mov %esp,%ebp
然后hexedit phase2.o把攻擊代碼填進去:

gcc -no-pie -o lb2m main.o phase2.o,我們用gdb檢查一下程式是不是按照我們預期的想法來執行的,
展示一下在執行完上述攻擊代碼后、在do_phase函式的nop全部執行完之前的堆疊的狀態:
(gdb) si
0x0804926a in do_phase ()
(gdb) x /24wx 0xbffff320
0xbffff320: 0x00000000 0xb7fb5000 0xb7e0ccb9 0xb7fb8588
0xbffff330: 0xb7fb5000 0xb7fb5000 0x00000000 0xb7e0cdfb
0xbffff340: 0xb7fb53fc 0x00040000 0xbffff368 0x080491ad
0xbffff350: 0xbffff354 0x32313931 0x38303032 0x00000036
0xbffff360: 0xb7fe6520 0xbffff380 0x00000000 0xb7df5b41
0xbffff370: 0xb7fb5000 0xb7fb5000 0x00000000 0xb7df5b41
看起來沒啥問題,./lb2m,過關,
方法2
phase2.o里面除了do_phase函式外,還有幾個有著奇怪函式名的函式,因為這個階段只能修改.text節,所以我們只關注有call puts的函式(否則的話我們可以修改重定位資訊,使得我們可以在任意想要呼叫puts的位置進行呼叫,不過這樣很麻煩,因為添加重定位表項會改變重定位表的大小,然后還要調整節頭表內容等等一系列操作,幾乎是在重構phase2.o),
話不多說,先來看看這個函式,
鏈接之前(phase2.s):
00000061 <kfSvKnbh>:
61: 55 push %ebp
62: 89 e5 mov %esp,%ebp
64: 83 ec 08 sub $0x8,%esp
67: 83 ec 08 sub $0x8,%esp
6a: 68 02 00 00 00 push $0x2
6f: ff 75 08 pushl 0x8(%ebp)
72: e8 fc ff ff ff call 73 <kfSvKnbh+0x12>
77: 83 c4 10 add $0x10,%esp
7a: 85 c0 test %eax,%eax
7c: 75 10 jne 8e <kfSvKnbh+0x2d>
7e: 83 ec 0c sub $0xc,%esp
81: ff 75 0c pushl 0xc(%ebp)
84: e8 fc ff ff ff call 85 <kfSvKnbh+0x24>
89: 83 c4 10 add $0x10,%esp
8c: eb 01 jmp 8f <kfSvKnbh+0x2e>
8e: 90 nop
8f: c9 leave
90: c3 ret
鏈接之后(lb2.s):
08049223 <kfSvKnbh>:
8049223: 55 push %ebp
8049224: 89 e5 mov %esp,%ebp
8049226: 83 ec 08 sub $0x8,%esp
8049229: 83 ec 08 sub $0x8,%esp
804922c: 68 86 a1 04 08 push $0x804a186
8049231: ff 75 08 pushl 0x8(%ebp)
8049234: e8 f7 fd ff ff call 8049030 <strcmp@plt>
8049239: 83 c4 10 add $0x10,%esp
804923c: 85 c0 test %eax,%eax
804923e: 75 10 jne 8049250 <kfSvKnbh+0x2d>
8049240: 83 ec 0c sub $0xc,%esp
8049243: ff 75 0c pushl 0xc(%ebp)
8049246: e8 f5 fd ff ff call 8049040 <puts@plt>
804924b: 83 c4 10 add $0x10,%esp
804924e: eb 01 jmp 8049251 <kfSvKnbh+0x2e>
8049250: 90 nop
8049251: c9 leave
8049252: c3 ret
這個函式有兩個引數,一個在地址0x8(%ebp)上,要和地址0x804a186上的東西進行比較,如果相同才能call puts;另一個引數在地址0xc(%ebp)上,作為puts的實參,
(gdb) x /s 0x804a186
0x804a186: "MSLuleX"
(gdb) x /2wx 0x804a186
0x804a186: 0x754c534d 0x0058656c
這時候我們的目標已經非常清晰了:在do_phase函式中呼叫kfSvKnbh函式,并為它提供好引數,
不同于方法1,這次我們需要仔細地維護do_phase函式的堆疊幀,我們注意到do_phase函式在return之前用pop %ebp代替了leave指令(詳見本文phase2開頭處展示的代碼),這成立的條件是%esp和%ebp的值相同,但是這次我們會改動%esp,所以我們需要在攻擊代碼的最后多加一條指令mov %ebp,%esp(注意,方法1不需要這么做,但是方法2不這么做就會引起Segmentation fault),
在寫攻擊代碼之前還有最后一個問題:怎么call kfSvKnbh?我們不能把鏈接后的kfSvKnbh函式的地址寫進攻擊代碼,因為鏈接后的該函式的地址是對phase2.o中call的運算元進行重定位而得到的,而這個call的運算元怎么填寫正是我們現在正在探討的問題,
我們必須call這個函式的相對地址, **相對地址=kfSvKnbh函式的地址-call指令的下一條指令的地址,**但這個“call指令的下一條指令的地址”我們暫時確定不了,所以我們給call指令的運算元先隨便寫個值(只要保證call運算元的位元組碼大小為4位元組即可),把攻擊代碼寫完之后再用hexedit調整這個值,
a2c.o: file format elf32-i386
Disassembly of section .text:
00000000 <.text>:
0: 6a 36 push $0x36
2: 68 32 30 30 38 push $0x38303032
7: 68 31 39 31 32 push $0x32313931
c: 89 e2 mov %esp,%edx
e: 68 6c 65 58 00 push $0x58656c
13: 68 4d 53 4c 75 push $0x754c534d
18: 89 e1 mov %esp,%ecx
1a: 52 push %edx
1b: 51 push %ecx
1c: e8 fd ff ff ff call 0x1e
21: 89 ec mov %ebp,%esp
把這些攻擊指令位元組碼填到phase2.o里,然后objdump -d phase2.o(注:我們這次的攻擊代碼不覆寫do_phase中保存%ebp舊值的指令):

00000091 <do_phase>:
91: 55 push %ebp
92: 89 e5 mov %esp,%ebp
94: 6a 36 push $0x36
96: 68 32 30 30 38 push $0x38303032
9b: 68 31 39 31 32 push $0x32313931
a0: 89 e2 mov %esp,%edx
a2: 68 6c 65 58 00 push $0x58656c
a7: 68 4d 53 4c 75 push $0x754c534d
ac: 89 e1 mov %esp,%ecx
ae: 52 push %edx
af: 51 push %ecx
b0: e8 fd ff ff ff call b2 <do_phase+0x21>
b5: 89 ec mov %ebp,%esp
b7: 90 nop
b8: 90 nop
......
我們發現“call指令的下一條指令的地址”是0xb5,這樣我們就可以計算要call的相對地址了:0x61-0xb5=-0x54,所以call指令的運算元應該是0xffffffac,我們重新hexedit phase2.o:

然后就能call kfSvKnbh了(objdump -d phase2.o):
00000091 <do_phase>:
91: 55 push %ebp
92: 89 e5 mov %esp,%ebp
94: 6a 36 push $0x36
96: 68 32 30 30 38 push $0x38303032
9b: 68 31 39 31 32 push $0x32313931
a0: 89 e2 mov %esp,%edx
a2: 68 6c 65 58 00 push $0x58656c
a7: 68 4d 53 4c 75 push $0x754c534d
ac: 89 e1 mov %esp,%ecx
ae: 52 push %edx
af: 51 push %ecx
b0: e8 ac ff ff ff call 61 <kfSvKnbh>
b5: 89 ec mov %ebp,%esp
......
gcc -no-pie -o lb2c main.o phase2.o,./lb2c,完工,
方法3
按部就班呼叫kfSvKnbh被函式牽著鼻子走一向不是我的風格,本著能懶就懶的原則,我才懶得讓它strcmp,直接call puts它不香嘛,所以下面來簡單介紹一下直接jmp的流氓方法:
進入do_phase函式,先保存%ebp的舊值(這里可以看作是kfSvKnbh函式在保存%ebp的舊值,看到下面你就明白了),接下來我們把學號push入堆疊,再把學號的首地址也push入堆疊,然后直接jmp到kfSvKnbh函式的call puts,列印完學號之后執行kfSvKnbh函式的leave和ret,回傳地址是main函式呼叫do_phase函式的下一條指令的地址,
寫攻擊代碼的程序和方法2大同小異,jmp指令的運算元和call一樣,都是相對地址,所以確定jmp運算元的方法與方法2里確定call運算元的方法一模一樣,這里我們直接展示hexedit完之后的phase2.o反匯編的結果:
84: e8 fc ff ff ff call 85 <kfSvKnbh+0x24>
89: 83 c4 10 add $0x10,%esp
8c: eb 01 jmp 8f <kfSvKnbh+0x2e>
8e: 90 nop
8f: c9 leave
90: c3 ret
00000091 <do_phase>:
91: 55 push %ebp
92: 89 e5 mov %esp,%ebp
94: 6a 36 push $0x36
96: 68 32 30 30 38 push $0x38303032
9b: 68 31 39 31 32 push $0x32313931
a0: 89 e0 mov %esp,%eax
a2: 50 push %eax
a3: e9 dc ff ff ff jmp 84 <kfSvKnbh+0x23>
a8: 90 nop
a9: 90 nop
gcc -no-pie -o lb2j main.o phase2.o,./lb2j,大功告成,
方法n
前面幾種方法都是修改do_phase函式中的代碼,其實我們還可以修改另外幾個奇怪函式的代碼,在幾個函式之間jmp來jmp去,反正最終只要到達列印學號的效果就好了,所有方法本質上都是在一個合適的地方把學號push入堆疊,然后跳轉到call puts的指令處進行列印,我們所要做的作業就是維護好函式的堆疊幀,僅此而已,
phase3 符號決議
gcc -no-pie -o lb3 main.o phase3.o,objdump -d lb3 > lb3.s,先來看一下do_phase函式:
080491b2 <do_phase>:
80491b2: 55 push %ebp
80491b3: 89 e5 mov %esp,%ebp
80491b5: 83 ec 18 sub $0x18,%esp
80491b8: c7 45 ea 79 7a 67 69 movl $0x69677a79,-0x16(%ebp)
80491bf: c7 45 ee 75 68 6e 62 movl $0x626e6875,-0x12(%ebp)
80491c6: 66 c7 45 f2 65 00 movw $0x65,-0xe(%ebp)
80491cc: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp)
80491d3: eb 28 jmp 80491fd <do_phase+0x4b>
80491d5: 8d 55 ea lea -0x16(%ebp),%edx
80491d8: 8b 45 f4 mov -0xc(%ebp),%eax
80491db: 01 d0 add %edx,%eax
80491dd: 0f b6 00 movzbl (%eax),%eax
80491e0: 0f b6 c0 movzbl %al,%eax
80491e3: 0f b6 80 60 c0 04 08 movzbl 0x804c060(%eax),%eax
80491ea: 0f be c0 movsbl %al,%eax
80491ed: 83 ec 0c sub $0xc,%esp
80491f0: 50 push %eax
80491f1: e8 5a fe ff ff call 8049050 <putchar@plt>
80491f6: 83 c4 10 add $0x10,%esp
80491f9: 83 45 f4 01 addl $0x1,-0xc(%ebp)
80491fd: 8b 45 f4 mov -0xc(%ebp),%eax
8049200: 83 f8 08 cmp $0x8,%eax
8049203: 76 d0 jbe 80491d5 <do_phase+0x23>
8049205: 83 ec 0c sub $0xc,%esp
8049208: 6a 0a push $0xa
804920a: e8 41 fe ff ff call 8049050 <putchar@plt>
804920f: 83 c4 10 add $0x10,%esp
8049212: 90 nop
8049213: c9 leave
8049214: c3 ret
我們來分析一下do_phase函式做了些啥,這個函式先是把9個位元組的資訊放入了堆疊里,然后進入一個回圈次數為9的回圈,對于剛剛那9個位元組的資訊,每次回圈會從中取出1個位元組的資訊,第x次回圈就是取第x個位元組的資訊,然后將這個第x個位元組的資訊作為一個char陣列的索引,得到一個char字符,并呼叫putchar函式將其列印出來,
我們用gdb除錯一下,邊除錯邊解釋上面這段話的意思:
(gdb) b *0x80491d5
Breakpoint 1 at 0x80491d5
(gdb) r
Breakpoint 1, 0x080491d5 in do_phase ()
(gdb) i r
eax 0x0 0
ecx 0xbffff380 -1073745024
edx 0xbffff3a4 -1073744988
ebx 0x0 0
esp 0xbffff340 0xbffff340
ebp 0xbffff358 0xbffff358
esi 0xb7fb5000 -1208266752
edi 0xb7fb5000 -1208266752
eip 0x80491d5 0x80491d5 <do_phase+35>
eflags 0x293 [ CF AF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) x /9bx 0xbffff342
0xbffff342: 0x79 0x7a 0x67 0x69 0x75 0x68 0x6e 0x62
0xbffff34a: 0x65
%ebp的值是0xbffff358,所以9個位元組的資訊的起始位置是%ebp-0x16=0xbffff342,用x命令列印如上,
第1次回圈,回圈變數i(位于%ebp-0xc處)的值為0,程式把地址%ebp-0x16+i處的值放到%eax里:
80491d5: 8d 55 ea lea -0x16(%ebp),%edx
80491d8: 8b 45 f4 mov -0xc(%ebp),%eax
80491db: 01 d0 add %edx,%eax
80491dd: 0f b6 00 movzbl (%eax),%eax
80491e0: 0f b6 c0 movzbl %al,%eax
(gdb) b *0x80491e3
(gdb) c
Breakpoint 2, 0x80491e3 in do_phase ()
(gdb) p /x $eax
$1 = 0x79
然后程式把%eax中的值0x79作為索引,取出char陣列中第0x79個元素,呼叫putchar函式把它列印出來,
這個char陣列的首地址是0x804c060,我們用gdb查看一下這個陣列:
(gdb) x /72wx 0x804c060
0x804c060 <NQqPQyqUth>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c070 <NQqPQyqUth+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c080 <NQqPQyqUth+32>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c090 <NQqPQyqUth+48>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c0a0 <NQqPQyqUth+64>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c0b0 <NQqPQyqUth+80>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c0c0 <NQqPQyqUth+96>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c0d0 <NQqPQyqUth+112>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c0e0 <NQqPQyqUth+128>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c0f0 <NQqPQyqUth+144>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c100 <NQqPQyqUth+160>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c110 <NQqPQyqUth+176>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c120 <NQqPQyqUth+192>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c130 <NQqPQyqUth+208>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c140 <NQqPQyqUth+224>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c150 <NQqPQyqUth+240>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c160: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c170: 0x00000000 0x00000000 0x00000000 0x00000000
我們發現這個陣列大小是256(0x100),里面的元素都是0,所以列印出來的東西是一片空白,
下一次回圈也是同樣的程序,我們不再繼續除錯,do_phase函式的邏輯就是這么簡單,我們把這個邏輯用c代碼重寫如下:
char NQqPQyqUth[256];
void do_phase() {
int i = 0;
char cookie[10] = {0x79,0x7a,0x67,0x69,0x75,0x68,0x6e,0x62,0x65};
for (; i <= 8; ++i) putchar(NQqPQyqUth[cookie[i]]);
putchar('\n');
}
到這里我們大概知道這個階段要干些啥了:新建一個phase3_patch.o,定義一個賦有初始值的強符號NQqPQyqUth,使得phase3.o中的弱符號NQqPQyqUth能夠參考phase3_patch.o中的NQqPQyqUth,這樣,我們就能在phase3_patch.o中給NQqPQyqUth賦上合適的值,使其能在執行do_phase函式時根據索引值列印字符陣列NQqPQyqUth中的某些元素,所有列印出來的內容連起來就是我們想要的學號,
readelf -s phase3.o,我們先來看看這個phase3.o中的弱符號:
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS phase3.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 5
5: 00000000 0 SECTION LOCAL DEFAULT 6
6: 00000000 0 SECTION LOCAL DEFAULT 8
7: 00000000 0 SECTION LOCAL DEFAULT 9
8: 00000000 0 SECTION LOCAL DEFAULT 7
9: 00000000 4 OBJECT GLOBAL DEFAULT 3 phase_id
10: 00000020 256 OBJECT GLOBAL DEFAULT COM NQqPQyqUth
11: 00000000 99 FUNC GLOBAL DEFAULT 1 do_phase
12: 00000000 0 NOTYPE GLOBAL DEFAULT UND putchar
13: 00000004 4 OBJECT GLOBAL DEFAULT 3 phase
我們看到弱符號NQqPQyqUth的型別為為COMMON,表示還未被分配位置的未初始化的資料目標,對于COMMON符號,Value欄位給出對齊要求,Size給出最小的大小,從符號表中我們可以看出,NQqPQyqUth的最小大小是256,正好符合我們用gdb除錯出來的結果,
接著我們來定義這個弱符號所要參考的強符號,我們新建一個.c檔案(vim phase3_patch.c),只需要寫一行代碼:
char NQqPQyqUth[256] = "1";
然后gcc -c phase3_patch.c,生成.o檔案,
我們來看看這個補丁檔案的節頭表和符號表:
readelf -S phase3_patch.o
There are 9 section headers, starting at offset 0x238:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000000 00 AX 0 0 1
[ 2] .data PROGBITS 00000000 000040 000100 00 WA 0 0 32
[ 3] .bss NOBITS 00000000 000140 000000 00 WA 0 0 1
[ 4] .comment PROGBITS 00000000 000140 00001d 01 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 00000000 00015d 000000 00 0 0 1
[ 6] .symtab SYMTAB 00000000 000160 000080 10 7 7 4
[ 7] .strtab STRTAB 00000000 0001e0 000011 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 0001f1 000045 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
readelf -s phase3_patch.o
Symbol table '.symtab' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS a3.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 2
4: 00000000 0 SECTION LOCAL DEFAULT 3
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 0 SECTION LOCAL DEFAULT 4
7: 00000000 256 OBJECT GLOBAL DEFAULT 2 NQqPQyqUth
好了,現在我們知道,.data節在phase3_patch.o中的偏移量為0x40,符號NQqPQyqUth在.data節中的偏移量為0x0,所以符號NQqPQyqUth在phase3_patch.o中的偏移量為0x40,又因為符號NQqPQyqUth的大小為256(0x100),所以它的內容在位置0x40~0x13f上,
hexedit phase3_patch.o,因為我們給這個強符號賦的初值為字串"1",所以位置0x40上的值是31,其余255個位置的值上都是0:
00000040 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1...............
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
回想一下do_phase函式的功能,參看那個我們還原出的c代碼,想要把學號列印出來,那么就需要把cookie陣列({0x79,0x7a,0x67,0x69,0x75,0x68,0x6e,0x62,0x65})中的每一個元素加上0x40之后對應的位置上的值依次改為我們的學號:
00000040 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1...............
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0 00 00 38 00 00 36 00 31 30 32 00 00 00 00 30 00 ..8..6.102....0.
000000B0 00 00 00 00 00 32 00 00 00 31 39 00 00 00 00 00 .....2...19.....
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
gcc -no-pie -o lb3 main.o phase3.o phase3_patch.o,./lb3,搞定,
phase4 switch陳述句與重定位
這個階段的do_phase函式和上一階段的差不多,都是一個回圈9次的回圈,這里貼一下反匯編看得清楚一點(gcc -no-pie -o lb4 main.o phase4.o,objdump -d lb4 > lb4.s):
080491cb <do_phase>:
80491cb: 55 push %ebp
80491cc: 89 e5 mov %esp,%ebp
80491ce: 83 ec 28 sub $0x28,%esp
80491d1: c7 45 e6 53 4e 58 47 movl $0x47584e53,-0x1a(%ebp)
80491d8: c7 45 ea 4a 54 43 46 movl $0x4643544a,-0x16(%ebp)
80491df: 66 c7 45 ee 50 00 movw $0x50,-0x12(%ebp)
80491e5: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%ebp)
80491ec: e9 e0 00 00 00 jmp 80492d1 <do_phase+0x106>
80491f1: 8d 55 e6 lea -0x1a(%ebp),%edx
80491f4: 8b 45 f0 mov -0x10(%ebp),%eax
80491f7: 01 d0 add %edx,%eax
80491f9: 0f b6 00 movzbl (%eax),%eax
80491fc: 88 45 f7 mov %al,-0x9(%ebp)
80491ff: 0f be 45 f7 movsbl -0x9(%ebp),%eax
8049203: 83 e8 41 sub $0x41,%eax
8049206: 83 f8 19 cmp $0x19,%eax
8049209: 0f 87 b0 00 00 00 ja 80492bf <do_phase+0xf4>
804920f: 8b 04 85 88 a1 04 08 mov 0x804a188(,%eax,4),%eax
8049216: ff e0 jmp *%eax
8049218: c6 45 f7 38 movb $0x38,-0x9(%ebp)
804921c: e9 9e 00 00 00 jmp 80492bf <do_phase+0xf4>
8049221: c6 45 f7 65 movb $0x65,-0x9(%ebp)
8049225: e9 95 00 00 00 jmp 80492bf <do_phase+0xf4>
/*這里省略20多個跳轉表項*/
80492b4: c6 45 f7 34 movb $0x34,-0x9(%ebp)
80492b8: eb 05 jmp 80492bf <do_phase+0xf4>
80492ba: c6 45 f7 67 movb $0x67,-0x9(%ebp)
80492be: 90 nop
80492bf: 8d 55 dc lea -0x24(%ebp),%edx
80492c2: 8b 45 f0 mov -0x10(%ebp),%eax
80492c5: 01 c2 add %eax,%edx
80492c7: 0f b6 45 f7 movzbl -0x9(%ebp),%eax
80492cb: 88 02 mov %al,(%edx)
80492cd: 83 45 f0 01 addl $0x1,-0x10(%ebp)
80492d1: 8b 45 f0 mov -0x10(%ebp),%eax
80492d4: 83 f8 08 cmp $0x8,%eax
80492d7: 0f 86 14 ff ff ff jbe 80491f1 <do_phase+0x26>
80492dd: 8d 55 dc lea -0x24(%ebp),%edx
80492e0: 8b 45 f0 mov -0x10(%ebp),%eax
80492e3: 01 d0 add %edx,%eax
80492e5: c6 00 00 movb $0x0,(%eax)
80492e8: 83 ec 0c sub $0xc,%esp
80492eb: 8d 45 dc lea -0x24(%ebp),%eax
80492ee: 50 push %eax
80492ef: e8 3c fd ff ff call 8049030 <puts@plt>
80492f4: 83 c4 10 add $0x10,%esp
80492f7: 90 nop
80492f8: c9 leave
80492f9: c3 ret
因為和phase3的分析程序類似,所以我們就不展示用gdb分析函式邏輯的程序了,直接上還原出來的c代碼:
void do_phase() {
char data;
int i = 0;
char cookie[10] = {0x53,0x4e,0x58,0x47,0x4a,0x54,0x43,0x46,0x50};
char output[10];
for (; i <= 8; ++i) {
switch(cookie[i]-0x41){
/*
case x: data = some value; break;
(The range of x is from 0 to 19.)
*/
default: break;
}
output[i] = data;
}
output[i] = '\0';
puts(output);
}
其中output的首地址是%ebp-0x24,cookie的首地址是%ebp-0x1a,i存放在地址%ebp-0x10上,data存放在地址%ebp-0x9上,
和switch相關的反匯編代碼是這些,我們重新把它貼一下:
8049203: 83 e8 41 sub $0x41,%eax
8049206: 83 f8 19 cmp $0x19,%eax
8049209: 0f 87 b0 00 00 00 ja 80492bf <do_phase+0xf4>
804920f: 8b 04 85 88 a1 04 08 mov 0x804a188(,%eax,4),%eax
8049216: ff e0 jmp *%eax
8049218: c6 45 f7 38 movb $0x38,-0x9(%ebp)
804921c: e9 9e 00 00 00 jmp 80492bf <do_phase+0xf4>
8049221: c6 45 f7 65 movb $0x65,-0x9(%ebp)
8049225: e9 95 00 00 00 jmp 80492bf <do_phase+0xf4>
/*這里省略20多個跳轉表項*/
80492b4: c6 45 f7 34 movb $0x34,-0x9(%ebp)
80492b8: eb 05 jmp 80492bf <do_phase+0xf4>
80492ba: c6 45 f7 67 movb $0x67,-0x9(%ebp)
80492be: 90 nop
%eax存放的是cookie[i]的值,它先減去0x41,然后和0x19比較,如果它大于0x19,那么就執行default陳述句的內容,否則就跳轉到對應的表項執行相應的case陳述句,
我們用gdb看一下這個跳轉表:
(gdb) x /26wx 0x804a188
0x804a188: 0x08049290 0x0804923c 0x080492a2 0x08049284
0x804a198: 0x08049254 0x08049221 0x08049296 0x08049218
0x804a1a8: 0x080492ba 0x08049248 0x0804925a 0x0804924e
0x804a1b8: 0x080492a8 0x08049233 0x08049242 0x0804927e
0x804a1c8: 0x0804926c 0x080492ae 0x08049260 0x08049272
0x804a1d8: 0x080492b4 0x0804928a 0x0804929c 0x08049278
0x804a1e8: 0x08049266 0x0804922a
這下我們完全明白這個函式的執行程序了,舉個例子來說明:比如第8次回圈的時候,取cookie[7]=0x46,然后switch(0x46-0x41),執行case 5,如何跳轉到case 5呢?我們看到跳轉表的首地址是0x804a188,0x804a188+4*0x5=0x804a119c,所以我們取地址0x804a119c上的值0x08049221,jmp到這里繼續執行,這里就是case 5要執行的代碼,
接下來開始著手解決phase4,因為這個階段不允許修改.text節和重定位節的內容,所以我們只能從修改跳轉表的角度考慮,開關陳述句的跳轉表是存放在節.rodata節(只讀資料節)的,
readelf -S phase4.o,我們發現.rodata節的偏移量是0x1d8:
There are 15 section headers, starting at offset 0x548:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000158 00 AX 0 0 1
[ 2] .rel.text REL 00000000 0003e0 000010 08 I 12 1 4
[ 3] .data PROGBITS 00000000 0001a0 000038 00 WA 0 0 32
[ 4] .rel.data REL 00000000 0003f0 000010 08 I 12 3 4
[ 5] .bss NOBITS 00000000 0001d8 000000 00 WA 0 0 1
[ 6] .rodata PROGBITS 00000000 0001d8 00006c 00 A 0 0 4
[ 7] .rel.rodata REL 00000000 000400 0000d0 08 I 12 6 4
[ 8] .comment PROGBITS 00000000 000244 00001d 01 MS 0 0 1
[ 9] .note.GNU-stack PROGBITS 00000000 000261 000000 00 0 0 1
[10] .eh_frame PROGBITS 00000000 000264 000058 00 A 0 0 4
[11] .rel.eh_frame REL 00000000 0004d0 000010 08 I 12 10 4
[12] .symtab SYMTAB 00000000 0002bc 0000f0 10 13 11 4
[13] .strtab STRTAB 00000000 0003ac 000033 00 0 0 1
[14] .shstrtab STRTAB 00000000 0004e0 000067 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
再來看phase4.o的反匯編代碼(objdump -d phase4.o > phase4.s),注意偏移量(相對.text節)為0x6d的那一行,它告訴我們跳轉表相對于.rodata節的偏移量為0x4:
61: 83 e8 41 sub $0x41,%eax
64: 83 f8 19 cmp $0x19,%eax
67: 0f 87 b0 00 00 00 ja 11d <do_phase+0xf4>
6d: 8b 04 85 04 00 00 00 mov 0x4(,%eax,4),%eax
74: ff e0 jmp *%eax
76: c6 45 f7 38 movb $0x38,-0x9(%ebp)
7a: e9 9e 00 00 00 jmp 11d <do_phase+0xf4>
7f: c6 45 f7 65 movb $0x65,-0x9(%ebp)
83: e9 95 00 00 00 jmp 11d <do_phase+0xf4>
/*這里省略20多個跳轉表項*/
112: c6 45 f7 34 movb $0x34,-0x9(%ebp)
116: eb 05 jmp 11d <do_phase+0xf4>
118: c6 45 f7 67 movb $0x67,-0x9(%ebp)
11c: 90 nop
因此,跳轉表的起始位置為0x1d8+0x4=0x1dc,我們用hexedit phase4.o查看這個跳轉表:

和前面用gdb除錯可執行檔案lb4時看到的跳轉表相比,不同的只是跳轉的絕對地址,而跳轉的相對地址是一樣的,
接下來我們針對cookie陣列({0x53,0x4e,0x58,0x47,0x4a,0x54,0x43,0x46,0x50})的每一個元素,追蹤到跳轉表對應的表項,進行修改,使得最終列印出來的是我們的學號,
還是拿第8次回圈作為例子,我們取到cookie[7]=0x46,0x1dc+4*(0x46-0x41) = 0x1f0,在hexedit phase4.o中我們找到地址0x1f0,它對應的值是7f,這表示這次回圈中,執行switch所跳轉表項的內容相對于.text節的偏移量為0x7f,所以我們到phase4.s中尋找偏移量為0x7f的指令,發現它本來是把0x65賦值給data,但是我們學號的第8個數字是8,所以應該把0x38賦給data,我們到phase4.s中找到這條指令,它的偏移量為0x76,所以我們需要hexedit phase4.o把地址0x1f0處的值修改為76,
同理修改其它8個跳轉表項,完成之后gcc -no-pie -o lb4 main.o phase4.o,./lb4檢查一下列印結果,完成,
phase5 可重定位目標檔案
這個階段需要我們填寫被人為清零的重定位表項,
關于如何閱讀重定位表項這個問題,我們在phase1中粗略地描述了一下,現在我們來結合phase5的檔案詳細描述一下,
readelf -S phase5.o,節頭表:
There are 15 section headers, starting at offset 0x9a8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000272 00 AX 0 0 1
[ 2] .rel.text REL 00000000 0007f8 0000b8 08 I 12 1 4
[ 3] .data PROGBITS 00000000 0002c0 00011c 00 WA 0 0 32
[ 4] .rel.data REL 00000000 0008b0 000020 08 I 12 3 4
[ 5] .bss NOBITS 00000000 0003dc 000000 00 WA 0 0 1
[ 6] .rodata PROGBITS 00000000 0003e0 000100 00 A 0 0 32
[ 7] .rel.rodata REL 00000000 0008d0 000040 08 I 12 6 4
[ 8] .comment PROGBITS 00000000 0004e0 00001d 01 MS 0 0 1
[ 9] .note.GNU-stack PROGBITS 00000000 0004fd 000000 00 0 0 1
[10] .eh_frame PROGBITS 00000000 000500 0000d8 00 A 0 0 4
[11] .rel.eh_frame REL 00000000 000910 000030 08 I 12 10 4
[12] .symtab SYMTAB 00000000 0005d8 000190 10 13 9 4
[13] .strtab STRTAB 00000000 000768 00008f 00 0 0 1
[14] .shstrtab STRTAB 00000000 000940 000067 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
readelf -s phase5.o,符號表:
Symbol table '.symtab' contains 25 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS phase5.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 5
5: 00000000 0 SECTION LOCAL DEFAULT 6
6: 00000000 0 SECTION LOCAL DEFAULT 9
7: 00000000 0 SECTION LOCAL DEFAULT 10
8: 00000000 0 SECTION LOCAL DEFAULT 8
9: 00000000 250 OBJECT GLOBAL DEFAULT 3 ohMkhV
10: 00000000 93 FUNC GLOBAL DEFAULT 1 OOtZxJJdNH
11: 000000fc 4 OBJECT GLOBAL DEFAULT 3 phase_id
12: 00000100 10 OBJECT GLOBAL DEFAULT 3 tqdzfNje
13: 00000020 52 OBJECT GLOBAL DEFAULT 6 yAnKQn
14: 0000010c 4 OBJECT GLOBAL DEFAULT 3 aQSEth
15: 0000005d 146 FUNC GLOBAL DEFAULT 1 transform_code
16: 000000ef 60 FUNC GLOBAL DEFAULT 1 generate_code
17: 00000080 128 OBJECT GLOBAL DEFAULT 6 AycPNh
18: 0000012b 136 FUNC GLOBAL DEFAULT 1 encode_1
19: 00000000 0 NOTYPE GLOBAL DEFAULT UND strlen
20: 000001b3 135 FUNC GLOBAL DEFAULT 1 encode_2
21: 00000110 8 OBJECT GLOBAL DEFAULT 3 encoder
22: 0000023a 56 FUNC GLOBAL DEFAULT 1 do_phase
23: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts
24: 00000118 4 OBJECT GLOBAL DEFAULT 3 phase
readelf -r phase5.o,重定位資訊表:
Relocation section '.rel.text' at offset 0x7f8 contains 23 entries:
Offset Info Type Sym.Value Sym. Name
00000066 00000d01 R_386_32 00000020 yAnKQn
00000075 00000501 R_386_32 00000000 .rodata
00000086 00000d01 R_386_32 00000020 yAnKQn
00000000 00000000 R_386_NONE # need modification
00000000 00000000 R_386_NONE # need modification
000000bd 00000d01 R_386_32 00000020 yAnKQn
000000cc 00000d01 R_386_32 00000020 yAnKQn
00000000 00000000 R_386_NONE # need modification
000000f9 00000e01 R_386_32 0000010c aQSEth
00000107 00000e01 R_386_32 0000010c aQSEth
00000000 00000000 R_386_NONE # need modification
00000118 00000e01 R_386_32 0000010c aQSEth
00000138 00001302 R_386_PC32 00000000 strlen
00000000 00000000 R_386_NONE # need modification
00000162 00000e01 R_386_32 0000010c aQSEth
000001c0 00001302 R_386_PC32 00000000 strlen
000001e4 00001101 R_386_32 00000080 AycPNh
000001ea 00000e01 R_386_32 0000010c aQSEth
00000246 00001002 R_386_PC32 000000ef generate_code
00000000 00000000 R_386_NONE # need modification
00000256 00000c01 R_386_32 00000100 tqdzfNje
00000000 00000000 R_386_NONE # need modification
00000268 00001702 R_386_PC32 00000000 puts
Relocation section '.rel.data' at offset 0x8b0 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
000000fc 00000501 R_386_32 00000000 .rodata
00000110 00001201 R_386_32 0000012b encode_1
00000114 00001401 R_386_32 000001b3 encode_2
00000118 00001601 R_386_32 0000023a do_phase
Relocation section '.rel.rodata' at offset 0x8d0 contains 8 entries:
Offset Info Type Sym.Value Sym. Name
00000054 00000201 R_386_32 00000000 .text
00000058 00000201 R_386_32 00000000 .text
0000005c 00000201 R_386_32 00000000 .text
00000060 00000201 R_386_32 00000000 .text
00000064 00000201 R_386_32 00000000 .text
00000068 00000201 R_386_32 00000000 .text
0000006c 00000201 R_386_32 00000000 .text
00000070 00000201 R_386_32 00000000 .text
Relocation section '.rel.eh_frame' at offset 0x910 contains 6 entries:
Offset Info Type Sym.Value Sym. Name
00000020 00000202 R_386_PC32 00000000 .text
00000040 00000202 R_386_PC32 00000000 .text
00000060 00000202 R_386_PC32 00000000 .text
00000080 00000202 R_386_PC32 00000000 .text
000000a0 00000202 R_386_PC32 00000000 .text
000000c0 00000202 R_386_PC32 00000000 .text
這些表的內容這么多,我們該從哪里入手呢?不急,我們從重定位表開始一點一點研究,這個重定位表有好幾個節,我們先來看.rel.text節的第一個表項,這個表項中的符號名(Sym. Name)為yAnKQn,該符號在其所在的節中的偏移量(Sym.Value)為0x20:
Relocation section '.rel.text' at offset 0x7f8 contains 23 entries:
Offset Info Type Sym.Value Sym. Name
00000066 00000d01 R_386_32 00000020 yAnKQn
我們根據符號名到符號表中去尋找這個符號:
Symbol table '.symtab' contains 25 entries:
Num: Value Size Type Bind Vis Ndx Name
13: 00000020 52 OBJECT GLOBAL DEFAULT 6 yAnKQn
我們對比重定位表的表頭和符號表的表頭,發現重定位表中的Sym. Name對應著符號表中的Name;重定位表中的Sym. Value對應著符號表中的Value,原來兩張表是有關聯的!
我們繼續看符號表中的這個表項,我們發現這個符號的大小為52(0x34),Ndx為6,Ndx的值表示這個符號屬于節頭表中的第Ndx個表項,我們再來看節頭表:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 6] .rodata PROGBITS 00000000 0003e0 000100 00 A 0 0 32
這說明符號yAnKQn屬于.rodata節,從節頭表中可以看出.rodata節在phase5.o中的偏移量為0x3e0,大小為0x100,而符號yAnKQn在.rodata節中的偏移量(Sym.Value)為0x20,所以符號yAnKQn在phase5.o節中的偏移量為0x3e0+0x20=0x400,當使用hexedit修改phase5.o中的內容時,如果我們需要修改符號yAnKQn的內容,那么我們直接到偏移量為0x400的地方進行修改,就可以了,
現在我們已經把三張表的資訊都關聯起來了,我們回到重定位表,繼續分析符號yAnKQn的重定位資訊,來看這個符號的Info值0x00000d01,Info包含了兩部分資訊,一部分是它的高24位,表示這個符號的索引,對于符號yAnKQn來說,它的索引就是0xd,我們到符號表中看Num列,符號yAnKQn的Num值 為13(0xd),即它的符號索引值,Info的另一部分資訊是它的低8位,表示該符號的重定位型別,對于符號yAnKQn來說,它的重定位型別(重定位表中的Type列)是R_386_32,
最后來看重定位表中的Offset列,Offset是需要被修改的參考的節偏移,對于符號yAnKQn,它的Offset為0x66,它是被.text節被參考的(因為這個表項在重定位表的.rel.text節),objdump -d phase5.o > phase5.s,我們找到偏移量為0x66的位元組所在的指令:
63: 8b 04 85 00 00 00 00 mov 0x0(,%eax,4),%eax
從0x66開始的4位元組表示的值是0x0,它表示的實際地址為:0x400(符號定義地址)+0x0(相對于參考符號地址的偏移量)=0x400,即這條指令的含義是把地址0x400+4*%eax上的值賦給%eax,這里的0x400是指在phase5.o中的偏移量,
分析完一個表項,我們就大致掌握了分析重定位表的方法了,接下來要填寫重定位表項的作業簡直易如反掌,
part1
我們先來填前三個被清零的重定位表項,這三個表項被重定位的位置都位于transform_code函式中,題目pdf中已經給出了這個函式的代碼框架,我們對應自己的程式修改了對應變數的名稱,這樣看起來更清楚一些:
int transform_code(int code, int mode) {
switch(yAnKQn[mode]) {
case 0: ......;
case 1: ......
......
default: ......
}
return code;
}
根據重定位表我們知道,switch跳轉表的重定位資訊沒有被清空(.rel.text節的第二個表項),所以我們盲猜這個函式中其余的重定位內容都和yAnKQn有關,我們在這個函式中找到了三個看起來需要被重定位的指令,它們與重定位表中第一個表項對應的指令一模一樣:
97: 8b 04 85 00 00 00 00 mov 0x0(,%eax,4),%eax
a8: 8b 04 85 00 00 00 00 mov 0x0(,%eax,4),%eax
da: 8b 04 85 00 00 00 00 mov 0x0(,%eax,4),%eax
這樣我們就知道重定位表的前三個空表項本來是什么內容了:Offset列分別為0x9a、0xab、0xdd,其余列和.rel.text節的第一個表項一模一樣,接下來hexedit phase5.o,.rel.text節位于phase5.o偏移量為0x7f8處(從節頭表的第三個表項或者重定位表的第一行都可以得知這個資訊),.rel.text節的前三個表項(未被清零)在phase5.o中的存在形式是這樣的:
000007F8 66 00 00 00 01 0D 00 00 f.......
00000800 75 00 00 00 01 05 00 00 u.......
00000808 86 00 00 00 01 0D 00 00 ........
我們發現這里面只包含了每個表項的Offset和Info,所以我們照貓畫虎,把被清零表項的Offset和Info還原一下:
00000810 9A 00 00 00 01 0D 00 00 ........
00000818 AB 00 00 00 01 0D 00 00 ........
00000830 DD 00 00 00 01 0D 00 00 ........
part2
下面一個被清零的重定位表項稍微復雜一點,它要求我們還原一個呼叫函式的重定位表項,復雜之處在于,填這個表項沒有例子可以抄,需要我們自己去定位,
先把題目pdf中的代碼框架貼過來:
void generate_code(int cookie) {
... = cookie;
for(i=0; i<...; i++) {
... = transform_code(..., i);
}
}
重定位表項對應的反匯編代碼在這里:
10f: e8 fc ff ff ff call 110 <generate_code+0x21>
現在我們知道,我們要重定位的是transform_code函式,它的Offset為0x110,我們到符號表中尋找符號transform_code,發現它的Num為15(0xf),所以它在重定位表中的Info值的高24位為0x00000f,根據機器位元組碼我們知道,該函式的重定位型別為R_386_PC32,所以它在重定位表中的Info值的低8位為0x02,hexedit phase5.o填寫如下:
00000848 10 01 00 00 02 0F 00 00 ........
part3
接下來是encode_1函式,因為它和encode_2行為類似,所以我們直接對著encode_2函式的重定位表項填encode_1函式缺失的表項即可,我們抄這一行:
Relocation section '.rel.text' at offset 0x7f8 contains 23 entries:
Offset Info Type Sym.Value Sym. Name
000001e4 00001101 R_386_32 00000080 AycPNh
也就是hexedit phase5.o的這一行:
00000878 E4 01 00 00 01 11 00 00 ........
照抄該行的Info,然后在phase5.s中找到該缺失表項的Offset,填到被清零的這一行:
00000860 5C 01 00 00 01 11 00 00 \.......
總結一下抄寫流程:
->在重定位表中一一對應Offset位于encode_1函式中的表項和Offset位于encode_2函式中的表項
->根據encode_1函式中缺失的重定位表項,找到對應的encode_2函式中的重定位表項,抄下Info
->根據對應的encode_2函式中的重定位表項,到phase5.s的encode_2函式中定位該指令
->到phase5.s的encode_1函式的對應位置找與剛剛那條指令一模一樣的指令
->根據找到指令的機器位元組碼確定缺失表項的Offset
part4
最后看do_phase函式:
typedef int (*CODER)(char*);
CODER encoder[] = { ...... };
void do_phase() {
generate_code(...);
......; // Call one encoder here
printf("%s n", ...);
}
再看一下do_phase函式:
0000023a <do_phase>:
23a: 55 push %ebp
23b: 89 e5 mov %esp,%ebp
23d: 83 ec 08 sub $0x8,%esp
240: 68 f3 00 00 00 push $0xf3
245: e8 fc ff ff ff call 246 <do_phase+0xc>
24a: 83 c4 04 add $0x4,%esp
24d: a1 04 00 00 00 mov 0x4,%eax
252: 83 ec 0c sub $0xc,%esp
255: 68 00 00 00 00 push $0x0
25a: ff d0 call *%eax
25c: 83 c4 10 add $0x10,%esp
25f: 83 ec 0c sub $0xc,%esp
262: 68 00 00 00 00 push $0x0
267: e8 fc ff ff ff call 268 <do_phase+0x2e>
26c: 83 c4 10 add $0x10,%esp
26f: 90 nop
270: c9 leave
271: c3 ret
很明顯第6個空表項要我們把符號encoder填進去,第7個表項只要照抄它上面那個表項就行了,我們不再贅述,直接展示修改結果:
00000890 4E 02 00 00 01 15 00 00 N.......
000008A0 63 02 00 00 01 0C 00 00 c.......
修改結果
重定位表全部填完之后,objdump -r phase5.o,我們看一下復原之后的重定位表:
Relocation section '.rel.text' at offset 0x7f8 contains 23 entries:
Offset Info Type Sym.Value Sym. Name
00000066 00000d01 R_386_32 00000020 yAnKQn
00000075 00000501 R_386_32 00000000 .rodata
00000086 00000d01 R_386_32 00000020 yAnKQn
0000009a 00000d01 R_386_32 00000020 yAnKQn
000000ab 00000d01 R_386_32 00000020 yAnKQn
000000bd 00000d01 R_386_32 00000020 yAnKQn
000000cc 00000d01 R_386_32 00000020 yAnKQn
000000dd 00000d01 R_386_32 00000020 yAnKQn
000000f9 00000e01 R_386_32 0000010c aQSEth
00000107 00000e01 R_386_32 0000010c aQSEth
00000110 00000f02 R_386_PC32 0000005d transform_code
00000118 00000e01 R_386_32 0000010c aQSEth
00000138 00001302 R_386_PC32 00000000 strlen
0000015c 00001101 R_386_32 00000080 AycPNh
00000162 00000e01 R_386_32 0000010c aQSEth
000001c0 00001302 R_386_PC32 00000000 strlen
000001e4 00001101 R_386_32 00000080 AycPNh
000001ea 00000e01 R_386_32 0000010c aQSEth
00000246 00001002 R_386_PC32 000000ef generate_code
0000024e 00001501 R_386_32 00000110 encoder
00000256 00000c01 R_386_32 00000100 tqdzfNje
00000263 00000c01 R_386_32 00000100 tqdzfNje
00000268 00001702 R_386_PC32 00000000 puts
鏈接一下,我們來看看列印出來是什么,
linux> gcc -no-pie -o lb5 main.o phase5.o
linux> ./lb5
UuUHH[[!?
emm這個字串果然很奇怪,
*重定位的具體程序
既然這個階段是針對重定位的,那么我們就來分析分析重定位到底是個什么樣的程序,
我們知道,當聯結器進行鏈接作業時,會把所有型別相同的節合并為一個同一型別的新節,合并之后,原來那些節中的符號會存放在新節中的新位置上,也就是說,這些符號在原來的檔案中的地址與在新檔案中的地址是不一樣的,又因為在匯編代碼中,匯編指令對這些符號的參考是通過訪問符號地址來進行的,所以這樣一來,匯編指令的運算元也要變動,即匯編機器碼也需要改動,這一系列的變動程序,就是重定位,
我們以phase5.o的.text節為例,分析重定位的程序,.text節的重定位資訊存放在.rel.text節中,我們在前面已經通過readelf -r phase5.o查看過.rel.text節了,我們選兩個典型的例子來進行分析,即do_phase函式中的這兩個重定位條目:
Relocation section '.rel.text' at offset 0x7f8 contains 23 entries:
00000246 00001002 R_386_PC32 000000ef generate_code
0000024e 00001501 R_386_32 00000110 encoder
先看符號generate_code的重定位,我們來對比一下鏈接之前(phase5.s)和鏈接之后(objdump -d lb5 > lb5.s)的call generate_code指令:
245: e8 fc ff ff ff call 246 <do_phase+0xc>
80493f7: e8 a5 fe ff ff call 80492a1 <generate_code>
因為我們分析的是.text節,所以重定位修改的當然是.text節的內容,我們發現call指令的運算元在重定位的程序中從-0x4(0xfffffffc)變成了-0x15b(0xfffffea5),這個-0x15b是怎么計算出來的呢?
想知道如何計算,先要知道計算方法,我們查看重定位表,發現generate_code的重定位型別為R_386_PC32,它是以PC相對地址的方式進行重定位的,計算公式如下:
R_386_PC32型別:重定位后參考處的值 = 符號定義地址 - 符號參考所在地址 + 重定位前參考處的初始值
這個公式中的符號指的是重定位的符號,這里指的就是符號generate_code,所以符號定義地址就是generate_code函式的地址0x80492a1,符號參考所在地址就是call的運算元的地址0x80493f8,重定位前參考處的初始值是鏈接之前的call的運算元,即-0x4,根據公式一算,立即就能得到重定位后參考處的值-0x15b(0xfffffea5),
《計算機系統基礎》書上的公式是這樣的:
R_386_PC32型別:重定位后參考處的值 = 符號定義地址 - ((符號所在節的地址 + 符號參考處相對其所在節的偏移量) - 重定位前參考處的初始值)
我們發現這兩個公式其實是一樣的,
再看符號encoder的重定位:
24d: a1 04 00 00 00 mov 0x4,%eax
80493ff: a1 54 c1 04 08 mov 0x804c154,%eax
從重定位表中我們知道encoder的重定位型別為R_386_32,R_386_32型別以絕對地址的方式進行重定位的,計算公式如下:
R_386_32型別:重定位后參考處的值 = 符號定義地址 + 重定位前參考處的初始值
readelf -s lb5,我們發現encoder的符號定義地址為0x0804c150:
Symbol table '.symtab' contains 81 entries:
Num: Value Size Type Bind Vis Ndx Name
49: 0804c150 8 OBJECT GLOBAL DEFAULT 23 encoder
61: 080492a1 60 FUNC GLOBAL DEFAULT 13 generate_code
0x0804c150+0x4=0x0804c154,這樣的計算真是再簡單不過了,
phase6 位置無關代碼
part1 基礎知識
《計算機系統基礎》書中把符號之間的參考分為以下4種情況:
1. 模塊內程序呼叫和跳轉
2. 模塊內資料參考
3. 模塊間資料參考
4. 模塊間程序呼叫和跳轉
對于第1種情況模塊內程序呼叫和跳轉,我們已經在phase2中用call和jmp兩種解題方法較為詳細地演示過了,這里不再進行解釋了,對于第4種情況模塊間程序呼叫和跳轉,詳細講起來會涉及到延遲系結的知識,比較復雜,先挖個坑,有時間再講(下次一定),
對于第2種和第3種情況,我們撰寫一段簡單的代碼來進行演示(vim p1.c):
static int aaa = 1;
static int bbb = 1;
int ccc = 1;
extern int ddd;
void foo1() {
aaa = 2;
bbb = 2;
ccc = 2;
ddd = 2;
}
我們用-fpic選項來生成位置無關代碼(gcc -fpic -c p1.c),這樣生成出來的.o檔案中,對符號aaa和bbb的操作會被視為模塊內資料參考(第2種情況),對符號ccc和ddd的操作會被視為模塊間資料參考(第3種情況),
objdump -d p1.o > p1.s,我們來看反匯編:
p1.o: file format elf32-i386
Disassembly of section .text:
00000000 <foo1>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: e8 fc ff ff ff call 4 <foo1+0x4>
8: 05 01 00 00 00 add $0x1,%eax
d: c7 80 00 00 00 00 02 movl $0x2,0x0(%eax)
14: 00 00 00
17: c7 80 04 00 00 00 02 movl $0x2,0x4(%eax)
1e: 00 00 00
21: 8b 90 00 00 00 00 mov 0x0(%eax),%edx
27: c7 02 02 00 00 00 movl $0x2,(%edx)
2d: 8b 80 00 00 00 00 mov 0x0(%eax),%eax
33: c7 00 02 00 00 00 movl $0x2,(%eax)
39: 90 nop
3a: 5d pop %ebp
3b: c3 ret
Disassembly of section .text.__x86.get_pc_thunk.ax:
00000000 <__x86.get_pc_thunk.ax>:
0: 8b 04 24 mov (%esp),%eax
3: c3 ret
結合重定位表一起看(readelf -r p1.o):
Relocation section '.rel.text' at offset 0x24c contains 6 entries:
Offset Info Type Sym.Value Sym. Name
00000004 00000e02 R_386_PC32 00000000 __x86.get_pc_thunk.ax
00000009 00000f0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
0000000f 00000309 R_386_GOTOFF 00000000 .data
00000019 00000309 R_386_GOTOFF 00000000 .data
00000023 00000c2b R_386_GOT32X 00000008 ccc
0000002f 0000102b R_386_GOT32X 00000000 ddd
Relocation section '.rel.eh_frame' at offset 0x27c contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000020 00000202 R_386_PC32 00000000 .text
00000040 00000702 R_386_PC32 00000000 .text.__x86.get_pc_thu
在.text節偏移量為0x4的地方,代碼call了一個名字很長的函式__x86.get_pc_thunk.ax,我們知道,call指令相當于把下一條指令的地址入堆疊,而這個函式中mov (%esp),%eax就相當于把剛剛入堆疊的、call指令的下一條指令地址放到%eax上,所以這個函式的功能就是取得下一條指令的地址,放在%eax中,
call指令的下一條指令中又有一個重定位資訊,它的型別是R_386_GOTPC,這個型別書上沒有進行解釋,我們只需要知道它的功能即可:add之前,%eax的值是當前的指令地址(我們可以用多種方式稱呼它:當前%eip的值、剛剛那條call指令下一條指令的地址、該add指令相對于.text節的偏移量);add之后,%eax的值是全域偏移量表GOT的地址,
再下一條指令仍然有重定位資訊,這次重定位的符號我們很熟悉:.data,它重定位到偏移量0xf的地方,也就是說,movl $0x2,0x0(%eax)中的0x0會被重定位成.data相關的資訊,這次的重定位型別是R_386_GOTOFF,它會計算符號的值與GOT地址之間的差值,在這里符號的值就是.data的地址,我們仍然只需要知道它的功能即可:%eax(現在%eax的值是GOT的地址)加上這個重定位之后的值,得到的值是addr(.data)+0x0,這個值是個地址,mov指令會把立即數0x2賦值到這個地址上,
同理,再下一條指令也是這樣:把立即數0x2賦值到地址addr(.data)+0x4上,看一下符號表和節頭表我們會發現,其實這兩個地址就是aaa和bbb的地址:
readelf -s p1.o
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS p1.c
2: 00000000 0 SECTION LOCAL DEFAULT 2
3: 00000000 0 SECTION LOCAL DEFAULT 4
4: 00000000 0 SECTION LOCAL DEFAULT 5
5: 00000000 4 OBJECT LOCAL DEFAULT 4 aaa
6: 00000004 4 OBJECT LOCAL DEFAULT 4 bbb
7: 00000000 0 SECTION LOCAL DEFAULT 6
8: 00000000 0 SECTION LOCAL DEFAULT 8
9: 00000000 0 SECTION LOCAL DEFAULT 9
10: 00000000 0 SECTION LOCAL DEFAULT 7
11: 00000000 0 SECTION LOCAL DEFAULT 1
12: 00000008 4 OBJECT GLOBAL DEFAULT 4 ccc
13: 00000000 60 FUNC GLOBAL DEFAULT 2 foo1
14: 00000000 0 FUNC GLOBAL HIDDEN 6 __x86.get_pc_thunk.ax
15: 00000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
16: 00000000 0 NOTYPE GLOBAL DEFAULT UND ddd
readelf -S p1.o
There are 14 section headers, starting at offset 0x308:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .group GROUP 00000000 000034 000008 04 11 14 4
[ 2] .text PROGBITS 00000000 00003c 00003c 00 AX 0 0 1
[ 3] .rel.text REL 00000000 00024c 000030 08 I 11 2 4
[ 4] .data PROGBITS 00000000 000078 00000c 00 WA 0 0 4
[ 5] .bss NOBITS 00000000 000084 000000 00 WA 0 0 1
[ 6] .text.__x86.get_p PROGBITS 00000000 000084 000004 00 AXG 0 0 1
[ 7] .comment PROGBITS 00000000 000088 00001d 01 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 00000000 0000a5 000000 00 0 0 1
[ 9] .eh_frame PROGBITS 00000000 0000a8 00004c 00 A 0 0 4
[10] .rel.eh_frame REL 00000000 00027c 000010 08 I 11 9 4
[11] .symtab SYMTAB 00000000 0000f4 000110 10 12 12 4
[12] .strtab STRTAB 00000000 000204 000047 00 0 0 1
[13] .shstrtab STRTAB 00000000 00028c 00007a 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
對于區域符號(local symbol,也可稱為本地符號,在這里即符號aaa和bbb),我們注意到這里并沒有使用GOT的表項,代碼只是利用了GOT的地址找到了.data節的地址,從而定位到了需要被賦值的區域符號,
接下來是對全域符號ccc的重定位,觀察反匯編代碼我們很容易就能看出來區域符號和全域符號的區別,
對區域符號的賦值只進行了一次記憶體參考:
d: c7 80 00 00 00 00 02 movl $0x2,0x0(%eax)
14: 00 00 00
而對全域符號的賦值需要兩次記憶體參考:
21: 8b 90 00 00 00 00 mov 0x0(%eax),%edx
27: c7 02 02 00 00 00 movl $0x2,(%edx)
我們先來看第一次記憶體參考,這里又雙叒叕有一個重定位,而且還是我們沒見過的重定位型別:R_386_GOT32X,R_386_GOT32X型別會計算GOT的地址與符號的GOT項之間的距離,對于這兩條mov指令而言,%eax保存著GOT的地址,%eax+0x0(0x0重定位之后會變成另外一個值)是符號ccc的GOT表項的地址,然后對%eax+0x0進行記憶體參考,參考該地址上的表項的內容,而表項的內容是符號ccc的地址,所以賦給%edx的是符號ccc的地址,最后把立即數0x2賦值到符號ccc的地址上,賦值就完成了,
符號ddd和符號ccc是一樣的,我們就不再加以描述了,分析完自己撰寫的樣例,我們大概對位置無關代碼和GOT有了一定的了解,這樣就可以開始著手解決phase6了,
part2 填補代碼節
objdump -d phase6.o > phase6.s,我們發現這兩個函式的指令全被置為nop了:
Disassembly of section .text.__x86.get_pc_thunk.ax:
00000000 <__x86.get_pc_thunk.ax>:
0: 90 nop
1: 90 nop
2: 90 nop
3: 90 nop
Disassembly of section .text.__x86.get_pc_thunk.bx:
00000000 <__x86.get_pc_thunk.bx>:
0: 90 nop
1: 90 nop
2: 90 nop
3: 90 nop
對照我們自己撰寫的小樣例,我們發現這兩個函式就是用來獲取call指令的下一條指令的地址的,只不過一個函式是把地址放到%eax里,另一個放到%ebx里(觀察函式名可以看出來),
我們需要在這些nop處填上正確的指令,寫個匯編代碼,然后反匯編一下得到正確指令的機器碼(早在phase2的方法1中,我們就描述過如何進行這個操作,這里直接把反匯編結果貼出來):
00000000 <.text>:
0: 8b 04 24 mov (%esp),%eax
3: 8b 1c 24 mov (%esp),%ebx
6: c3 ret
然后我們需要尋找在phase6.o中填寫機器碼的位置,尋找的方法前面已經進行過很多遍了,這里不再描述尋找方法,我們直接把需要的資訊貼出來:
readelf -s phase6.o
Symbol table '.symtab' contains 42 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS phase6.c
2: 00000000 0 SECTION LOCAL DEFAULT 3
3: 00000000 0 SECTION LOCAL DEFAULT 5
4: 00000000 0 SECTION LOCAL DEFAULT 6
5: 00000000 0 SECTION LOCAL DEFAULT 7
6: 00000000 0 SECTION LOCAL DEFAULT 9
7: 00000000 0 SECTION LOCAL DEFAULT 11
8: 00000000 0 SECTION LOCAL DEFAULT 13
9: 00000000 0 SECTION LOCAL DEFAULT 14
10: 00000000 0 SECTION LOCAL DEFAULT 16
11: 00000102 0 NOTYPE LOCAL DEFAULT 3 .L6
12: 00000000 0 SECTION LOCAL DEFAULT 17
13: 0000008b 0 NOTYPE LOCAL DEFAULT 3 .L14
14: 00000090 0 NOTYPE LOCAL DEFAULT 3 .L13
15: 000000a6 0 NOTYPE LOCAL DEFAULT 3 .L12
16: 000000b9 0 NOTYPE LOCAL DEFAULT 3 .L11
17: 000000cd 0 NOTYPE LOCAL DEFAULT 3 .L10
18: 000000de 0 NOTYPE LOCAL DEFAULT 3 .L9
19: 000000f1 0 NOTYPE LOCAL DEFAULT 3 .L7
20: 00000000 0 SECTION LOCAL DEFAULT 15
21: 00000000 0 SECTION LOCAL DEFAULT 1
22: 00000000 0 SECTION LOCAL DEFAULT 2
23: 00000000 158 OBJECT GLOBAL DEFAULT 5 cjHQHR
24: 00000000 88 FUNC GLOBAL DEFAULT 3 OOtZxJJdNH
25: 00000000 0 FUNC GLOBAL HIDDEN 13 __x86.get_pc_thunk.ax
26: 00000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
27: 00000000 4 OBJECT GLOBAL DEFAULT 9 phase_id
28: 000000a0 10 OBJECT GLOBAL DEFAULT 5 tqdzfNje
29: 00000020 52 OBJECT GLOBAL DEFAULT 7 yAnKQn
30: 000000ac 4 OBJECT GLOBAL DEFAULT 5 aQSEth
31: 00000058 179 FUNC GLOBAL DEFAULT 3 transform_code
32: 0000010b 89 FUNC GLOBAL DEFAULT 3 generate_code
33: 00000000 0 FUNC GLOBAL HIDDEN 14 __x86.get_pc_thunk.bx
34: 00000080 128 OBJECT GLOBAL DEFAULT 7 AycPNh
35: 00000164 156 FUNC GLOBAL DEFAULT 3 encode_1
36: 00000000 0 NOTYPE GLOBAL DEFAULT UND strlen
37: 00000200 155 FUNC GLOBAL DEFAULT 3 encode_2
38: 00000000 8 OBJECT GLOBAL DEFAULT 11 encoder
39: 0000029b 82 FUNC GLOBAL DEFAULT 3 do_phase
40: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts
41: 00000008 4 OBJECT GLOBAL DEFAULT 11 phase
readelf -S phase6.o
There are 22 section headers, starting at offset 0xc5c:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .group GROUP 00000000 000034 000008 04 19 25 4
[ 2] .group GROUP 00000000 00003c 000008 04 19 33 4
[ 3] .text PROGBITS 00000000 000044 0002ed 00 AX 0 0 1
[ 4] .rel.text REL 00000000 0009e0 000118 08 I 19 3 4
[ 5] .data PROGBITS 00000000 000340 0000b0 00 WA 0 0 32
[ 6] .bss NOBITS 00000000 0003f0 000000 00 WA 0 0 1
[ 7] .rodata PROGBITS 00000000 000400 000100 00 A 0 0 32
[ 8] .rel.rodata REL 00000000 000af8 000040 08 I 19 7 4
[ 9] .data.rel.local PROGBITS 00000000 000500 000004 00 WA 0 0 4
[10] .rel.data.rel.loc REL 00000000 000b38 000008 08 I 19 9 4
[11] .data.rel PROGBITS 00000000 000504 00000c 00 WA 0 0 4
[12] .rel.data.rel REL 00000000 000b40 000018 08 I 19 11 4
[13] .text.__x86.get_p PROGBITS 00000000 000510 000004 00 AXG 0 0 1
[14] .text.__x86.get_p PROGBITS 00000000 000514 000004 00 AXG 0 0 1
[15] .comment PROGBITS 00000000 000518 00001d 01 MS 0 0 1
[16] .note.GNU-stack PROGBITS 00000000 000535 000000 00 0 0 1
[17] .eh_frame PROGBITS 00000000 000538 000110 00 A 0 0 4
[18] .rel.eh_frame REL 00000000 000b58 000040 08 I 19 17 4
[19] .symtab SYMTAB 00000000 000648 0002a0 10 20 23 4
[20] .strtab STRTAB 00000000 0008e8 0000f6 00 0 0 1
[21] .shstrtab STRTAB 00000000 000b98 0000c4 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
定位到phase6.o中偏移量為0x510和0x514的地方,修改如下:
00000510 8B 04 24 C3 8B 1C 24 C3 ..$...$.
part3 填補重定位資訊
最后我們來填寫被清零的重定位資訊,
readelf -r phase6.o,因為重定位的節比較多,而需要我們填寫的只有.rel.text節,所以我們只展示.rel.text節:
Relocation section '.rel.text' at offset 0x9e0 contains 35 entries:
Offset Info Type Sym.Value Sym. Name
00000007 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
00000000 00000000 R_386_NONE
0000005c 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
00000000 00000000 R_386_NONE
00000067 00001d2b R_386_GOT32X 00000020 yAnKQn
00000083 00000509 R_386_GOTOFF 00000000 .rodata
00000000 00000000 R_386_NONE
00000000 00000000 R_386_NONE
000000bb 00001d2b R_386_GOT32X 00000020 yAnKQn
00000000 00000000 R_386_NONE
000000e0 00001d2b R_386_GOT32X 00000020 yAnKQn
000000f3 00001d2b R_386_GOT32X 00000020 yAnKQn
00000113 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
00000000 00000000 R_386_NONE
00000000 00000000 R_386_NONE
00000000 00000000 R_386_NONE
00000141 00001f04 R_386_PLT32 00000058 transform_code
0000014c 00001e2b R_386_GOT32X 000000ac aQSEth
0000016c 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
00000172 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
0000017d 00002404 R_386_PLT32 00000000 strlen
000001a0 0000222b R_386_GOT32X 00000080 AycPNh
000001aa 00001e2b R_386_GOT32X 000000ac aQSEth
00000208 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
0000020e 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
00000219 00002404 R_386_PLT32 00000000 strlen
0000023c 0000222b R_386_GOT32X 00000080 AycPNh
00000246 00001e2b R_386_GOT32X 000000ac aQSEth
000002a3 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
000002a9 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
000002b6 00002004 R_386_PLT32 0000010b generate_code
000002bf 0000262b R_386_GOT32X 00000000 encoder
000002cb 00001c2b R_386_GOT32X 000000a0 tqdzfNje
000002da 00001c2b R_386_GOT32X 000000a0 tqdzfNje
000002e0 00002804 R_386_PLT32 00000000 puts
觀看phase6.s我們知道(不看也能猜到),get_pc_thunk函式的重定位表項的后面一條肯定是GOT的重定位表項,所以我們對照著已有表項,直接就能填掉三個空表項:
Relocation section '.rel.text' at offset 0x9e0 contains 35 entries:
Offset Info Type Sym.Value Sym. Name
00000007 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
00000000 00000000 R_386_NONE
0000005c 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
00000000 00000000 R_386_NONE
......
00000113 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
00000000 00000000 R_386_NONE
......
0000016c 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
00000172 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
填寫程序不再描述了,直接展示hexedit phase6.o改動后的那三行:
000009E8 0C 00 00 00 0A 1A 00 00 ........
000009F8 61 00 00 00 0A 1A 00 00 a.......
00000A48 19 01 00 00 0A 1A 00 00 ........
剩下的空都是phase5里的重定位資訊,我們到phase6.s里確定Offset,然后直接填寫,
switch跳轉表部分:
90: 8b 80 00 00 00 00 mov 0x0(%eax),%eax
a6: 8b 80 00 00 00 00 mov 0x0(%eax),%eax
cd: 8b 80 00 00 00 00 mov 0x0(%eax),%eax
00000A10 92 00 00 00 2B 1D 00 00 ....+...
00000A18 A8 00 00 00 2B 1D 00 00 ....+...
00000A28 CF 00 00 00 2B 1D 00 00 ....+...
generate_code函式部分:
11d: 8b 83 00 00 00 00 mov 0x0(%ebx),%eax
131: 8b 83 00 00 00 00 mov 0x0(%ebx),%eax
14a: 8b 83 00 00 00 00 mov 0x0(%ebx),%eax
00000A50 1F 01 00 00 2B 1E 00 00 ....+...
00000A58 33 01 00 00 2B 1E 00 00 3...+...
填完之后再次readelf -r phase6.o,簡單看一眼完整的重定位資訊表:
Relocation section '.rel.text' at offset 0x9e0 contains 35 entries:
Offset Info Type Sym.Value Sym. Name
00000007 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
0000000c 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
0000005c 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
00000061 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
00000067 00001d2b R_386_GOT32X 00000020 yAnKQn
00000083 00000509 R_386_GOTOFF 00000000 .rodata
00000092 00001d2b R_386_GOT32X 00000020 yAnKQn
000000a8 00001d2b R_386_GOT32X 00000020 yAnKQn
000000bb 00001d2b R_386_GOT32X 00000020 yAnKQn
000000cf 00001d2b R_386_GOT32X 00000020 yAnKQn
000000e0 00001d2b R_386_GOT32X 00000020 yAnKQn
000000f3 00001d2b R_386_GOT32X 00000020 yAnKQn
00000113 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
00000119 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
0000011f 00001e2b R_386_GOT32X 000000ac aQSEth
00000133 00001e2b R_386_GOT32X 000000ac aQSEth
00000141 00001f04 R_386_PLT32 00000058 transform_code
0000014c 00001e2b R_386_GOT32X 000000ac aQSEth
滿懷好奇地看一下這次列印出來的是什么奇怪的字串:
linux> gcc -no-pie -o lb6 main.o phase6.o
linux> ./lb6
a*aTTgg-K
好啦,linklab的6個階段全部完成啦,
,%eax
cd: 8b 80 00 00 00 00 mov 0x0(%eax),%eax
00000A10 92 00 00 00 2B 1D 00 00 …+…
00000A18 A8 00 00 00 2B 1D 00 00 …+…
00000A28 CF 00 00 00 2B 1D 00 00 …+…
generate_code函式部分:
```assembly
11d: 8b 83 00 00 00 00 mov 0x0(%ebx),%eax
131: 8b 83 00 00 00 00 mov 0x0(%ebx),%eax
14a: 8b 83 00 00 00 00 mov 0x0(%ebx),%eax
00000A50 1F 01 00 00 2B 1E 00 00 ....+...
00000A58 33 01 00 00 2B 1E 00 00 3...+...
填完之后再次readelf -r phase6.o,簡單看一眼完整的重定位資訊表:
Relocation section '.rel.text' at offset 0x9e0 contains 35 entries:
Offset Info Type Sym.Value Sym. Name
00000007 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
0000000c 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
0000005c 00001902 R_386_PC32 00000000 __x86.get_pc_thunk.ax
00000061 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
00000067 00001d2b R_386_GOT32X 00000020 yAnKQn
00000083 00000509 R_386_GOTOFF 00000000 .rodata
00000092 00001d2b R_386_GOT32X 00000020 yAnKQn
000000a8 00001d2b R_386_GOT32X 00000020 yAnKQn
000000bb 00001d2b R_386_GOT32X 00000020 yAnKQn
000000cf 00001d2b R_386_GOT32X 00000020 yAnKQn
000000e0 00001d2b R_386_GOT32X 00000020 yAnKQn
000000f3 00001d2b R_386_GOT32X 00000020 yAnKQn
00000113 00002102 R_386_PC32 00000000 __x86.get_pc_thunk.bx
00000119 00001a0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
0000011f 00001e2b R_386_GOT32X 000000ac aQSEth
00000133 00001e2b R_386_GOT32X 000000ac aQSEth
00000141 00001f04 R_386_PLT32 00000058 transform_code
0000014c 00001e2b R_386_GOT32X 000000ac aQSEth
滿懷好奇地看一下這次列印出來的是什么奇怪的字串:
linux> gcc -no-pie -o lb6 main.o phase6.o
linux> ./lb6
a*aTTgg-K
好啦,linklab的6個階段全部完成啦,
特別鳴謝
最后要特別感謝我的舍友,他在我寫linklab時提供了許多有建設性意義的幫助與指導,我在寫linklab時有很多沒弄明白的知識點,多虧他幫助我一起梳理,他也寫了一篇關于linklab的實驗記錄,我在寫本文時參考了一些他的記錄,大家可以去幫他點個贊:Linklab實驗 - 知乎
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/232502.html
標籤:其他
上一篇:Ubuntu18.04架設Elasticsearch單臺機器(第一次寫博客)
下一篇:cgb2008-京淘day12
