Skip to content

Instantly share code, notes, and snippets.

@ytomino
Last active March 29, 2016 11:52
Show Gist options
  • Save ytomino/14a007f18a20b528502a to your computer and use it in GitHub Desktop.
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/
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