NJU-ICS-linklab 從零開始複習程序的鏈接與ELF

linklab實驗記錄

實驗材料:https://github.com/qmj0923/NJU-ICS-linklab

實驗環境:debian-10.5.0-i386-netinst虛擬機

參考書目:

​ 1.《計算機系統基礎(第2版)》(袁春風、餘子濠 編著)第4章

​ 2.《深入理解計算機系統(原書第3版)》第7章

網絡課程參考:中國大學MOOC——計算機系統基礎(四):編程與調試實踐

修改ELF文件的工具:hexedit

安裝:sudo apt-get install hexedit

用戶手冊:man hexedit

運行:hexedit <filename>

保存:Ctrl+WF2

保存並退出: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.oobjdump -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.sobjdump -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.oobjdump -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.oobjdump -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實驗 - 知乎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章