go conn 讀取byte數組后是否要_【技術推薦】正向角度看Go逆向

Go語言具有開發效率高,運行速度快,跨平臺等優點,因此正越來越多的被攻擊者所使用,其生成的是可直接運行的二進制文件,因此對它的分析類似于普通C語言可執行文件分析,但是又有所不同,本文將會使用正向與逆向結合的方式描述這些區別與特征。

語言特性

1?Compile與Runtime

Go語言類似于C語言,目標是一個二進制文件,逆向的也是native代碼,它有如下特性:

●?強類型檢查的編譯型語言,接近C但擁有原生的包管理,內建的網絡包,協程等使其成為一款開發效率更高的工程級語言。

●?作為編譯型語言它有運行速度快的優點,但是它又能通過內置的運行時符號信息實現反射這種動態特性。

●?作為一種內存安全的語言,它不僅有內建的垃圾回收,還在編譯與運行時提供了大量的安全檢查。

可見盡管它像C編譯的可執行文件但是擁有更復雜的運行時庫,Go通常也是直接將這些庫統一打包成一個文件的,即使用靜態鏈接,因此其程序體積較大,且三方庫、標準庫與用戶代碼混在一起,需要區分,這可以用類似flirt方法做區分(特別是對于做了混淆的程序)。在分析Go語言編寫的二進制程序前,需要弄清楚某一操作是發生在編譯期間還是運行期間,能在編譯時做的事就在編譯時做,這能實現錯誤前移并提高運行效率等,而為了語言的靈活性引入的某些功能又必須在運行時才能確定,在這時就需要想到運行時它應該怎么做,又需要為它提供哪些數據,例如:

func main() {    s := [...]string{"hello", "world"}    fmt.Printf("%s %s\n", s[0], s[1])  // func Printf(format string, a ...interface{}) (n int, err error)

在第二行定義了一個字符串數組,第三行將其輸出,編譯階段就能確定元素訪問的指令以及下標訪問是否越界,于是就可以去除s的類型信息。但是由于Printf的輸入是interface{}類型,因此在編譯時它無法得知傳入的數據實際為什么類型,但是作為一個輸出函數,希望傳入數字時直接輸出,傳入數組時遍歷輸出每個元素,那么在傳入參數時,就需要在編譯時把實際參數的類型與參數綁定后再傳入Printf,在運行時它就能根據參數綁定的信息確定是什么類型了。其實在編譯時,編譯器做的事還很多,從逆向看只需要注意它會將很多操作轉換為runtime的內建函數調用,這些函數定義在cmd/compile/internal/gc/builtin/runtime.go,并且在src/runtime目錄下對應文件中實現,例如:

a := "123" + b + "321" 

將被轉換為concatstring3函數調用:

0x0038 00056 (str.go:4) LEAQ    go.string."123"(SB), AX0x003f 00063 (str.go:4) MOVQ    AX, 8(SP)0x0044 00068 (str.go:4) MOVQ    $3, 16(SP)0x004d 00077 (str.go:4) MOVQ    "".b+104(SP), AX0x0052 00082 (str.go:4) MOVQ    "".b+112(SP), CX0x0057 00087 (str.go:4) MOVQ    AX, 24(SP)0x005c 00092 (str.go:4) MOVQ    CX, 32(SP)0x0061 00097 (str.go:4) LEAQ    go.string."321"(SB), AX0x0068 00104 (str.go:4) MOVQ    AX, 40(SP)0x006d 00109 (str.go:4) MOVQ    $3, 48(SP)0x0076 00118 (str.go:4) PCDATA  $1, $10x0076 00118 (str.go:4) CALL    runtime.concatstring3(SB)

我們將在匯編中看到大量這類函數調用,本文將在對應章節介紹最常見的一些函數。若需要觀察某語法最終編譯后的匯編代碼,除了使用ida等也可以直接使用如下三種方式:

go tool compile -N -l -S once.gogo tool compile -N -l once.go ; go tool objdump -gnu -s Do once.ogo build -gcflags -S once.go

2 動態與類型系統

盡管是編譯型語言,Go仍然提供了一定的動態能力,這主要表現在接口與反射上,而這些能力離不開類型系統,它需要保留必要的類型定義以及對象和類型之間的關聯,這部分內容無法在二進制文件中被去除,否則會影響程序運行,因此在Go逆向時能獲取到大量的符號信息,大大簡化了逆向的難度,對此類信息已有大量文章介紹并有許多優秀的的工具可供使用,例如go_parser與redress,因此本文不再贅述此內容,此處推薦《Go二進制文件逆向分析從基礎到進階——綜述》。

本文將從語言特性上介紹Go語言編寫的二進制文件在匯編下的各種結構,為了表述方便此處定義一些約定:

1. 盡管Go并非面向對象語言,但是本文將Go的類型描述為類,將類型對應的變量描述為類型的實例對象。

2. 本文分析的樣例是x64上的樣本,通篇會對應該平臺敘述,一個機器字認為是64bit。

3. 本文會涉及到Go的參數和匯編層面的參數描述,比如一個復數在Go層面是一個參數,但是它占16字節,在匯編上將會分成兩部分傳遞(不使用xmm時),就認為匯編層面是兩個參數。

4. 一個復雜的實例對象可以分為索引頭和數據部分,它們在內存中分散存儲,下文提到一種數據所占內存大小是指索引頭的大小,因為這部分是逆向關注的點,詳見下文字符串結構。

數據類型

1 數值類型

數值類型很簡單只需要注意其大小即可:

7c62e72d449d778a6de76501c60d9bd3.png

2 字符串string

Go語言中字符串是二進制安全的,它不以\0作為終止符,一個字符串對象在內存中分為兩部分,一部分為如下結構,占兩個機器字用于索引數據:

type StringHeader struct {    Data uintptr            // 字符串首地址    Len  int                // 字符串長度}

而它的另一部分才存放真正的數據,它的大小由字符串長度決定,在逆向中重點關注的是如上結構,因此說一個string占兩個機器字,后文其他結構也按這種約定。例如下圖使用printf輸出一個字符串"hello world",它會將上述結構入棧,由于沒有終止符ida無法正常識別字符串結束因此輸出了很多信息,我們需要依靠它的第二個域(此處的長度0x0b)決定它的結束位置:

3a9aaebb17522e235e5b6c77d0df32e2.png

字符串常見的操作是字符串拼接,若拼接的個數不超過5個會調用concatstringN,否則會直接調用concatstrings,它們聲明如下,可見在多個字符串拼接時參數形式不同:

func concatstring2(*[32]byte, string, string) stringfunc concatstring3(*[32]byte, string, string, string) stringfunc concatstring4(*[32]byte, string, string, string, string) stringfunc concatstring5(*[32]byte, string, string, string, string, string) stringfunc concatstrings(*[32]byte, []string) string

因此在遇到concatstringN時可以跳過第一個參數,隨后入棧的參數即為字符串,而遇到concatstrings時,跳過第一個參數后匯編層面還剩三個參數,其中后兩個一般相同且指明字符串個數,第一個參數則指明字符串數組的首地址,另外經常出現的是string與[]byte之間的轉換,詳見下文slice部分。提醒一下,可能是優化導致一般來說在棧內一個純字符串的兩部分在物理上并沒有連續存放,例如下圖調用macaron的context.Query("username")獲取到的應該是一個代表username的字符串,但是它們并沒有被連續存放:

4bc87fb856dbc9434dd5556adae98fad.png

因此ida中通過定義本地結構體去解析string會遇到困難,其他結構也存在這類情況,氣!

3 數組array

類似C把字符串看作char數組,Go類比如array和string的結構類似,其真實數據也是在內存里連續存放,而使用如下結構索引數據,對數組里的元素訪問其地址偏移在編譯時就能確定,總之逆向角度看它也是占兩個機器字:

type arrayHeader struct {    Data uintptr            Len int}

數組有三種存儲位置,當數組內元素較少時可以直接存于棧上,較多時存于數據區,而當數據會被返回時會存于堆上。如下定義了三個局部變量,但是它們將在底層表現出不同的形態:

func ArrDemo() *[3]int {    a := [...]int{1, 2, 3}    b := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7}    c := [...]int{1, 2, 3}    if len(a) < len(b) {return &c}    return nil}

變量a的匯編如下,它直接在棧上定義并初始化:

92b1c4b30af31da4abbbe859c3088857.png

變量b的匯編如下,它的初始值被定義在了數據段并進行拷貝初始化:

f57e39f3226a2e2b181ab53779105175.png

事實上更常見的拷貝操作會被定義為如下這類函數,因此若符號信息完整遇到無法識別出的函數一般也就是數據拷貝函數:

f3ff96d05eb658b8707640a1faf3e17e.png

變量c的匯編如下,盡管它和a的值一樣,但是它的地址會被返回,如果在C語言中這種寫法會造成嚴重的后果,不過Go作為內存安全的語言在編譯時就識別出了該問題(指針逃逸)并將其放在了堆上,此處引出了runtime.newobject函數,該函數傳入的是數據的類型指針,它將在堆上申請空間存放對象實例,返回的是新的對象指針:

3b43ee256f58e845146832403c080114.png

經常會遇到的情況是返回一個結構體變量,然后將其賦值給newobject申請的新變量上。

4 切片slice

類似數組,切片的實例對象數據結構如下,可知它占用了三個機器字,與它相關的函數是growslice表示擴容,逆向時可忽略:

type SliceHeader struct {    Data uintptr                        // 數據指針    Len  int                            //  當前長度    Cap  int                            // 可容納的長度}

更常見的函數是與字符串相關的轉換,它們在底層調用的是如下函數,此處我們依然不必關注第一個參數:

func slicebytetostring(buf *[32]byte, ptr *byte, n int) stringfunc stringtoslicebyte(*[32]byte, string) []byte

例如下圖:

04d95b342f34c5b70e92a70ef068b9be.png

可見傳入的是兩個參數代表一個string,返回了三個數據代表一個[]byte。

5 字典map

字典實現比較復雜,不過在逆向中會涉及到的內容很簡單,字典操作常見的會轉換為如下函數,一般fastrand和makemap連用返回一個map,它為一個指針,讀字典時使用mapaccess1和mapaccess2,后者是使用,ok語法時生成的函數,runtime里還有很多以2結尾的函數代表同樣的含義,后文不再贅述。寫字典時會使用mapassign函數,它返回一個地址,將value寫入該地址,另外還比較常見的是對字典進行遍歷,會使用mapiterinit和mapiternext配合:

func fastrand() uint32func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)func mapaccess1(mapType *byte, hmap map[any]any, key *any) (val *any)func mapaccess2(mapType *byte, hmap map[any]any, key *any) (val *any, pres bool)func mapassign(mapType *byte, hmap map[any]any, key *any) (val *any)func mapiterinit(mapType *byte, hmap map[any]any, hiter *any)func mapiternext(hiter *any)

事實上更常見的是上面這些函數的同類函數,它們的后綴代表了對特定類型的優化,例如如下代碼,它首先調用makemap_small創建了一個小字典并將其指針存于棧上,之后調用mapassign_faststr傳入一個字符串鍵并獲取一個槽,之后將數據寫入返回的槽地址里,這里就是一個創建字典并賦值的過程:

c10bdaa0905b52b197ae53e550e08c0e.png

如下是訪問字典里數據的情況,調用mapaccess1_fast32傳入了一個32位的數字作為鍵:

2972183fb951f2117557a53a8a54386f.png

可以看到mapaccess和mapassign的第一個參數代表字典的類型,因此能很容易知道字典操作參數和返回值的類型。

6 結構體struct

類似于C語言,Go的結構體也是由其他類型組成的復合結構,它里面域的順序也是定義的順序,里面的數據對齊規則和C一致不過我們可以直接從其類型信息獲得,不必自己算。在分析結構體變量時必須要了解結構體的類型結構了,其定義如下:

type rtype struct {    size       uintptr  // 該類型對象實例的大小    ptrdata    uintptr  // number of bytes in the type that can contain pointers    hash       uint32   // hash of type; avoids computation in hash tables    tflag      tflag    // extra type information flags    align      uint8    // alignment of variable with this type    fieldAlign uint8    // alignment of struct field with this type    kind       uint8    // enumeration for C    alg        *typeAlg // algorithm table    gcdata     *byte    // garbage collection data    str        nameOff  // 名稱    ptrToThis  typeOff  // 指向該類型的指針,如該類為Person,代碼中使用到*Person時,后者也是一種新的類型,它是指針但是所指對象屬于Person類,后者的類型位置存于此處}type structField struct {    name        name    // 屬性名稱    typ         *rtype  // 該域的類型    offsetEmbed uintptr // 該屬性在對象中的偏移左移一位后與是否是嵌入類型的或,即offsetEmbed>>1得到該屬性在對象中的偏移}type structType struct {    rtype    pkgPath name            // 包名    fields  []structField   // 域數組}type uncommonType struct {    pkgPath nameOff // 包路徑    mcount  uint16  // 方法數    xcount  uint16  // 導出的方法數    moff    uint32  // 方法數組的偏移,方法表也是有需的,先導出方法后私有方法,而其內部按名稱字符串排序    _       uint32  // unused}type structTypeUncommon struct {    structType    u uncommonType}

如下為macaron的Context結構體的類型信息,可見它的實例對象占了0x90字節,這實際上會和下面fields中對象所占空間對應:

c0590477f79038bc6aff8dc160c875e7.png

通過macaron_Context_struct_fields可轉到每個域的定義,可見其域名稱域類型,偏移等:

0b616d730363bc5327c3cb21beb24bb2.png

結構體類型作為自定義類型除了域之外,方法也很重要,這部分在后文會提到。

7 接口interface

接口和反射息息相關,接口對象會包含實例對象類型信息與數據信息。這里需要分清幾個概念,一般我們是定義一種接口類型,再定義一種數據類型,并且在這種數據類型上實現一些方法,Go使用了類似鴨子類型,只要定義的數據類型實現了某個接口定義的全部方法則認為實現了該接口。前面提到的兩個是類型,在程序運行過程中對應的是類型的實例對象,一般是將實例對象賦值給某接口,這可以發生在兩個階段,此處主要關注運行時階段,這里在匯編上會看到如下函數:

// Type to empty-interface conversion.func convT2E(typ *byte, elem *any) (ret any)// Type to non-empty-interface conversion.func convT2I(tab *byte, elem *any) (ret any)

如上轉換后的結果就是接口類型的實例對象,此處先看第二個函數,它生成的對象數據結構如下,其中itab結構體包含接口類型,轉換為接口前的實例對象的類型,以及接口的函數表等,而word是指向原對象數據的指針,逆向時主要關注word字段和itab的fun字段,fun字段是函數指針數組,它里元素的順序并非接口內定義的順序,而是名稱字符串排序,因此對照源碼分析時需要先排序才能根據偏移確定實際調用的函數:

type nonEmptyInterface struct {    // see ../runtime/iface.c:/Itab    itab *struct {                                  ityp   *rtype                       // 代表的接口的類型,靜態static interface type        typ    *rtype                       // 對象實例真實的類型,運行時確定dynamic concrete type        link   unsafe.Pointer                       bad    int32        unused int32        fun    [100000]unsafe.Pointer       // 方法表,具體大小由接口定義確定    }    word unsafe.Pointer}

這是舊版Go的實現,在較新的版本中此結構定義如下,在新版中它的起始位置偏移是0x18,因此我們可以直接通過調用偏移減0x18除以8獲取調用的是第幾個方法:

type nonEmptyInterface struct {    // see ../runtime/iface.go:/Itab    itab *struct {        ityp *rtype // static interface type        typ  *rtype // dynamic concrete type        hash uint32 // copy of typ.hash        _    [4]byte        fun  [100000]unsafe.Pointer // method table    }    word unsafe.Pointer}

上面講的是第二個函數的作用,解釋第一個函數需要引入一種特殊的接口,即空接口,由于這種接口未定義任何方法,那么可以認為所有對象都實現了該接口,因此它可以作為所有對象的容器,在底層它和其他接口也擁有不同的數據結構,空接口的對象數據結構如下:

// emptyInterface is the header for an interface{} value.type emptyInterface struct {    typ  *rtype                             // 對象實例真實的類型指針    word unsafe.Pointer                     // 對象實例的數據指針}

可見空接口兩個域剛好指明原始對象的類型和數據域,而且所有接口對象是占用兩個個機器字,另外常見的接口函數如下:

// Non-empty-interface to non-empty-interface conversion.func convI2I(typ *byte, elem any) (ret any)// interface type assertions x.(T)func assertE2I(typ *byte, iface any) (ret any)func assertI2I(typ *byte, iface any) (ret any)

例如存在如下匯編代碼:

0579cb2668f316db2efd7ad934cbb0d3.png

可以知道convI2I的結果是第一行所指定接口類型對應的接口對象,在最后一行它調用了itab+30h處的函數,根據計算可知是字母序后的第4個函數,這里可以直接查看接口的類型定義,獲知第四個函數:

b84f8ee45646d2a9480668fb1f2c596e.png

語法特征

1 創建對象

Go不是面向對象的,此處將Go的變量當做對象來描述。函數調用棧作為一種結構簡單的數據結構可以輕易高效的管理局部變量并實現垃圾回收,因此新建對象也優先使用指令在棧上分配空間,當指針需要逃逸或者動態創建時會在堆區創建對象,這里涉及make和new兩個關鍵詞,不過在匯編層面它們分別對應著makechan,makemap,makeslice與newobject,由于本文沒有介紹channel故不提它,剩下的makemap和newobject上文已經提了,還剩makeslice,它的定義如下:

func?makeslice(et *_type, len, cap?int) unsafe.Pointer

如下,調用make([]uint8, 5,10)創建一個slice后,會生成此代碼:

cbe4fe7bba211039cdc68644a5ed84e5.png

2 函數與方法

2.1 棧空間

棧可以分為兩個區域,在棧底部存放局部變量,棧頂部做函數調用相關的參數與返回值傳遞,因此在分析時不能對頂部的var命名,因為它不特指某具體變量而是隨時在變化的,錯誤的命名容易造成混淆,如下圖,0xE60距0xEC0足夠遠,因此此處很大概率是局部變量可重命名,而0xEB8距棧頂很近,很大概率是用于傳參的,不要重命名:

77ff249371f995b5817beb17c22bbfc2.png

2.2 變參

類似Python的一般變參實際被轉換為一個tuple,Go變參也被轉換為了一個slice,因此一個變參在匯編級別占3個參數位,如下代碼:

func VarArgDemo(args ...int) (sum int) {}func main() {    VarArgDemo(1, 2, 3)}

它會被編譯為如下形式:

b61bf7adc62d727547f325eba667f583.png

這里先將1,2,3保存到rsp+80h+var_30開始的位置,然后將其首地址、長度(3)、容量(3)放到棧上,之后調用VarArgDeme函數。

2.3 匿名函數

匿名函數通常會以外部函數名_funcX來命名,除此之外和普通函數沒什么不同,只是需要注意若使用了外部變量,即形成閉包時,這些變量會以引用形式傳入,如在os/exec/exec.go中如下代碼:?

go func() {            select {            case c.ctx.                c.Process.Kill()            case c.waitDone:            }        }()

其中c是外部變量,它在調用時會以參數形式傳入(newproc請見后文協程部分):

c8401803d591957960c9f859fcc658f0.png

而在io/pipe.go中的如下代碼:

func (p *pipe) CloseRead(err error) error {    if err == nil {        err = ErrClosedPipe    }    p.rerr.Store(err)    p.once.Do(func() { close(p.done) })    return nil}

其中p是外部變量,它在調用時是將其存入外部寄存器(rdx)傳入的:

8f555826a3390cce9f7e651572acb954.png

可見在使用到外部變量時它們會作為引用被傳入并使用。

2.4 方法

Go可以為任意自定義類型綁定方法,方法將會被轉換為普通函數,并且將方法的接收者轉化為第一個參數,再看看上文結構體處的圖:

c0590477f79038bc6aff8dc160c875e7.png

如上可見Context含44個導出方法,3個未導出方法,位置已經被計算出在0xcdbaa8,因此可轉到方法定義數組:

7855e655801443b05b45f92ec4894983.png7f816e11825279653d89248ce6e3a6bc.png

如上可見,首先是可導出方法,它們按照名稱升序排序,之后是未導出方法,它們也是按名稱升序排序,另外導出方法有完整的函數簽名,而未導出方法只有函數名稱。在逆向時不必關心這一部分結構,解析工具會自動將對應的函數調用重命名,此處僅了解即可。

在逆向時工具會將其解析為類型名__方法名或類型名_方法名,因此遇到此類名稱時我們需要注意它的第一個參數是隱含參數,類似C++的this指針,但Go的方法定義不僅支持傳引用,也支持傳值,因此第一個參數可能在匯編層面不只占一個機器字,如:

type Person struct {    name   string    age    int    weight uint16    height uint16}func (p Person) Print() {    fmt.Printf("%t\n", p)}func (p *Person) PPrint() {    fmt.Printf("%t\n", p)}func main(){    lihua := Person{        name:   "lihua",        age:    18,        weight: 60,        height: 160,    }    lihua.Print()    lihua.PPrint()}

編譯后如下所示:

bd2fbb7315b386e7f4ad998690a52951.png

根據定義兩個方法都沒有參數,但是從匯編看它們都有參數,如注釋,在逆向時是更常見的是像PPrint這種方法,即第一個參數是對象的指針。

2.5 函數反射

函數在普通使用和反射使用時,被保存的信息不相同,普通使用不需要保存函數簽名,而反射會保存,更利于分析,如下代碼:

//go:noinlinefunc Func1(b string, a int) bool {    return a < len(b)}//go:noinlinefunc Func2(a int, b string) bool {    return a < len(b)}func main(){    fmt.Println(Func1("233", 2))    v := reflect.ValueOf(Func2)    fmt.Println(v.Kind()==reflect.Func)}

編譯后通過字符串搜索,可定位到被反射的函數簽名(當然在逆向中并不知道應該搜什么,而是在函數周圍尋找簽名):

2593dbfd17ce467c720cd4da3c487384.pngab7b6ed8397a369be1ed82c8e34cdeb0.png

而普通函數的簽名無法被搜到:

5ca095d6645232bc3ccf85ffe7b76de4.png

3 伸縮棧

由于go可以擁有大量的協程,若使用固定大小的棧將會造成內存空間浪費,因此它使用伸縮棧,初始時一個普通協程只分配幾KB的棧,并在函數執行前先判斷棧空間是否足夠,若不夠則通過一些方式擴展棧,這在匯編上的表現形式如下:

55ff83e5a5f9871cc50a8b513ba93c07.png

在調用runtime·morestack*函數擴展棧后會重新進入函數并進入左側分支,因此在分析時直接忽略右側分支即可。

4 調用約定

Go統一通過棧傳遞參數和返回值,這些空間由調用者維護,返回值內存會在調用前選擇性的被初始化,而參數傳遞是從左到右順序,在內存中從下到上寫入棧,因此看到mov [rsp + 0xXX + var_XX], reg(棧頂)時就代表開始為函數調用準備參數了,繼續向下就能確定函數的參數個數及內容:

e429d58c44fe7bd2b23f8fdb936b74d3.png

如圖,mov [rsp+108h+v_108], rdx即表示開始向棧上傳第一個參數了,從此處到call指令前都是傳參,此處可見在匯編層面傳了3個參數,其中第2個和第3個參數為Go語言里的第二個參數,call指令之后為返回值,不過可能存在返回值未使用的情況,因此返回值的個數和含義需要從函數內部分析,比如此處的Query我們已知arg_0/arg_8/arg_10為參數,那么剩下的arg18/arg20即為返回值:

a24294d7fc14e3eeca101a48f18d8a20.png

需要注意的是不能僅靠函數頭部就斷定參數個數,例如當參數為一個結構體時,可能頭部的argX只代表了其首位的地址,因此需要具體分析函數retn指令前的指令來確定返回值大小。

5 寫屏障

Go擁有垃圾回收,其三色標記法使用了寫屏障的方法保證一致性,在垃圾收集過程中會將寫屏障標志置位,此時會進入另一條邏輯,但是我們在逆向分析過程中可以認為該位未置位而直接分析無保護的情況:

de8f8392479d48f220a482b9e7932fd7.png

如上圖,先判斷標志,再決定是否進入,在分析時可以直接認為其永假并走左側分支。

6 協程go

使用go關鍵詞可以創建并運行協程,它在匯編上會被表現為由runtime_newproc(fn,args?),它會封裝函數與參數并創建協程執行信息,并在適當時候被執行,如:

e0f41dac2f50445cd58d4e21f2627316.png

這里執行了go loop(),由于沒有參數此處newproc只被傳入了函數指針這一個參數,否則會傳入繼續傳入函數所需的參數,在分析時直接將函數作為在新的線程里執行即可。

7 延遲執行defer

延遲執行一般用于資源釋放,它會先注冊到鏈表中并在當前調用棧返回前執行所有鏈表中注冊的函數,在匯編層面會表現為runtime_deferproc,例如常見的鎖釋放操作:

545bfdc6464e4cfa696d7e94270bdb28.png

這里它第一個參數代表延遲函數參數字節大小為8字節,第二個參數為函數指針,第三個參數為延遲執行函數的參數,若創建失敗會直接返回,返回前會調用runtime_deferreturn去執行其他創建的延遲執行函數,一般我們是不需要關注該語句的,因此可以直接跳過相關指令并向左側繼續分析。

8 調用c庫cgo

Go可以調用C代碼,但調用C會存在運行時不一致,Go統一將C調用看作系統調用來處理調度等問題,另一方類型不一致才是我們需要關注的重點,為了解決類型與命名空間等問題cgo會為C生成樁代碼來橋接Go,于是這類函數在Go語言側表現為XXX_CFunc__YYY,它封裝參數并調用runtime_cgocall轉換狀態,在中間表示為NNN_cgo_abcdef123456_CFunc__ZZZ,這里它解包參數并調用實際c函數,例如:

ff5364ccf46720412fecc7b739cde22e.png

此處它調用了libc的void* realloc(void*, newsize),在Go側它封裝成了os_user__Cfunc_realloc,在該函數內部參數被封裝成了結構體并作為指針與函數指針一起被傳入了cgocall,而函數指針即_cgo_3298b262a8f6_Cfunc_realloc為中間層負責解包參數等并調用真正的C函數:

8cd5d414648bf51b0da078f28ba43cf0.png

9 其他

還有些內容,如看到以panic開頭的分支不分析等不再演示,分析時遇到不認識的三方庫函數和標準庫函數直接看源碼即可。

參考鏈接

https://draveness.me/golang/

https://tiancaiamao.gitbooks.io/go-internals/content/zh/02.3.html

https://www.pnfsoftware.com/blog/analyzing-golang-executables/

https://rednaga.io/2016/09/21/reversing_go_binaries_like_a_pro/

https://research.swtch.com/interfaces

推薦閱讀:

  1. ?【高級持續性威脅追蹤】Purple Fox 新變種講述從釣魚網站到Rootkit的故事

  2. 【漏洞通告】FasterXML Jackson-databind多個反序列化漏洞

  3. 【漏洞通告】Apache Flink文件寫入與任意文件讀取漏洞(CVE-2020-17518/CVE-2020-17519)

  4. 【漏洞通告】Zyxel多個設備密碼硬編碼漏洞(CVE-2020-29583)

  5. 【高級持續性威脅追蹤】來自Mustang Panda的攻擊? ?我兔又背鍋了!

深信服千里目安全實驗室e8301f2969589ff6a6e2c25d105321f1.png

深信服科技旗下安全實驗室,致力于網絡安全攻防技術的研究和積累,深度洞察未知網絡安全威脅,解讀前沿安全技術。

●?掃碼關注我們

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/538583.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/538583.shtml
英文地址,請注明出處:http://en.pswp.cn/news/538583.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Confluence 6 選擇一個外部數據庫

2019獨角獸企業重金招聘Python工程師標準>>> 注意&#xff1a; 選擇一個合適的數據庫通常需要花費很多時間。同時 Confluence 自帶的 XML 數據備份和恢復功能通常也不適合合并和備份有大量數據的數據庫。如果你想在系統運行后進行數據合并&#xff0c;你通常需要使用…

spark中saveAsTextFile如何最終生成一個文件

原文地址&#xff1a;http://www.cnblogs.com/029zz010buct/p/4685173.html ----------------------------------------------------------------------- 一般而言&#xff0c;saveAsTextFile會按照執行task的多少生成多少個文件&#xff0c;比如part-00000一直到part-0000n&…

python爬取內容亂碼_python爬取html中文亂碼

環境&#xff1a; python3.6 爬取代碼&#xff1a; import requests url https://www.dygod.net/html/tv/hytv/ req requests.get(url) print(req.text) 爬取結果&#xff1a; / _-如上&#xff0c;title內容出現亂碼&#xff0c;自己感覺應該是編碼的問題&#xff0c;但是不…

前端每日實戰:34# 視頻演示如何用純 CSS 創作在文本前后穿梭的邊框

效果預覽 按下右側的“點擊預覽”按鈕可以在當前頁面預覽&#xff0c;點擊鏈接可以全屏預覽。 https://codepen.io/comehope/pen/qYepNv 可交互視頻教程 此視頻是可以交互的&#xff0c;你可以隨時暫停視頻&#xff0c;編輯視頻中的代碼。 請用 chrome, safari, edge 打開觀看。…

not support mysql_MYSQL出現quot; Client does not support authentication quot;的解決方法

MYSQL 幫助&#xff1a;A.2.3 Client does not support authentication protocolMySQL 4.1 and up uses an authentication protocol based on a password hashing algorithm that is incompatible with that used by older clients. If you upgrade the server to 4.1, attemp…

spark shell中編寫WordCount程序

啟動hdfs 略http://blog.csdn.net/zengmingen/article/details/53006541 啟動spark 略安裝&#xff1a;http://blog.csdn.net/zengmingen/article/details/72123717 spark-shell&#xff1a;http://blog.csdn.net/zengmingen/article/details/72162821準備數據 vi wordcount.t…

初級英語02

做客 1 Diana,i havent seen you for ages,how have you been? 2 would you like something to drink? 3 give my best to your parents. 4 did you hear what happened?whats the matter with him? 5 id like to applogize for leaving so early,i brought a little gift,…

mysql計算機二級選擇題題庫_全國計算機二級mysql數據庫選擇題及答案

全國計算機二級mysql數據庫選擇題及答案選擇題是全國計算機二級mysql考試里的送分題&#xff0c;下面小編為大家帶來了全國計算機二級mysql數據庫選擇題及答案&#xff0c;歡迎大家閱讀&#xff01;全國計算機二級mysql數據庫選擇題及答案1) 函數 max( ) 表明這是一個什么函數?…

git add 撤銷_更科學地管理你的項目,Git 簡明教程(二)

修改文件內容上回說到&#xff0c;我們已經成功創建并提交了一個 README.md 文件到 FirstGit 版本庫中1、修改文件現在我們更改 README.md 內容2、查看版本庫狀態該文件夾內右鍵運行 Git Bash Here執行命令 git statusGit 提示我們的改動還沒有 commit&#xff0c;并且它給出了…

Eclipse中Copy Qualified Name復制類全名解決辦法

原文鏈接&#xff1a;http://www.cnblogs.com/zyh1994/p/6393550.html ----------------------------------------------------------------------------------------------- Eclipse中 用Copy Qualified Name復制類全名時 總是這樣的/struts1/src/me/edu/HelloAction.java很不…

c 連接mysql錯誤信息_使用C語言訪問MySQL數據 —— 連接和錯誤處理

2011-05-09 wcdj可以通過許多不同的編程語言來訪問MySQL&#xff0c;例如&#xff0c;C&#xff0c;C&#xff0c;Java&#xff0c;Perl&#xff0c;Python&#xff0c;Tcl&#xff0c;PHP等。本文主要總結使用C語言接口如何訪問MySQL數據。(一) 連接例程(二) 錯誤處理(一) 連接…

eclipse編寫wordcount提交spark運行

采用集成了scala的eclipse編寫代碼 代碼&#xff1a; package wordcountimport org.apache.spark.SparkConf import org.apache.spark.SparkContextobject WordCount {def main(args: Array[String]): Unit {//非常重要&#xff0c;是通向Spark集群的入口val confnew SparkCon…

gitlab 刪除分支_如何刪除gitlab上默認受保護的master主分支

今天開發在檢查代碼的時候&#xff0c;發現master分支有問題&#xff0c;現在準備刪除此主分支&#xff0c;并且重新提交正確的代碼&#xff0c;不過在刪除時發現&#xff0c;master分支不能被刪除。ps&#xff1a;主分支一般都是線上分支&#xff0c;需要開發確認后并且做好備…

rsync服務擴展應用

rsync服務擴展應用① 守護進程多模塊功能配置第一步&#xff1a;修改配置文件 注&#xff1a;可以再vim中輸入&#xff1a;20,22copy22&#xff0c;表示復制20到22行到22行之后 vim /etc/rsyncd.conf[backup01]comment "backup dir by oldboy"path /backup[backup0…

NodeJs 安裝

進入官網下載&#xff0c;zip 安裝包 https://nodejs.org/en/download/ 解壓 配置環境變量到安裝目錄 cmd 測試 node -v npm -v

SSH秘鑰登錄服務器

一、查看本機 ssh 公鑰&#xff0c;生成公鑰 1.通過命令窗口 a. 打開你的 git bash 窗口 b. 進入 .ssh 目錄&#xff1a;cd ~/.ssh c. 找到 id_rsa.pub 文件&#xff1a;ls d. 查看公鑰&#xff1a;cat id_rsa.pub 或者 vim id_rsa.pub git–查看本機 ssh 公鑰&#xff0c…

mysql存入mtr數據_mysql mtr寫入數據

selenium 打開瀏覽器import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebE ...Win8&period;1安裝Visual Studio 2015提示需要KB2919355http://www.microsoft.com/zh-cn/download/details.aspx?id42335 安裝說明: 1.若要…

diff git 代碼實現_Git 自救指南:這些坑你都跳得出嗎?

每天都會寫架構師文章&#xff0c;Java技術文章天天更新&#xff0c;感興趣的點個關注再走唄&#xff01;Git 雖然因其分布式管理方式&#xff0c;不完全依賴網絡&#xff0c;良好的分支策略&#xff0c;容易部署等優點&#xff0c;已經成為最受歡迎的源代碼管理方式。但是一分…

HDU 4812 D Tree

HDU 4812 思路&#xff1a; 點分治 先預處理好1e6 3以內到逆元 然后用map 映射以分治點為起點的鏈的值a 成他的下標 u 然后暴力跑出以分治點兒子為起點的鏈的值b&#xff0c;然后在map里查找inv[b]*k 代碼&#xff1a; #include<bits/stdc.h> using namespace std; #d…

Angular CLI 安裝

安裝Angular 官網的教程&#xff0c;因為國內網絡環境原因&#xff0c;訪問不了服務器&#xff0c;導致安裝失敗。 1、先安裝NodeJs 安裝教程&#xff1a;http://blog.csdn.net/zengmingen/article/details/72650484 2、通過NodeJs中的模塊npm 命令行安裝 CLI 2.1、設置npm的…