Last active
March 29, 2016 11:52
-
-
Save ytomino/14a007f18a20b528502a to your computer and use it in GitHub Desktop.
Bad EnumWindows. Please run it with Delphi 2007. An entry for https://parnassus.co/bad-delphi-code-the-competition/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
program bad_enumwindows; | |
{$APPTYPE CONSOLE} | |
uses | |
Types, Windows, SysUtils; | |
{ EnumWindows with the magic context } | |
type | |
CEachWindow = function(Wnd: HWND; Context: Pointer): Boolean; cdecl; | |
function EnumWindowsProc(Wnd: HWND; var D: TMethod): LongBool; stdcall; | |
asm | |
mov EDX, D | |
mov EAX, TMethod[EDX].Data | |
push EAX | |
mov EAX, Wnd | |
push EAX | |
mov ECX, TMethod[EDX].Code | |
call ECX | |
add esp, 8 | |
xor al, 1 | |
cbw | |
cwde | |
end; | |
procedure EnumerateWindows(Proc: CEachWindow; Context: Pointer); | |
var | |
D: TMethod; | |
begin | |
D.Code := @Proc; | |
D.Data := Context; | |
EnumWindows(@EnumWindowsProc, LPARAM(@D)) | |
end; | |
procedure EnumerateChildWindows(Parent: HWND; Proc: CEachWindow; | |
Context: Pointer); | |
var | |
D: TMethod; | |
begin | |
D.Code := @Proc; | |
D.Data := Context; | |
EnumChildWindows(Parent, @EnumWindowsProc, LPARAM(@D)) | |
end; | |
{ base class of the enumerators } | |
type | |
TEnumerator = class(TObject) | |
private | |
caller_ebx, caller_esi, caller_edi, caller_ebp, caller_eip: Pointer; | |
enum_ebx, enum_esi, enum_edi, enum_esp, enum_eip: Pointer; | |
save_esp, save_eip: Pointer; | |
base_esp: Pointer; | |
FNext, FCurrent: Pointer; | |
FState: (esStart, esEnumerating, esTerminated, esEnded); | |
public | |
procedure BeforeDestruction; override; | |
function MoveNext: Boolean; register; | |
property GetCurrent: Pointer read FCurrent; | |
class function Each(Item: Pointer; Self: TEnumerator): Boolean; cdecl; | |
static; | |
class function GetEnumerator: TEnumerator; register; | |
end; | |
TEnumeratorClass = class of TEnumerator; | |
procedure TEnumerator.BeforeDestruction; | |
asm | |
mov edx, TEnumerator[eax].base_esp | |
sub edx, $400 { do nothing if unwinding by any exception } | |
cmp esp, edx | |
jb @Exit | |
mov dl, TEnumerator[eax].FState | |
cmp dl, esEnumerating | |
jnz @Exit | |
mov TEnumerator[eax].FState, esTerminated { set to terminated } | |
{ save the context } | |
pop ecx | |
mov TEnumerator[eax].save_eip, ecx | |
mov TEnumerator[eax].save_esp, esp | |
{ context switch } | |
mov TEnumerator[eax].caller_ebx, ebx | |
mov TEnumerator[eax].caller_esi, esi | |
mov TEnumerator[eax].caller_edi, edi | |
mov TEnumerator[eax].caller_ebp, ebp | |
mov TEnumerator[eax].caller_eip, offset @Next | |
mov ebx, TEnumerator[eax].enum_ebx | |
mov esi, TEnumerator[eax].enum_esi | |
mov edi, TEnumerator[eax].enum_edi | |
mov esp, TEnumerator[eax].enum_esp | |
mov edx, TEnumerator[eax].enum_eip | |
jmp edx | |
@Next: | |
{ restore the context } | |
mov esp, TEnumerator[eax].save_esp | |
mov ecx, TEnumerator[eax].save_eip | |
push ecx | |
{ unwind the stack } | |
mov eax, TEnumerator[eax].base_esp | |
lea edx, [eax - 24] | |
mov eax, [esp] {return to BeforeDestruction} | |
mov [edx], eax | |
mov eax, [esp + 4] {pop in BeforeDestruction} | |
mov [edx + 4], eax | |
mov eax, [esp + 8] {pop in BeforeDestruction} | |
mov [edx + 8], eax | |
mov eax, [esp + 12] {return to Destroy} | |
mov [edx + 12], eax | |
mov eax, [esp + 16] {return to finally block} | |
mov [edx + 16], eax | |
mov eax, [esp + 20] {return to normal context} | |
mov [edx + 20], eax | |
mov esp, edx | |
@Exit: | |
end; | |
function TEnumerator.MoveNext: Boolean; | |
asm | |
mov edx, TEnumerator[eax].FNext | |
mov TEnumerator[eax].FCurrent, edx {FCurrent := FNext} | |
{ jump to the current state } | |
xor edx, edx | |
mov dl, TEnumerator[eax].FState | |
mov edx, dword ptr[@JumpTable + edx * 4] | |
jmp edx | |
@Start: | |
mov TEnumerator[eax].FState, esEnumerating | |
@Enumerating: | |
{ save the context } | |
pop ecx | |
mov TEnumerator[eax].save_esp, esp | |
mov TEnumerator[eax].save_eip, ecx | |
{ context switch } | |
mov TEnumerator[eax].caller_ebx, ebx | |
mov TEnumerator[eax].caller_esi, esi | |
mov TEnumerator[eax].caller_edi, edi | |
mov TEnumerator[eax].caller_ebp, ebp | |
mov TEnumerator[eax].caller_eip, offset @Next { set to back to @Next after } | |
mov ebx, TEnumerator[eax].enum_ebx | |
mov esi, TEnumerator[eax].enum_esi | |
mov edi, TEnumerator[eax].enum_edi | |
mov esp, TEnumerator[eax].enum_esp | |
mov edx, TEnumerator[eax].enum_eip | |
jmp edx | |
@Next: | |
{ restore the context} | |
mov esp, TEnumerator[eax].save_esp | |
mov ecx, TEnumerator[eax].save_eip | |
push ecx | |
mov dl, TEnumerator[eax].FState | |
cmp dl, esEnded | |
jne @Continue | |
{ unwind the stack } | |
mov eax, TEnumerator[eax].base_esp | |
lea edx, [eax - 16] { return address over the exception-frame (12-byte) } | |
mov eax, esp | |
mov ecx, 16 | |
push edx | |
call System.Move | |
pop esp | |
@Continue: | |
mov al, 1 | |
ret | |
@Terminated: | |
mov TEnumerator[eax].FState, esEnded | |
mov al, 1 | |
ret | |
@Ended: | |
xor al, al | |
ret | |
@JumpTable: | |
dd @Start | |
dd @Enumerating | |
dd @Terminated | |
dd @Ended | |
end; | |
class function TEnumerator.Each(Item: Pointer; Self: TEnumerator): Boolean; | |
asm | |
mov edx, Item | |
mov eax, Self | |
push $12345678 //ecx {?... dummy, for escaping crash when exception } | |
push ebp | |
mov TEnumerator[eax].FNext, edx | |
mov TEnumerator[eax].enum_ebx, ebx | |
mov TEnumerator[eax].enum_esi, esi | |
mov TEnumerator[eax].enum_edi, edi | |
mov TEnumerator[eax].enum_esp, esp | |
mov TEnumerator[eax].enum_eip, offset @Next | |
mov ebx, TEnumerator[eax].caller_ebx | |
mov esi, TEnumerator[eax].caller_esi | |
mov edi, TEnumerator[eax].caller_edi | |
mov edx, TEnumerator[eax].caller_eip | |
mov ebp, TEnumerator[eax].caller_ebp | |
jmp edx | |
@Next: | |
pop ebp | |
pop ecx {?... pop dummy} | |
mov eax, Self | |
mov al, TEnumerator[eax].FState | |
cmp al, esTerminated | |
setae al | |
end; | |
class function TEnumerator.GetEnumerator: TEnumerator; | |
asm | |
mov dl, 1 { the hidden flag for the constructor } | |
call TEnumerator.Create { creating an instance, eax = VMT } | |
lea edx, [ebp + 8] { esp on "for-in", a caller should make its stack frame! } | |
mov TEnumerator[eax].base_esp, edx | |
mov TEnumerator[eax].caller_ebx, ebx | |
mov TEnumerator[eax].caller_esi, esi | |
mov TEnumerator[eax].caller_edi, edi | |
mov edx, [ebp] { ebp on "for-in" } | |
mov TEnumerator[eax].caller_ebp, edx | |
mov edx, [ebp + 4] { return address of "for-in" } | |
mov TEnumerator[eax].caller_eip, edx | |
mov [ebp + 4], offset @Return { set to back to @Return after } | |
ret | |
@Return: | |
mov dl, TEnumerator[eax].FState | |
mov TEnumerator[eax].FState, esEnded | |
test dl, dl | |
jz @Exit | |
mov ebx, TEnumerator[eax].caller_ebx | |
mov esi, TEnumerator[eax].caller_esi | |
mov edi, TEnumerator[eax].caller_edi | |
mov ebp, TEnumerator[eax].caller_ebp | |
@Exit: | |
mov edx, TEnumerator[eax].caller_eip { return to the same point } | |
jmp edx | |
end; | |
{ EnumWindows with enumerators } | |
type | |
TWindowEnumerator = class(TEnumerator) | |
function GetCurrent: HWND; | |
property Current: HWND read GetCurrent; | |
end; | |
TWindowCollection = record | |
function GetEnumerator: TWindowEnumerator; | |
class function List: TWindowCollection; inline; static; | |
end; | |
TChildWindowCollection = record | |
Parent: HWND; | |
function GetEnumerator: TWindowEnumerator; | |
class function List(AParent: HWND): TChildWindowCollection; inline; static; | |
end; | |
function TWindowEnumerator.GetCurrent: HWND; | |
begin | |
Result := HWND(inherited GetCurrent) | |
end; | |
function TWindowCollection.GetEnumerator: TWindowEnumerator; | |
procedure Call; | |
begin | |
EnumerateWindows(CEachWindow(@TWindowEnumerator.Each), Result); | |
end; | |
begin | |
Result := TWindowEnumerator(TWindowEnumerator.GetEnumerator); | |
Call; | |
end; | |
class function TWindowCollection.List: TWindowCollection; | |
begin | |
end; | |
function TChildWindowCollection.GetEnumerator: TWindowEnumerator; | |
procedure Call; | |
begin | |
EnumerateChildWindows(Parent, CEachWindow(@TWindowEnumerator.Each), | |
Result); | |
end; | |
begin | |
Result := TWindowEnumerator(TWindowEnumerator.GetEnumerator); | |
Call; | |
end; | |
class function TChildWindowCollection.List(AParent: HWND): | |
TChildWindowCollection; | |
begin | |
Result.Parent := AParent | |
end; | |
{ sample application, window-tree viewer } | |
procedure Walk (I: HWND; Level: Integer); | |
var | |
H: string; | |
T: array[0..80] of Char; | |
begin | |
if IsWindowVisible(I) then | |
begin | |
H := StringOfChar(' ', Level) + IntToHex(I, SizeOf(HWND) * 2) + ' '; | |
if GetWindowText(I, T, 80 - Length(H)) = 0 then | |
begin | |
GetClassName(I, T, 80 - Length(H)); | |
end; | |
WriteLn(H, T); | |
for I in TChildWindowCollection.List(I) do Walk(I, Level + 1) | |
end | |
end; | |
var | |
I: HWND; | |
begin | |
for I in TWindowCollection.List do Walk(I, 0); | |
ReadLn | |
end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment