TL;DR: Heap blocks marked as "internal" have a special flag in _HEAP_ENTRY.Flags
[edit] revised my previous answer with a proper answer.
Here's my guess attempt to your question.
According to the windbg help, the "!heap" command code is located in exts.dll (i.e. winxpexts.dll).
Put this DLL on IDA and downloaded symbols for it. There’s only one occurrence of "Internal" in the DLL, inside the DumpHeapEntry() function :
.text:0192463D movzx eax, byte_1963152
.text:01924644 test eax, eax
.text:01924646 jz short loc_1924656
.text:01924648 push offset aInternal ; " Internal "
.text:0192464D call _ExtensionApis.lpOutputRoutine ; some sort of printf routine
The output of "Internal" is therefore conditioned by the value of byte_1963152 : if byte_1963152 is not 0, then "Internal" is printed.
Only once occurrence of write value with anything else than 0 happens (in ReadHeapEntry() which is called at the start of DumpHeapEntry() ):
.text:0191F025 movzx eax, [ebp+var_B]
.text:0191F029 and eax, 8
.text:0191F02C jz short loc_191F035
.text:0191F02E mov byte_1963152, 1
This translates to:
if((UINT)var_B & 8)
byte_1963152 = 1;
var_B is set here :
text:0191EFF7 mov eax, [ebp+var_18]
.text:0191EFFA mov edx, [ebp+var_14]
.text:0191EFFD mov cl, 10h ; shift right by 0x10 bits
.text:0191EFFF call __aullshr
.text:0191F004 mov [ebp+var_B], al
__aullshr stands for "Arithmetic Unsigned Long Long Shift Right". In the above code eax is the low 32-bit part of a 64-bit unsigned long long, while edx is the high 32-bit part.
Notice that var_B is a 8-bit quantity ('al' register is used).
Hence:
// where var_14_18 is a combination (64-bit) of var_14 and var_18
var_B = (char)(var_14_18 >> 0x10 );
var_14 and var_18 are set here :
.text:0191EF01 push 0
.text:0191EF03 push offset aAgregatecode ; "AgregateCode"
.text:0191EF08 push 0
.text:0191EF0A push 0
.text:0191EF0C call _GetShortField@16 ; GetShortField(x,x,x,x)
.text:0191EF11 mov [ebp+var_18], eax ; high part
.text:0191EF14 mov [ebp+var_14], edx ; low part
; cut
.text:0191EF28 mov ecx, [ebp+var_18]
.text:0191EF2B and ecx, _EncodeFlagMask ; from HEAP.EncodeFlagMask
.text:0191EF31 jz short loc_191EF75
.text:0191EF33 mov edx, [ebp+var_18]
.text:0191EF36 xor edx, _CrtHeapCode ; from HEAP.Encoding.Code1
.text:0191EF3C mov eax, [ebp+var_14]
.text:0191EF3F xor eax, dword_1963194 ; from HEAP.Encoding.Code2
.text:0191EF45 mov [ebp+var_18], edx
.text:0191EF48 mov [ebp+var_14], eax
So, windbg use the GetShortField() function on "AgregateCode" and sets both of the aforementioned variable (which is also a single unsigned long long value).
Note that it also uses the HEAP.Encoding.Code1 and HEAP.Encoding.Code2 to XOR both of the value (HEAP is the the current heap from which the heap entry is a part).
"AgregateCode" is a field of both HEAP_ENTRY and HEAP_FREE_ENTRY structures (from Win 8.1 x86):
0:000> dt _heap_entry -r2
ntdll!_HEAP_ENTRY
+0x000 Size : Uint2B
+0x002 Flags : UChar
+0x003 SmallTagIndex : UChar
+0x000 SubSegmentCode : Uint4B
+0x004 PreviousSize : Uint2B
+0x006 SegmentOffset : UChar
+0x006 LFHFlags : UChar
+0x007 UnusedBytes : UChar
+0x000 FunctionIndex : Uint2B
+0x002 ContextValue : Uint2B
+0x000 InterceptorValue : Uint4B
+0x004 UnusedBytesLength : Uint2B
+0x006 EntryOffset : UChar
+0x007 ExtendedBlockSignature : UChar
+0x000 Code1 : Uint4B
+0x004 Code2 : Uint2B
+0x006 Code3 : UChar
+0x007 Code4 : UChar
+0x004 Code234 : Uint4B
+0x000 AgregateCode : Uint8B
This translated to C, gives:
typedef struct _HEAP_ENTRY // 20 elements, 0x8 bytes (sizeof)
{
union // 6 elements, 0x8 bytes (sizeof)
{
struct // 3 elements, 0x8 bytes (sizeof)
{
/*0x000*/ UINT16 Size;
/*0x002*/ UINT8 Flags;
/*0x003*/ UINT8 SmallTagIndex;
/*0x004*/ UINT8 _PADDING0_[0x4];
};
struct // 4 elements, 0x8 bytes (sizeof)
{
/*0x000*/ ULONG32 SubSegmentCode;
/*0x004*/ UINT16 PreviousSize;
union // 2 elements, 0x1 bytes (sizeof)
{
/*0x006*/ UINT8 SegmentOffset;
/*0x006*/ UINT8 LFHFlags;
};
/*0x007*/ UINT8 UnusedBytes;
};
struct // 2 elements, 0x8 bytes (sizeof)
{
/*0x000*/ UINT16 FunctionIndex;
/*0x002*/ UINT16 ContextValue;
/*0x004*/ UINT8 _PADDING1_[0x4];
};
struct // 4 elements, 0x8 bytes (sizeof)
{
/*0x000*/ ULONG32 InterceptorValue;
/*0x004*/ UINT16 UnusedBytesLength;
/*0x006*/ UINT8 EntryOffset;
/*0x007*/ UINT8 ExtendedBlockSignature;
};
struct // 2 elements, 0x8 bytes (sizeof)
{
/*0x000*/ ULONG32 Code1;
union // 2 elements, 0x4 bytes (sizeof)
{
struct // 3 elements, 0x4 bytes (sizeof)
{
/*0x004*/ UINT16 Code2;
/*0x006*/ UINT8 Code3;
/*0x007*/ UINT8 Code4;
};
/*0x004*/ ULONG32 Code234;
};
};
/*0x000*/ UINT64 AgregateCode;
};
}HEAP_ENTRY, *PHEAP_ENTRY;
Thus we have the following pseudo-code (minus some other checks):
high_part, low_part = GetShortField(0,0,"AgregateCode", 0);
high_part ^= HEAP.Encoding.Code1;
low_part ^= HEAP.Encoding.Code2;
AgregateCode = Make64BitFromTwo32Bit(high_part, low_part);
char var_B = (char)(AgregateCode >> 0x10);
if(var_B & 8)
printf("Internal");
Given that "AgregateCode" is ... well, an aggregate of Code1 to Code 4 :
struct // 2 elements, 0x8 bytes (sizeof)
{
/*0x000*/ ULONG32 Code1;
union // 2 elements, 0x4 bytes (sizeof)
{
struct // 3 elements, 0x4 bytes (sizeof)
{
/*0x004*/ UINT16 Code2;
/*0x006*/ UINT8 Code3;
/*0x007*/ UINT8 Code4;
};
/*0x004*/ ULONG32 Code234;
};
};
/*0x000*/ UINT64 AgregateCode;
If you shift 0x10 and AND 8 the AgregateCode field you end up finally testing the 11th bit (start counting at 0) of Code1.
As the structure is a big union, you finally end up testing: _HEAP_ENTRY.Flags
It happens that a heap flag has already the value 8, its name is: HEAP_ENTRY_VIRTUAL_ALLOC
http://doxygen.reactos.org/da/ddb/heap_8h_source.html#l000