Within the guts of .net, the definition for a struct containing certain members is the same as a definition for a class which those same fields and members, and which inherits from System.ValueType. Note that compilers will not allow one to declare a class which inherits ValueType, but when one declares a struct, the compiler, "behind the scenes" declares a class which does.
What makes value types special in .net is the way the run-time allocates storage locations (variables, fields, parameters, etc.) When a storage location of a type not inheriting from ValueType is declared, the runtime will allocate space for a heap object reference. By contrast, when a storage location of a type inheriting from ValueType is declared, the runtime will allocate space for all the public and private fields of that type. For a type like int, the system allocates a private field which is of a special primitive type, outside the normal type system.
Note that a storage location of a value type doesn't really hold an instance of that type; instead is an instance of that type, and holds all of the fields of that type. A statement like struct1 = struct2 does not replace the value-type instance struct1 with the instance struct2. Instead, it copies all of the fields from struct2 over the corresponding fields in struct1. Likewise if a value-type storage location is passed as a method to a procedure without using the ref keyword, what is passed is not the struct instance itself, but rather the contents of its fields.
If it is necessary to copy a value-type storage location to a one of type not derived from ValueType (e.g. Object or IComparable), the system will create a new heap-object instance of the value type, copy all the fields from the value type to that new instancen and store a reference to that new instance in the target storage location. This process is called "boxing". Most compilers will do this implicitly, thus attempting to behave as though a value type storage location holds an object which derives from ValueType. It's important to note, though, that this is an illusion. If type X derives from Y, one has an X named xx and a Y named yy, and one performs xx = yy, such a statement should cause xx and yy to refer to the same object instance. That will happen if xx and yy are types not derived from ValueType, even if yy holds an instance of something derived from ValueType. It will not happen, however, if xx and/or yy derives from ValueType. In that case, the system will copy fields from one instance to another (possibly new) instance.