If you want an in depth explanation of how matrix palette effects work, click here: http://msdn.microsoft.com/msdnmag/issues/01/06/Matrix/. This article is helpful, but is starting to become outdated and does assume you know how non-skinned animation works.

Note that you can modify the effect however you please, and add your own normal maps, lighting algorithms, etc.
    // These are required by the Animation library
    float4x4 MatrixPalette[56];
    float4x4 World;

    // These are not
    float4x4 View;
    float4x4 Projection;
    texture BasicTexture;

    // Stores sampler state info for the texture
    sampler TextureSampler = sampler_state
    {
       Texture = (BasicTexture);
    };

Creating the Vertex Shader

    // This is passed into our vertex shader from Xna
    struct VS_INPUT
    {
        // This is the position of the vertex in the model file
	float4 position : POSITION;
        // The vertex normal
	float3 normal : NORMAL0;
	// This is the texture coordinate for the vertex in the model file
	float2 texcoord : TEXCOORD0;
	// These are the indices (4 of them) that index the bones that affect
	// this vertex.  The indices refer to the MatrixPalette.
	half4 indices : BLENDINDICES0;
	// These are the weights (4 of them) that determine how much each bone
	// affects this vertex.
	float4 weights : BLENDWEIGHT0;
    };
    // This is passed out from our vertex shader once we have processed the input
    struct VS_OUTPUT
    {
        // The final position of the vertex in world space
	float4 position : POSITION;
	// The texture coordinate associated with the vertex
	float2 texcoord : TEXCOORD0;
    };
    // This is the output from our skinning method
    struct SKIN_OUTPUT
    {
        float4 position;
        float4 normal;
    };
    // This method takes in a vertex and applies the bone transforms to it.
    SKIN_OUTPUT Skin4( const VS_INPUT input)
    {
        SKIN_OUTPUT output = (SKIN_OUTPUT)0;
        // Since the weights need to add up to one, store 1.0 - (sum of the weights)
        float lastWeight = 1.0;
        float weight = 0;
        // Apply the transforms for the first 3 weights
        for (int i = 0; i < 3; ++i)
        {
            weight = input.weights[i];
            lastWeight -= weight;
            output.position     += mul( input.position, MatrixPalette[input.indices[i]]) * weight;
            output.normal       += mul( input.normal, MatrixPalette[input.indices[i]]) * weight;
        }
        // Apply the transform for the last weight
        output.position     += mul( input.position, MatrixPalette[input.indices[3]])*lastWeight;
        output.normal       += mul( input.normal, MatrixPalette[input.indices[3]])*lastWeight;
        return output;
    };
    void TransformVertex (in VS_INPUT input, out VS_OUTPUT output)
    {
        // Calculate the skinned position
        SKIN_OUTPUT skin = Skin4(input);
        // This is the final position of the vertex, and where it will be drawn on the screen
        float4x4 WorldViewProjection = mul(World,mul(View,Projection));
        output.position = mul(skin.position, WorldViewProjection);
        // This is not used by is included to demonstrate how to get the normal in world space
        float4 transformedNormal = mul(skin.normal, WorldViewProjection);
        output.texcoord = input.texcoord;
    }

Creating the Pixel Shader

    // This is passed into our pixel shader
    struct PS_INPUT
    {
        float2 texcoord : TEXCOORD0;
    };
    // This is the final color rendered on the screen
    struct PS_OUTPUT
    {
        float4 color : COLOR;
    };
    void TransformPixel (in PS_INPUT input, out PS_OUTPUT output)
    {
        output.color = tex2D(TextureSampler,input.texcoord);
    }
    technique TransformTechnique
    {
        pass P0
        {
            VertexShader = compile vs_2_0 TransformVertex();
            PixelShader  = compile ps_2_0 TransformPixel();
        }
    }