二進(jìn)制安全之棧溢出
本文主要介紹二進(jìn)制安全的棧溢出內(nèi)容。
棧基礎(chǔ)
內(nèi)存四區(qū)
代碼區(qū)(.text):這個(gè)區(qū)域存儲著被裝入執(zhí)行的二進(jìn)制機(jī)器代碼,處理器會到這個(gè)區(qū)域取指令執(zhí)行。
數(shù)據(jù)區(qū)(.data):用于存儲全局變量和靜態(tài)變量等。
堆區(qū):動(dòng)態(tài)地分配和回收內(nèi)存,進(jìn)程可以在堆區(qū)動(dòng)態(tài)地請求一定大小的內(nèi)存,并在用完后歸還給堆區(qū)。
地址由高到低生長
棧區(qū):用于動(dòng)態(tài)地存儲函數(shù)之間的調(diào)用關(guān)系,以保證被調(diào)用函數(shù)在返回時(shí)恢復(fù)到母函數(shù)中繼續(xù)執(zhí)行;此外
局部變量
也存儲在棧區(qū)。地址由低到高生長
BSS段:(bss segment)通常是指用來存放程序中未初始化的全局變量的一塊內(nèi)存區(qū)域,屬于靜態(tài)內(nèi)存分配。
棧的概念
一種數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)存儲方式為先進(jìn)后出,壓棧(push)和出棧(pop)
每個(gè)程序都有自己的進(jìn)程地址空間,進(jìn)程地址空間中的某一部分就是該程序的棧,用于保存函數(shù)調(diào)用信息和局部變量
程序的棧是從進(jìn)程空間的高地址向低地址增長的,數(shù)據(jù)是從低地址向高地址存放的
函數(shù)調(diào)用
函數(shù)調(diào)用經(jīng)常嵌套,在同一時(shí)刻,堆棧中會有多個(gè)函數(shù)的信息。
棧幀
每個(gè)未完成運(yùn)行的函數(shù)占用一個(gè)獨(dú)立的連續(xù)區(qū)域,稱作棧幀。
基本流程
;調(diào)用前
push arg3 ? ? ? ? ? ? ? ;32位esp-4,64位esp-8
push arg2
push arg1
call func ? ? ? ? ? ? ? ;1. 壓入當(dāng)前指令的地址,即保存返回地址 2. jmp到調(diào)用函數(shù)的入口地址
push ebp ? ? ? ? ? ? ? ?;保存舊棧幀的底部,在func執(zhí)行完成后在pop ebp
mov ebp,esp ? ? ? ? ;設(shè)置新棧幀的底部
sub esp,xxx ? ? ? ? ;設(shè)置新棧幀的頂部
詳細(xì)流程
int func_b(int b1,int b2)
{
?int var_b1,var_b2;
?var_b1 = b1+b2;
?var_b2 = b1-b2;
?return var_b1 * var_b2;
}
int func_a(int a1,int a2)
{
?int var_a;
?var_a = fuc_b(a1+a2);
?return var_a;
}
int main(int argc,char** argv,char **envp)
{
?int var_main;
?var_main = func_A(4,3);
?return 0;
}
參數(shù)傳遞
x86
通過棧傳參
先壓入最后一個(gè)參數(shù)
x64
rdi rsi rdx rcx r8 r9 接收后六個(gè)參數(shù)
之后的參數(shù)通過棧傳參
64位的利用方式
構(gòu)造rop鏈
ROPgadget –binary level3_x64 –only ‘pop|ret’
# Gadgets information
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret ? ? ? ? ? ?
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret
依次找pop rdi,pop rsi..,pop r9 ,這些寄存器里面存放的是參數(shù),可以通過pop覆蓋其中的內(nèi)容
棧溢出
棧溢出指的是程序向棧中某個(gè)變量寫入的字節(jié)數(shù)超過了這個(gè)變量本身申請的字節(jié)數(shù),因而導(dǎo)致棧中與之相鄰的變量的值被改變。
棧溢出目的
破壞程序內(nèi)存結(jié)構(gòu)
執(zhí)行system(/bin/sh)
執(zhí)行shellcode
棧溢出思路
判斷溢出點(diǎn)
常見的危險(xiǎn)函數(shù):
輸入:gets scanf vscanf
輸出:sprintf
字符串:strcpy strcat bcopy
判斷padding
計(jì)算我們所要操作的地址和所要覆蓋的地址的距離
IDA靜態(tài)分析中常見的三種索引方式
a. 相對于?;刂返乃饕?通過查看EBP相對偏移獲得 char name[32]; [esp+0h] [ebp-28h] ==> 0×28+0×4
b. 相對于棧頂指針的索引,需要加上ESP到EBP的偏移,然后轉(zhuǎn)換為a方式
c. 直接地址索引,相當(dāng)于直接給出了地址
覆寫內(nèi)容
覆蓋函數(shù)返回地址
覆蓋棧上某個(gè)變量的內(nèi)容,如局部變量和參數(shù)
Ret2text
返回到某個(gè)代碼段的地址,如.text:0804863A mov dword ptr [esp], offset command ; "/bin/sh"要求我們控制程序執(zhí)行程序本身已有的代碼
Ret2shellocde
跳轉(zhuǎn)到我們在棧中輸入的代碼,一般在沒有開啟NX保護(hù)的時(shí)候使用.
ret2shellcode的目標(biāo)即在棧上寫入布局好的shellcode,利用ret_address返回到shellcode處執(zhí)行代碼。
Ret2syscal
讓程序返回到系統(tǒng)調(diào)用,調(diào)用syscall或execve執(zhí)行某個(gè)程序,對于靜態(tài)編譯的程序,沒有l(wèi)ibc,只好通過execve執(zhí)行shellcode了。
syscall ?--->rax syscall 0x3b ?==>execve rax
? ? ? ? ? ? ? --->rdi ?path ?==> /bin/sh ? ? ? ? ?rdi
? ? ? ? ? ? ? --->rsi ?argv ?/ ? ? ? ? ? ? ? ? ? ? ? ? ? rsi
? ? ? ? ? ? ? --->rdx env ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?rdx
int execve(const char *filename, char *const argv[],char *const envp[]);
? ? ? ?execve("/bin/sh",null.null) 等同于system("bin/sh")
syscall(32位程序?yàn)閕nt80)會根據(jù)系統(tǒng)調(diào)用號查找syscall_table,execve對應(yīng)的系統(tǒng)調(diào)用號是0x3b。
當(dāng)我們給syscall的第一個(gè)參數(shù)即rax中寫入0x3b時(shí)(32位程序?yàn)?xb),就會找到syscall_table[0x3b],即syscall_execve,然后通過execve啟動(dòng)程序。
找syscall和int 80的方法:ROPgadget –binary test –only ‘int/syscall’
靜態(tài)編譯的程序沒有system等函數(shù)的鏈接支持,故一般利用ret2syscall構(gòu)造棧溢出
Ret2libc
如找到system函數(shù)在動(dòng)態(tài)鏈接庫libc中的地址,將return的內(nèi)容覆蓋為該地址,跳轉(zhuǎn)執(zhí)行
leak出libc_addr + call system + 執(zhí)行system(‘/bin/sh’)
難點(diǎn):libc動(dòng)態(tài)加載,每次基址都會變化,如何泄露libc的地址?
思路:got —> read_addr() —>libc
read_addr – libc_base = offsset (不變)
libc_base = read_addr – offset
bin/sh的來源 : 程序本身或libc或者寫一個(gè)/bin/sh到bss段
binsh = libc.search(“/bin/sh”).next()
其它
判斷是否是否為動(dòng)態(tài)編譯
? ? ~/stack/day_4 ldd ret2text
? ?linux-gate.so.1 => ?(0xf7f36000)
? ?libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d60000) ?-->libc的版本,也可以vmmap查看
? ?/lib/ld-linux.so.2 (0xf7f38000)
判斷l(xiāng)ibc的版本
a. 本地直接通過vmmap查看
b. 遠(yuǎn)程的根據(jù)函數(shù)后幾位的偏移得到
? ? ? ?libc-database
? ? ? ? ? ?link:https://github.com/lieanu/libc-database.git
? ? ? ? ? ?usage: ./find func_name offset
? ? ? ? ? ?exemplify: ./find gets 5a0
? ? ? ? ? ?effection:
? ? ? ? ? ? ? ? ? ?? ?libc-database git :( master) ./find gets 5a0
? ? ? ? ? ? ? ? ? ?archive-eglibc (id libc6_2.17-93ubuntu4_i386)
c: 5a0怎么來的?
? ?.got.plt:0804A010 off_804A010 ? ? dd offset gets
? ?pwndbg> x/20gz 0x0804a010
0x804a010 <gets@got.plt>: ? 0x08048476f7e643e0 ?0x08048496f7e64ca0
0x804a020 <__gmon_start__@got.plt>: 0x080484b6080484a6 ?0xf7e65360f7e1d540
0x804a030 <rand@got.plt>: ? 0x080484f6080484e6 ?0x0000000000000000
0x804a040 <stdin@@GLIBC_2.0>: ? 0x00000000f7fb75a0 ?0x0000000000000000
0x804a050: ?0x0000000000000000 ?0x0000000000000000
0x804a060 <stdout@@GLIBC_2.0>: ?0x00000000f7fb7d60 ?0x0000000000000000
0x804a070: ?0x0000000000000000 ?0x0000000000000000
0x804a080: ?0x0000000000000000 ?0x0000000000000000
0x804a090: ?0x0000000000000000 ?0x0000000000000000
0x804a0a0: ?0x0000000000000000 ?0x0000000000000000
64位程序和32位程序的區(qū)別
1. 傳參方式
? ? ? ?64位:rdi rsi rdx rcx r8 r9
? ? ? ?32位:通過棧傳參
2. syscall & int 80
??臻g布局
// 偽代碼
A(int arg_a1,int arg_a2)
B(int arg_b1,int arg_b2,int arg_b3)
C()
-------------------------------------
// B的壓棧流程
---> ESP ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //指向棧頂,隨著壓棧不斷抬高
? ? ? ?buf[128] ? ? ? ? ? ? ? ? ? ?//局部變量
? ? ? ?EBP ? ? ? ? ? ? ? ? ? ? ? ? //保存舊棧幀的底部,4字節(jié)
? ? ? ?return ? ? ? ? ? ? ? ? ? ? ?//這是B的返回地址,即C
? ? ? ?arg_b1
? ? ? ?arg_b2
? ? ? ?arg_b3 ? ? ? ? ?
? ?-->EBP ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //指向當(dāng)前棧幀的底部,隨著壓棧不斷抬高,指向舊棧幀
棧溢出原理
當(dāng)局部變量buf超過128字節(jié),會向下覆蓋EBP,return以及參數(shù)的內(nèi)容。
構(gòu)造return
將buf 的 132到136字節(jié)的空間輸入shellcode的地址
會跳轉(zhuǎn)執(zhí)行shellcode
保護(hù)機(jī)制
NX
保護(hù)原理
堆棧不可執(zhí)行保護(hù),bss段也不可執(zhí)行,windows下為DEP,可通過gcc -z execstack關(guān)閉
開啟NX后再把return的內(nèi)容覆蓋為一段shellcode,在開啟NX的時(shí)候,不能執(zhí)行。
繞過原理 : 32位
實(shí)現(xiàn)A函數(shù)執(zhí)行的方法,即構(gòu)建ROP鏈
return —> fake_addr —> A
將B的參數(shù)從arg_b2到arg_b3也覆蓋成A的參數(shù)
// 偽代碼
A(int arg_a1,int arg_a2)
B(int arg_b1,int arg_b2,int arg_b3)
C(int arg_c1,int arg_c2)
-------------------------------------
// B的壓棧流程
---> ESP ?
? ? ? ?buf[128]
? ? ? ?EBP ?
? ? ? ?return //把return的內(nèi)容覆蓋為A的地址
? ? ? ?arg_b1 //程序在調(diào)用A函數(shù)的時(shí)候,把下一個(gè)棧數(shù)據(jù)當(dāng)作A的返回地址,因此需要在再下一條語句的時(shí)候開始覆蓋參數(shù)
? ? ? ?arg_b2 ?arg_a2 //將B的參數(shù)用A的參數(shù)覆蓋掉
? ? ? ?arg_b3 arg_a1
-->EBP
借鑒上面的方法,在調(diào)用A之后,再調(diào)用C,構(gòu)建ROP鏈
這時(shí)不能把系統(tǒng)認(rèn)為的A的返回地址的arg_b1覆蓋為C的返回地址,不然會向上覆蓋arg_a2和arg_a2,導(dǎo)致A無法正常執(zhí)行。
這時(shí)需要再找一個(gè)return語句,程序里面通常含有pop-pop-ret的鏈
ROPgadget –binary –only ‘pop|ret’ : 自動(dòng)尋找rop鏈
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret //選擇這個(gè)地址,代碼段無NX
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret
將arg_b1覆蓋為addr_pop_pop_ret的地址:4006b0
此時(shí)將將arg_a1 pop到r14,arg_a2 pop到r15,然后ret
將ret的內(nèi)容覆蓋為C的入口地址,即可!
程序執(zhí)行如下代碼:
// 偽代碼
A(int arg_a1,int arg_a2)
B(int arg_b1,int arg_b2,int arg_b3)
C(int arg_c1,int arg_c2)
-------------------------------------
// B的壓棧流程
---> ESP ?
? ? ? ?buf[128]
? ? ? ?EBP ?
? ? ? ?return //-->fake_addr_A
? ? ? ?arg_b1 //-->4006b0 ?addr_pop_pop_ret
? ? ? ?arg_b2 ?arg_a1 //pop r14
? ? ? ?arg_b3 arg_a2 //pop r15
? ? ? ?ret // --->fake_addr_C
? ? ? ?0 // --->C的返回地址,現(xiàn)在沒用了
? ? ? ?arg_c1
? ? ? ?arg_c2
-->EBP
完整流程
使用buf將??臻g覆蓋
在B退出的時(shí)候ret到A
依次取覆蓋之后的A的兩個(gè)參數(shù),執(zhí)行A函數(shù)
返回到pop_pop_ret的地址
將ret的地址覆蓋為C的地址
將C的返回地址置空
寫入C的參數(shù)
執(zhí)行C函數(shù)
總結(jié)
A函數(shù)的功能通常時(shí)”/bin/sh”
C函數(shù)的功能為system
上述流程執(zhí)行完則可以達(dá)到反彈shell的目的
由于程序不在棧上執(zhí)行而是在代碼段中執(zhí)行,所有可以繞過NX保護(hù)機(jī)制。
Canary(金絲雀)
保護(hù)原理
開啟canary后,會在程序的EBP與ESP之間的位置隨機(jī)插入一段md5值,占4字節(jié)或8字節(jié)。
canary為一段以 /0 結(jié)尾的一串md5值,如123456/0,起截?cái)嘧饔?,防止打印?
在程序return之前與內(nèi)核地址[fs:0x28]異或校驗(yàn)md5值
異或結(jié)果為1時(shí)報(bào)錯(cuò)退出,為0時(shí)正常ret。
幾種思路
如果能在棧中拿到md5值,在指定位置可以精準(zhǔn)覆蓋之。
將從內(nèi)核中取的md5值,設(shè)置為自己定義的值,覆蓋的時(shí)候覆蓋自己定義的值。
gcc開啟canary
參數(shù):-fstack-protector :啟用保護(hù),不過只為局部變量中含有數(shù)組的插入保護(hù)
參數(shù):-fstack-protector-all :為所有函數(shù)插入保護(hù)
參數(shù):-fstack-protector-strong -fstack-protector-explicit :只對明確有stack-protect 屬性的函數(shù)啟用保護(hù)
參數(shù):-fo-stack-protector :禁用保護(hù)
3種利用方法利用
覆蓋canary的最后一個(gè)字節(jié)
利用棧溢出將”\0″覆蓋掉,則可以將canary打印出來。
smash
leak stackguard — top
查看開啟的保護(hù)機(jī)制
? > ~/stack/day_1> checksec leak_canary
[*] '/root/stack/day_1/leak_canary'
? ?Arch: ? ? i386-32-little
? ?RELRO: ? ?Partial RELRO
? ?Stack: ? ?Canary found
? ?NX: ? ? ? NX enabled
? ?PIE: ? ? ?No PIE (0x8048000)
PIE
保護(hù)原理
讓程序能裝載在隨機(jī)的地址,主要是代碼段的地址隨機(jī)化,改變的是高位的基地址。
gdb中使用show proc info 可以顯示代碼段的基地址
–enabled-default-pie開啟 -no-pie關(guān)閉
通常與ALSR聯(lián)合使用
ALSR
保護(hù)原理
每次加載程序,使其地址空間分布隨機(jī)化,即使可執(zhí)行文件開啟PIE保護(hù),還需要系統(tǒng)開啟ASLR才會真正打亂基址。主要是堆棧和libc的地址隨機(jī)化。
修改/proc/sys/kernel/randommize_va_space來控制ASLR的開關(guān)。
棧溢出進(jìn)階
pwntools
# Pwntools環(huán)境預(yù)設(shè)
from pwn import *
context.arch = "amd64/i386" #指定系統(tǒng)架構(gòu)
context.terminal = ["tmux,"splitw","-h"] #指定分屏終端
context.os = "linux" ? ? #context用于預(yù)設(shè)環(huán)境
# 庫信息
elf = ELF('./PWNME') # ELF載入當(dāng)前程序的ELF,以獲取符號表,代碼段,段地址,plt,got信息
libc = ELF('lib/i386-linux-gnu/libc-2.23.so') ?# 載入libc的庫,可以通過vmmap查看
/*
首先使用ELF()獲取文件的句柄,然后使用這個(gè)句柄調(diào)用函數(shù),如
>>> e = ELF('/bin/cat')
>>> print hex(e.address) # 文件裝載的基地址
>>> print hex(e.symbols['write']) # plt中write函數(shù)地址
>>> print hex(e.got['write']) ?# GOT表中write符號的地址
>>> print hex(e.plt['write']) # PLT表中write符號的地址 ? ? ? ? ? ? ? ? ? ?
*/ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?
# Pwntools通信 ? ? ? ? ? ? ? ? ? ?
p = process('./pwnme') # 本地 process與程序交互
r = remote('exploitme.example.com',3333) ? ?# 遠(yuǎn)程
? ? ? ? ? ? ? ? ? ?
# 交互
recv() # 接收數(shù)據(jù),一直接收
recv(numb=4096,timeout=default) # 指定接收字節(jié)數(shù)與超時(shí)時(shí)間 ? ? ? ? ? ? ? ? ? ?
recvuntil("111") # 接收到111結(jié)束,可以裁剪,如.[1:4]
recbline() # 接收到換行結(jié)束
recvline(n) # 接收到n個(gè)換行結(jié)束
recvall() # 接收到EOF
recvrepeat(timeout=default) #接收到EOF或timeout
send(data) # 發(fā)送數(shù)據(jù)
sendline(data) # 發(fā)送一行數(shù)據(jù),在末尾會加\n
sendlineafter(delims,data) # ? 在程序接收到delims再發(fā)送data ? ? ? ? ? ? ? ? ?
r.send(asm(shellcraft.sh())) ?# 信息通信交互 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
r.interactive() # send payload后接收當(dāng)前的shell
? ? ? ? ? ? ? ? ?
# 字符串與地址的轉(zhuǎn)換
p64(),p32() ?#將字符串轉(zhuǎn)化為ascii字節(jié)流
u64(),u32() ?#將ascii的字節(jié)流解包為字符串地址 ? ? ? ? ?
got & plt
在IDA中選擇view-open subview - segment
可以直接查看到got和plt段
.plt:08048440 ; __unwind {
.plt:08048440 ? ? ? ? ? ? ? ? push ? ?ds:dword_804A004
.plt:08048446 ? ? ? ? ? ? ? ? jmp ? ? ds:dword_804A008 ;804A008是got表的地址
.plt:08048446 sub_8048440 ? ? endp
plt段的某個(gè)地址存放著指令 jmp got
.got.plt:0804A00C off_804A00C ? ? dd offset printf ? ? ? ?; DATA XREF: _printf↑r
.got.plt:0804A010 off_804A010 ? ? dd offset gets ? ? ? ? ?; DATA XREF: _gets↑r
.got.plt:0804A014 off_804A014 ? ? dd offset time ? ? ? ? ?; DATA XREF: _time↑r
.got.plt:0804A018 off_804A018 ? ? dd offset puts ? ? ? ? ?; DATA XREF: _puts↑r
.got.plt:0804A01C off_804A01C ? ? dd offset system ? ? ? ?; DATA XREF: _system↑r
.got.plt:0804A020 off_804A020 ? ? dd offset __gmon_start__
.got.plt:0804A020 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; DATA XREF: ___gmon_start__↑r
.got.plt:0804A024 off_804A024 ? ? dd offset srand ? ? ? ? ; DATA XREF: _srand↑r
.got.plt:0804A028 off_804A028 ? ? dd offset __libc_start_main
.got.plt:0804A028 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; DATA XREF: ___libc_start_main↑r
.got.plt:0804A02C off_804A02C ? ? dd offset setvbuf ? ? ? ; DATA XREF: _setvbuf↑r
.got.plt:0804A030 off_804A030 ? ? dd offset rand ? ? ? ? ?; DATA XREF: _rand↑r
.got.plt:0804A034 off_804A034 ? ? dd offset __isoc99_scanf
got段中存放著程序中函數(shù)的地址,可以避免每次調(diào)用某個(gè)函數(shù)的時(shí)候去libc庫中尋找。
函數(shù)調(diào)用流程
找到plt表.plt表存放指令
跳轉(zhuǎn)到got表,got表存放地址,不能填在return的位置
找到對應(yīng)的func_addr
沒有的時(shí)候跳轉(zhuǎn)到libc中取出函數(shù),并緩存到got表
plt2leakgot
plt["write"](1,got("write"),4)
通過plt的write函數(shù)leak出got的地址
libc_csu_init
在所有的64位程序中都含有l(wèi)ibc_csu_init函數(shù)
.text:0000000000400650 __libc_csu_init proc near ? ? ? ? ? ? ? ; DATA XREF: _start+16↑o
.text:0000000000400650 ; __unwind {
.text:0000000000400690
.text:0000000000400690 loc_400690: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400690 ? ? ? ? ? ? ? ? mov ? ? rdx, r13 // 4. 利用點(diǎn),將r13給到了rdx
.text:0000000000400693 ? ? ? ? ? ? ? ? mov ? ? rsi, r14 // 5. 控制rsi
.text:0000000000400696 ? ? ? ? ? ? ? ? mov ? ? edi, r15d // 6. 控制rdi的低四位
.text:0000000000400699 ? ? ? ? ? ? ? ? call ? ?qword ptr [r12+rbx*8] //7. 給rbx賦0,相當(dāng)于call [r12]
.text:000000000040069D ? ? ? ? ? ? ? ? add ? ? rbx, 1
.text:00000000004006A1 ? ? ? ? ? ? ? ? cmp ? ? rbx, rbp
.text:00000000004006A4 ? ? ? ? ? ? ? ? jnz ? ? short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6 ? ? ? ? ? ? ? ? add ? ? rsp, 8
.text:00000000004006AA ? ? ? ? ? ? ? ? pop ? ? rbx //1. 控制函數(shù)從這里執(zhí)行
.text:00000000004006AB ? ? ? ? ? ? ? ? pop ? ? rbp
.text:00000000004006AC ? ? ? ? ? ? ? ? pop ? ? r12 //8. 給r12添一個(gè)main_addr
.text:00000000004006AE ? ? ? ? ? ? ? ? pop ? ? r13 //2. 通過??刂苧13
.text:00000000004006B0 ? ? ? ? ? ? ? ? pop ? ? r14
.text:00000000004006B2 ? ? ? ? ? ? ? ? pop ? ? r15
.text:00000000004006B4 ? ? ? ? ? ? ? ? retn ? //3. ret到 mov ?rdx, r13
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp
//實(shí)現(xiàn)通過??刂苧dx
目的
在64位提供三個(gè)參數(shù)的用法
利用原理
程序在啟動(dòng)main函數(shù)之前,都由glibc的標(biāo)準(zhǔn)c庫啟動(dòng),即由libc_csu_init啟動(dòng)main函數(shù)
libc_csu_init可以獲得一個(gè)有4個(gè)參數(shù)調(diào)用的地方,比如系統(tǒng)調(diào)用函數(shù)syscall
如syscall—>rax syacall 0x3b ==>execve
—>rdi path “/bin/sh”
—>rsi argv
—>rdx env
通過syscall調(diào)用execve,執(zhí)行execve(“/bin/sh”,null,null),等價(jià)于system(“/bin/sh”)
syscall(32位程序?yàn)閕nt80)會根據(jù)系統(tǒng)調(diào)用號查找syscall_table,execve對應(yīng)的系統(tǒng)調(diào)用號是0x3b。
當(dāng)我們給syscall的第一個(gè)參數(shù)即rax中寫入0x3b時(shí)(32位程序?yàn)?xb),就會找到syscall_table[0x3b],即syscall_execve,然后通過execve啟動(dòng)程序。
找syscall和int 80的方法:ROPgadget –binary test –only ‘int/syscall’
ret2_dl_runtime_resolve
解決32位無輸出函數(shù)的情況,64位使用IOfile的方式。
找對應(yīng)的plt中的地址
跳轉(zhuǎn)到對應(yīng)的got表中,got中如果有,則執(zhí)行對應(yīng)函數(shù)
如果跳轉(zhuǎn)到的got對應(yīng)位置沒有值,got會向后累加一個(gè)地址,然后跳轉(zhuǎn)到plt[0],壓入兩個(gè)參數(shù),一個(gè)是index,一個(gè)是與DYNAMIC有關(guān)的參數(shù),稱為link_map(動(dòng)態(tài)鏈接用到的名字,如puts),然后再調(diào)用dl_runtime_resolve(link_map_obj,reloc_index)
push ? ?cs:qword_602008
.plt:00000000004007A6 ? ? ? ? ? ? ? ? jmp ? ? cs:qword_602010
dl_runtime_resolve實(shí)際上是一個(gè)解析實(shí)際地址的函數(shù),根據(jù)函數(shù)名稱做解析,然后寫回到plt的index對應(yīng)的got
調(diào)用完之后,會根據(jù)參數(shù)調(diào)用解析出來的地址,比如解析出來的puts函數(shù),那么會調(diào)用puts函數(shù),并且寫入puts_got中
結(jié)束后,接著運(yùn)行程序
因此,我們向DYNAMIC中寫入puts字符串就可以了