###Solution using `Block`

The easiest option is probably to use `Block`, and wrap your result in `Hold` or `HoldForm`. So,

    a = 1; b = 2; c = 3;

    result = 
      Block[{a, b, c},
        Hold[Evaluate[Expand[(a + b + c)^3]]]
      ]

    (*
      Hold[a^3+3 a^2 b+3 a b^2+b^3+3 a^2 c+6 a b c+3 b^2 c+3 a c^2+3 b c^2+c^3]
    *)

The values will be automatically substituted once you call `ReleaseHold`:

    ReleaseHold[result]

    (* 216 *)
 
###Use metaprogramming to automate things

Here I will show how you can automate this step with some meta-programming. The following function returns all symbols used in building a given expression, and belonging to some pre-defined set of contexts (which I default to ``{"Global`"}``:

    ClearAll[getHeldSymbols];
    SetAttributes[getHeldSymbols, HoldFirst];
    getHeldSymbols[expr_, contexts_: {"Global`"}] :=
      Thread[
        Cases[
          Unevaluated[expr],
          s_Symbol /; MemberQ[contexts, Context[s]] :> HoldComplete[s],
          {0, Infinity},
          Heads -> True
        ],
        HoldComplete]

For example:

    getHeldSymbols[Expand[(a + b + c)^3]]

    (* HoldComplete[{a, b, c}]  *)

Now, the following short code defines a dynamic environment where the values of defined symbols participating in building your expression have been "frozen":

    ClearAll[evalWithFrozenSyms]
    SetAttributes[evalWithFrozenSyms, HoldFirst];
    evalWithFrozenSyms[expr_, wrapper_: Hold] :=
       getHeldSymbols[expr] /. HoldComplete[{syms___}] :>
           Block[{syms}, wrapper[##] &@expr]

Now we can use it:

    evalWithFrozenSyms[Expand[(a+b+c)^3]]

    (* Hold[a^3+3 a^2 b+3 a b^2+b^3+3 a^2 c+6 a b c+3 b^2 c+3 a c^2+3 b c^2+c^3]  *)