In the first approach, why do we cast shellcode to (int) when overwriting the return address?
I am not in the mind of the person who wrote that code, but standard C does require the cast. The identifier shellcode designates an object of array type. In that context, its value is automatically converted to a pointer to the first array element. C allows pointer types to be converted to integer types, but it does not specify any implicit conversions of that kind, so explicitly converting the pointer to type int is necessary for C conformance. C conformance seems an odd concern for code that intentionally contains (other) undefined behavior, so maybe the main point is to avoid the compiler rejecting or warning about the assignment.
In the second approach, why do we need the complex cast (int (*)()) instead of just assigning the pointer directly?
Similar reasons, most likely. C defines implicit conversions between some object pointer types, but most pointer conversions require explicit casts. The cast you ask about is to the type of the variable being assigned. It is complex(-ish) because the type in question is a function pointer type, and their type names are more complex than those of typical pointer-to-object types.
What's the fundamental difference between these two methods in terms of how the shellcode gets executed?
The first alternative intends to plug the address of the shellcode into the location where (the approach supposes) the containing function's epilog will look for a function return address. The idea is that when the function terminates normally, it will "return" to the shellcode instead of to its actual caller.
The second alternative intends to call the shellcode directly, as if it were a function.
I'm specifically confused about the type casting rationale.
In both cases, the casting seems to be only about matching the type of the value being assigned (the shellcode address) to the declared data type of the storage to which it being is assigned.
In the first case, whether that data type is fit for purpose and whether the cast does the wanted thing are functions of the particular platform and C implementation involved. The code has undefined behavior as far as C is concerned, as a result of an intentional bounds overrun. The language spec provides no reason to expect it to work for getting the shellcode executed. Among many other considerations, it probably wouldn't work in practice on platforms where function pointers are a different size from int (most x86_64 platforms, for instance), or on platforms where the implementation of pointer-to-integer casts is more complex than a mere reinterpretation of the bits, or on platforms where data addresses and code addresses have different size or representation.
In the second case, the data type is definitely fit for purpose, but whether the cast does the wanted thing or is even accepted at all is still a function of the particular platform and C implementation involved. This code, too, has UB as far as C is concerned, because C does not define behavior for converting object pointers to function pointers. The language spec provides no reason to expect it to work for getting the shellcode executed. Among many other considerations, it might not work on platforms where data addresses have different size or representation than code addresses, and of course, it will not work if the compiler rejects the cast, which is well within its rights to do.
shellcodewhile the first places the embedded function's address where the return address frommainpresumably lives, making the shell code execute whenmainis supposed to return (if I read it correctly).