Bypassing Linux Pkeys Security Mechanism in a PWN Challenge
An in‑depth analysis of Linux Memory Protection Keys (pkeys), their x86_64 and arm64 implementations, related syscalls, and a step‑by‑step exploit that randomises PKRU permissions and then uses WRPKRU to bypass the restrictions and capture the flag in a CTF PWN challenge.
Simple Introduction
Memory Protection Keys for Userspace (PKU, also called PKEYs) provide a way to enforce page‑based protection by changing a thread‑local register instead of modifying page tables, avoiding TLB flushes.
x86_64 Implementation
In x86_64 each page‑table entry reserves four bits for a protection key, giving 16 possible keys. The per‑CPU PKRU register holds two bits per key (Access‑Disable and Write‑Disable) that define a control set.
0000 → Pkey 0
0001 → Pkey 1
…
1111 → Pkey 15Example: a thread with three regions A, B, C assigned keys 0, 1, 2 gets read‑only, read‑write, and no‑access permissions respectively.
If the thread accesses region A it can only read.
If it accesses region B it can read and write.
If it accesses region C the access is blocked.
Two privileged instructions manage PKRU: RDPKRU reads the current PKRU value. WRPKRU writes a new PKRU value (ECX and EDX must be zero).
System Calls
Linux exposes three syscalls that interact with pkeys:
pkey_alloc(unsigned long flags, unsigned long init_access_rights)– allocate a new key. pkey_free(int pkey) – free a key. pkey_mprotect(void *addr, size_t len, int prot, int pkey) – change page permissions and associate a key.
Numbers: 0x149 = __NR_pkey_alloc, 0x14a = __NR_pkey_mprotect.
CTF PWN Challenge “Midas’ Touch”
The provided binary uses a seccomp filter that only allows a few syscalls (open, openat, mmap, mprotect, …). In the_curse_of_midas() the program repeatedly calls pkey_mprotect with a random key and a NULL address, which does not change page permissions but updates the corresponding bits in PKRU. This randomises the permission bits of all future keys, making the later _mprotect(secret,0x1000,7) call likely to deny reads of the flag.
By using WRPKRU (found in the supplied libc at offset 0x126256) the exploit clears the PKRU register (writes 0) so that all keys have read‑write access. The exploit then leaks the PIE base and libc base with a format‑string payload %11$p%29$p, computes the address of WRPKRU, builds a ROP chain that calls puts to output the flag, and finally restores control flow.
from pwn import *
context.arch='amd64'
# p = remote('challenge.ilovectf.cn',30377)
p = process('./midas')
elf = ELF('./midas')
libc = ELF('./libc.so.6')
fmt = b'%11$p%29$p'
p.recvuntil('
')
p.send(fmt)
# parse leaks, compute bases, locate WRPKRU, build ROP, send payloadRunning the payload shows the PKRU register change from 0x55555554 to 0x0 and prints the flag.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Black & White Path
We are the beacon of the cyber world, a stepping stone on the road to security.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
