go逆向首先了解他的函数调用规则。
环境搭建在go 环境搭建
代码这样
package main
func val(c, d int) (a int, b int) {
	e := 5
	f := 6
	a = c + d + e + f
	b = d * 2
	return
}
func test() {
	i, j := val(1, 2)
	i = i + 3
	j = j + 4
}
func main() {
	test()
}
 
然后编译成二进制文件
go build -gcflags "-N -l" 1.go
 
gcflags可以向go编译器传递参数。
-N参数代表禁止优化, -l参数代表禁止内联,
 go在编译目标程序的时候会嵌入运行时(runtime)的二进制,
 禁止优化和内联可以让运行时(runtime)中的函数变得更容易调试.
然后就好多了。
先看main函数的汇编
sub     rsp, 8
mov     [rsp+8+var_8], rbp
lea     rbp, [rsp+8+var_8]
call    main_test
mov     rbp, [rsp+8+var_8]
add     rsp, 8
nop
retn
 
显然我们看到因为main没有参数
 就是把栈抬高八个字节
 然后rbp放进去
 然后放的这个地方是rbp新地址。
函数返回的时候正常返回就可以了。
单从这里来看
 效果跟C语言函数调用的效果是一样的。
 都是压一个rbp进去
 只不过是具体的汇编不一样。
再来看test函数
sub     rsp, 38h                      #两个参数此时rsp指向main函数中call的时候压进去的返回地址
mov     [rsp+38h+var_8], rbp          #0x38的栈最下面八个字节是rbp
lea     rbp, [rsp+38h+var_8]
mov     eax, 1                        #两个参数乱入
mov     ebx, 2                    
xchg    ax, ax                        #xchg是交换,ax交换ax,啥也不干,cherest师傅说是废话文学
call    main_val                      #又把返回地址压进去之后先不看下面的先去看看val函数
                                      #经过下面的函数调用回来之后我们现在知道
                                      #开了0x38的栈  最下面8个字节是栈底
                                      #然后最上面两个是函数调用时候的传参
                                      #rax  rbx里面放着返回值
mov     [rsp+38h+var_10], rax         #把返回值放进临时变量i  j 
mov     [rsp+38h+var_18], rbx
mov     rcx, [rsp+38h+var_10]         #剩下的四个qword就是两个i  两个j
mov     [rsp+38h+var_20], rcx         #两个的目的是进行了i = i + 3这样的操作
mov     rcx, [rsp+38h+var_18]         #可能因为关了优化  导致有点空间上的浪费
mov     [rsp+38h+var_28], rcx         #但是并不影响我们研究函数的调用规则
mov     rcx, [rsp+38h+var_20]
add     rcx, 3
mov     [rsp+38h+var_20], rcx
mov     rcx, [rsp+38h+var_28]
add     rcx, 4
mov     [rsp+38h+var_28], rcx
mov     rbp, [rsp+38h+var_8]
add     rsp, 38h
retn                                 #最后返回
 
val
var_28= qword ptr -28h
var_20= qword ptr -20h
var_18= qword ptr -18h
var_10= qword ptr -10h
var_8= qword ptr -8
arg_0= qword ptr  8
arg_8= qword ptr  10h
sub     rsp, 28h                    #这个函数栈空间开了0x28
mov     [rsp+28h+var_8], rbp        #然后又是rbp放进去这个操作
lea     rbp, [rsp+28h+var_8]
mov     [rsp+28h+arg_0], rax        #rax是第一个参数 放在了返回地址下面第一个qword
mov     [rsp+28h+arg_8], rbx        #rbx是第二个参数 放在返回地址下面第二个qword
mov     [rsp+28h+var_10], 0         #这连着的四个显然就是两个返回值跟两个临时变量
mov     [rsp+28h+var_18], 0
mov     [rsp+28h+var_20], 5
mov     [rsp+28h+var_28], 6
mov     rcx, [rsp+28h+arg_0]        #这里就是做那个加法,都加在rcx里面
add     rcx, [rsp+28h+arg_8]        #参数就是用之前rax  rbx加进去的。
add     rcx, [rsp+28h+var_20]
add     rcx, 6
mov     [rsp+28h+var_10], rcx       #最后的结果放进返回值那里
mov     rbx, [rsp+28h+arg_8]
shl     rbx, 1                      
mov     [rsp+28h+var_18], rbx       #对临时变量b进行处理
mov     rax, [rsp+28h+var_10]       #然后rax  rbx又分别是两个返回值   rax是第一个返回值  rbx是第二个返回值
mov     rbp, [rsp+28h+var_8]
add     rsp, 28h
retn
 
我们总结一下。
 其实单论栈布局来讲这与C语言的栈布局并没有什么区别。
 栈指针在栈底
 后面跟着返回地址
 再后面跟着参数
 不一样的只不过是整体实现过程中的具体汇编代码,或者说整个的过程有差别。
函数调用方面传参用的是rax rbx 返回值也是rax rbx。
参数方面是stdcall的调用规则。从右往左压参。
 说白了就是咱平常说的返回地址下面是第一个参数,然后是第二个第三个。
然后临时变量的分布也有规律这里的话是先压返回值,然后压临时变量
 但是我们不关心,平常可能也会被优化掉。
那么问题来了
 我们的传参参数多了剩下的寄存器应该是啥?
 如果我们一个函数中调用了好几个函数,栈又是怎样的分布?
 带着问题我们接着实验。









