I am not very familiar with the built-in CharacterController, but I can tell you how I have implemented this for my own character controller (although, I should warn you it is almost definitely more convoluted than necessary).

I made this class

    using UnityEngine;
    public class CopyMovement : MonoBehaviour
    {
       [SerializeField] public GameObject target;
        Transform target_transform;
        Transform common_ancestor;
    
        Quaternion previousAncestorRotation;
        Vector3 previousAncestorPosition;
    
        Matrix4x4 previousTargetWorldToLocal;
        Quaternion previousTargetRotation;
    
        void Start(){
            if(target!=null)
                Initialize();
        }
    
        public void Initialize(){
            target_transform = target.transform;
            common_ancestor = Utils.FindCommonAncestor(transform, target_transform);
            if(common_ancestor!=null){
                previousAncestorPosition= common_ancestor.position;
                previousAncestorRotation = common_ancestor.rotation;
            }
    
            previousTargetWorldToLocal = target_transform.worldToLocalMatrix;
            previousTargetRotation = target_transform.rotation;
        }
    
        void LateUpdate()
        {
            //if the objects do not share ancestors, it's fairly straightforward: calculate the position and rotation of the constrained object in the local space of the previous parent pose, and 
            //move it to the same pose in the _current_ local space
    
            //if they are part of the same hierarchy, however, the transformations of the common ancestors get applied twice to the constrained object (once naturally, once because of this script)
            //so, we just apply the inverse of the movement of the common ancestors to correct it. Simple, right?
            if(target!=null){
    
                Vector3 pointInPreviousLocalSpace = previousTargetWorldToLocal.MultiplyPoint(transform.position);
                Quaternion rotationInPreviousLocalSpace =  Quaternion.Inverse(previousTargetRotation) * transform.rotation;
    
                //set up the whole common-ancestor thing
                Quaternion offsetAncestorRotation = Quaternion.identity;
                Matrix4x4 ancestorMatrix = Matrix4x4.identity;
                if(common_ancestor!=null){
                    offsetAncestorRotation = Quaternion.Inverse(previousAncestorRotation) * common_ancestor.rotation;
                    ancestorMatrix = Matrix4x4.TRS(
                        common_ancestor.worldToLocalMatrix.MultiplyVector(previousAncestorPosition-common_ancestor.position), 
                        Quaternion.Inverse(offsetAncestorRotation),
                        Vector3.one
                    );
                }
    
                //the thing happens!
                Matrix4x4 theMatrix;
                if(common_ancestor!=null)
                    theMatrix = common_ancestor.localToWorldMatrix* ancestorMatrix * common_ancestor.worldToLocalMatrix * target_transform.localToWorldMatrix;
                else
                    theMatrix = target_transform.localToWorldMatrix;
                    
                transform.SetPositionAndRotation( theMatrix.MultiplyPoint(pointInPreviousLocalSpace),  
                    Quaternion.Inverse(offsetAncestorRotation) * target_transform.rotation * rotationInPreviousLocalSpace);
                
    
                //store the current local space information for next time
                previousTargetWorldToLocal = target_transform.worldToLocalMatrix;
                previousTargetRotation = target_transform.rotation;
    
                if(common_ancestor!=null){
                    previousAncestorRotation = common_ancestor.rotation;
                    previousAncestorPosition = common_ancestor.position;
                }
    
            }
        }
    }

And added a component of this type to the character. Then, inside of the character controller, when checking the ground that it is standing on, I change the settings of the CopyMovement. The abridged version of that code is something like this:

    CopyMovement groundParentConstraint;

    bool checkGround(){
        if(castRayDownwards()){
            if(groundParentConstraint.target != hit.collider.gameObject){
                groundParentConstraint.target = hit.collider.gameObject;
                groundParentConstraint.Initialize();
            }
            return true;
        }
        else{
            groundParentConstraint.target = null;
            return false;
        }
    }


On top of this being a bit convoluted, it also does not work when the "parent" (the platform, in this case) has root motion animation, but do not ask me why, because I have no clue. But still, it's better than nothing.