Link to home
Start Free TrialLog in
Avatar of kretzschmar
kretzschmarFlag for Germany

asked on

Any way to detect if a screencapture (snapshot) is done?

Any way to detect if a screencapture (snapshot) is done?
and if possible, to replace the screenshot with another graphic?

scenario:
any software makes screenshots in a periodical intervall
i like to
- detect this
- replace the content of the shot on the fly

any ideas?

meikl ;-)

Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

No... Because all applications can just grab the contents of the screen whenever they want.
You could try to capture the PrintScreen button but an application like Paint Shop Pro doesn't use that key anyways.
Avatar of kretzschmar

ASKER

thanks for your response, alex

i guessed that also.

but i thought there may be a possibility to watch/hook/inject
about an access to the desktop-canvas

any further suggestions?

meikl ;-)
Well, perhaps you could hook into some Windows API's but if I think about Paint Shop Pro and it's ability to not just capture the whole screen but also just a part of the screen, or a window or even a button then I'm not sure it will work all the times. Besides, it could even be that some of those screen capture applications disable those kinds of hooks, but I'm not sure about that. Some screen capture tools are doing quite complicated things when you make a screenshot. :-)
usually i thought about to hook the GetDC and/or GetWindowDC - api
and compare the given handle with the desktop handle,
if it match i would return a dc from a bitmap canvas

sounds hazardous, or?

meikl ;-)
SOLUTION
Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
isn't it enough to replace the bitblt function within the WIN-API as such kind of program will copy the screen content to a bitmap and send it to a remote host?

My idea:  write some signature(pattern)  to the screen and check all outgoing data packages for this pattern,
will this work?
> how would you know if the desktop is just drawn or captured?
well, thats unknown

>Another risk could be that you would slow down your system considerably because with >every screen update, you would have to check for update or capture...
yep, thats an issue to avoid partial


>isn't it enough to replace the bitblt function within the WIN-API
would be an idea too, but there are also other function like StretchBlt

>write some signature(pattern)  to the screen and check all outgoing data packages for >this pattern, will this work?
the pattern maybe due compression or image-format change lost

any further suggestions?

meikl ;-)
what about a statistical analysis of the outgoing data bytes of the machine, you should see some peaks?
and a peak change correlated to the screen change.
Meikl,

Perhaps this problem could be tackled from another angle? (the angle you are proposing is nearly impossible to handle correctly)

Is it a case where you are trying to circumvent a certain program, or group of programs? If so, it may be easier to work that angle. Maybe with a little more info the others here can provide alternative ideas that would suit your needs.

Regards,
Russell
i think this should be a countermeasure against any spy software, correct?
>Is it a case where you are trying to circumvent a certain program, or group of
>programs? If so, it may be easier to work that angle.

usual i'm interested for solutions for

DameWare Mini RemoteControl
Tivoli RemoteControl
pcAnyWhere

>i think this should be a countermeasure against any spy software, correct?

well, as above most remotecontrols, but a general solution would be nice

meikl ;-)
Wait... You don't want your Boss to see the bikini girl you use as desktop background when he has remote access to your system, right? :-P
>Wait... You don't want your Boss to see the bikini girl you use as desktop background
>when he has remote access to your system, right? :-P

usual i have no problem with my boss, its more the young administartors (outsourced IT),
which taking itself too important, and i dislike if someone is lurking over my shoulder ;-)
(usual i notice this by jumping mousecursor and popupping a background window, so
some big brother is watching me, very annoying)

meikl ;-)
Just kill the process that would allow them to access your system remotely in that case. That's a lot easier.
I have never been bothered by young administrators anyway since I just don't allow them access to my system when I need all it's resources to develop software. No administrator is allowed to peek remotely on my system because that would invalidate the performance tests that I need to do quite often. :-)

One nice trick to kill unwanted processes is quite simple, btw. Start the task manager and right-click the unwanted process. Then attach the debugger (!!) and once the debugger is attached, you own the process and can kill it by closing the debugger. It is what I sometimes used to kill a background task that kept making a full backup of my system every day. The administrator was so frustrated after a while because it kept "crashing" on my system that they finally decided to use a better back-up system, that would make backups during the night when I wasn't using the system. ;-)
good point, alex, i will try this next monday
the debug option is disabled :-(
how to enable?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of SaLz
SaLz

u know that if u could replace a ss with an image on your hd, then in effect, print screen is a myth, this idea could work for Anti-Cheat Programs for games, when an Anti-Cheat program take a SS of your screen and sends it to the master server, the delphi program will say hey, ur not taking a snap of this desktop, u can have this snap from my hard drive instead.

pritty good idea.

Sal.
Awww. No debug option. You have a smart administrator... :) Mine wasn't that smart. He did notice the application crashing all the time so he blamed it to the application. (Especially after I've sold a few collegues how to disable it, because then the "problem" occurred on several systems.)
Maybe it works if you install MS Visual Studio on your system or at least the debugger from VS.

@Sal, responding to clipboard changes won't work since screen capture applications just don't use the clipboard to capture screenshots. The problem here is that Meikl doesn't want someone else looking at his desktop by using some remote tool. Responding to the print screen key won't work either. These capture programs just ask the system do draw a screenshot on some device that is NOT the desktop. Thus you need to approach the problem at the root, which happens to be at API level or even lower. It would have been nice if there was a way to disable those sneaky applications that try to capture the screen.

But I wonder... If you can replace API functions within Windows, then perhaps you can alter them for a single process. Maybe the solution to disable those spies is by just injecting some DLL within their process space that will make them crash. Not a nice way, but no application will continue to run after an unhandled exception. Maybe it's enough to capture one specific API call for these processes and make it fail, thus bringing it completely down.
Also gives you a reason to complain about your administrator because every time he tries to spy on your system, some part of it just crashes and it is caused by his spying software. ;-)
well,
thanks for all comments, seems there is no easy to implement solution ;-)

i will share the points equal to all participants.
is this ok for you?

meikl ;-)
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
would be an idea too, but this may detected, and i don't know how to block a port
(btw. i know the ports)

any who knows to block a port?

Well, there is a way to get the tcp table (very quickly) and to forcefully close any given connection. This would allow you to effectively close the communication channel that the remote software has established with your computer.

Take a look at the following paq.

https://www.experts-exchange.com/questions/20997952/blocking-internet-connections.html

If you think this would be useful for you, I could re-tool the code to handle your situation

Regards,
Russell
>I could re-tool the code to handle your situation

would be great, russell, its a thematic that was out of my focus,
so i guess it would be hard and therefore time expensive for me
to re-tool the code

meikl ;-)

Meikl,

I'm sure that you could have worked the code to fit yuor situation, but I figured I would save you the time. Here is a re-coding of the tcp table handling so its a little more generic, and can be used as a set of function calls. There are only a few functions, so the learning curve is pretty low

function     TcpOpenEnum(var TcpTable: PTcpTable): DWORD;
procedure  TcpCloseEnum(TcpTable: PTcpTable);
function     TcpPortFromLong(Port: LongWord): Word;
function     TcpAddrFromLong(Address: LongWord): String;
function     TcpStateDescription(State: LongWord): String;
function     TcpDeleteRow(TcpRow: PTcpRow): DWORD;

An example of using all functions:

var  lpTable:       PTcpTable;
     dwCount:       Integer;
begin

  // Retrieve the table of tcp entries
  if (TCPOpenEnum(lpTable) = ERROR_SUCCESS) then
  begin
     // Resource protection
     try
        // Walk the table entries
        for dwCount:=0 to Pred(lpTable^.dwNumEntries) do
        begin
           // Write out
           // -  the local port (in common format, vs network order)
           // -  the local address (in string format)
           // -  the descriptive state of the tcp entry
           WriteLn(TcpPortFromLong(lpTable^.Table[dwCount].dwLocalPort),
                   ' , ',
                   TcpAddrFromLong(lpTable^.Table[dwCount].dwLocalAddr),
                   ' , ',
                   TcpStateDescription(lpTable^.Table[dwCount].dwState));

           // Example of closing a tcp port/connection
           // - check for a connection to a remote port 80 (http) and close it
           if (TcpPortFromLong(lpTable^.Table[dwCount].dwRemotePort) = 80) then
              TcpDeleteRow(@lpTable^.Table[dwCount]);
        end;
     finally
        // Free the memory allocated by the open enum function
        TCPCloseEnum(lpTable);
     end;
  end;

end;

And finally, the source for it all.

Let me know if you run into questions/problems,
Russell

--------

unit TcpApi;
////////////////////////////////////////////////////////////////////////////////
//
//   Unit           :  TCPAPI
//   Date           :  Original -  05.25.2004
//                     Updated  -  11.12.2004
//   Author         :  rllibby
//
//   Description    :  Set of TCP enumeration and helper routines.
//
////////////////////////////////////////////////////////////////////////////////
interface

////////////////////////////////////////////////////////////////////////////////
//   Include units
////////////////////////////////////////////////////////////////////////////////
uses
  Windows,
  SysUtils;

////////////////////////////////////////////////////////////////////////////////
//   General constants
////////////////////////////////////////////////////////////////////////////////
const
  ALLOC_SIZE        =  4096;

////////////////////////////////////////////////////////////////////////////////
//   Data structures
////////////////////////////////////////////////////////////////////////////////
type
  PMIB_TCPROW       =  ^MIB_TCPROW;
  MIB_TCPROW        =  packed record
     dwState:       LongWord;
     dwLocalAddr:   LongWord;
     dwLocalPort:   LongWord;
     dwRemoteAddr:  LongWord;
     dwRemotePort:  LongWord;
  end;
  TTcpRow           =  MIB_TCPROW;
  PTcpRow           =  ^TTcpRow;

  PMIB_TCPTABLE     =  ^MIB_TCPTABLE;
  MIB_TCPTABLE      =  packed record
     dwNumEntries:  LongWord;
     Table:         Array [0..MaxWord] of MIB_TCPROW;
  end;
  TTcpTable         =  MIB_TCPTABLE;
  PTcpTable         =  ^TTcpTable;

  PIP_BYTES         =  ^IP_BYTES;
  IP_BYTES          =  Array [0..3] of Byte;
  TIpBytes          =  IP_BYTES;
  PIpBytes          =  ^TIpBytes;

////////////////////////////////////////////////////////////////////////////////
//   Function definitions
////////////////////////////////////////////////////////////////////////////////
type
  TGetTcpTable      =  function(lpTcpTable: PTcpTable; lpdwSize: PDWORD; bOrder: BOOL): DWORD; stdcall;
  TSetTcpEntry      =  function(lpTcpRow: PTcpRow): DWORD; stdcall;

////////////////////////////////////////////////////////////////////////////////
//   TCP table entry state constants
////////////////////////////////////////////////////////////////////////////////
const
  MIB_TCP_STATE_CLOSED       =  1;
  MIB_TCP_STATE_LISTEN       =  2;
  MIB_TCP_STATE_SYN_SENT     =  3;
  MIB_TCP_STATE_SYN_RCVD     =  4;
  MIB_TCP_STATE_ESTAB        =  5;
  MIB_TCP_STATE_FIN_WAIT1    =  6;
  MIB_TCP_STATE_FIN_WAIT2    =  7;
  MIB_TCP_STATE_CLOSE_WAIT   =  8;
  MIB_TCP_STATE_CLOSING      =  9;
  MIB_TCP_STATE_LAST_ACK     =  10;
  MIB_TCP_STATE_TIME_WAIT    =  11;
  MIB_TCP_STATE_DELETE_TCB   =  12;

const
  MIB_TCP_STATES:            Array [0..12] of PChar =
                            ('Unknown',
                             'Closed',
                             'Listening',
                             'Syn Sent',
                             'Syn Received',
                             'Established',
                             'Fin Wait1',
                             'Fin Wait2',
                             'Close Wait',
                             'Closing',
                             'Last Ack',
                             'Time Wait',
                             'Deleted');

////////////////////////////////////////////////////////////////////////////////
//   Late bound function wrappers
////////////////////////////////////////////////////////////////////////////////
function   GetTcpTable(lpTcpTable: PTcpTable; lpdwSize: PDWORD; bOrder: BOOL): DWORD; stdcall;
function   SetTcpEntry(lpTcpRow: PTcpRow): DWORD; stdcall;

////////////////////////////////////////////////////////////////////////////////
//   TCP functions designed to be used by developers
////////////////////////////////////////////////////////////////////////////////
function   TcpOpenEnum(var TcpTable: PTcpTable): DWORD;
procedure  TcpCloseEnum(TcpTable: PTcpTable);
function   TcpPortFromLong(Port: LongWord): Word;
function   TcpAddrFromLong(Address: LongWord): String;
function   TcpStateDescription(State: LongWord): String;
function   TcpDeleteRow(TcpRow: PTcpRow): DWORD;

implementation

////////////////////////////////////////////////////////////////////////////////
//   Library and function name constants
////////////////////////////////////////////////////////////////////////////////
const
  LIB_IPHLPAPI            =  'iphlpapi.dll';
  FUNC_GETTCPTABLE        =  'GetTcpTable';
  FUNC_SETTCPENTRY_NAME   =  'SetTcpEntry';

////////////////////////////////////////////////////////////////////////////////
//   Protected variables
////////////////////////////////////////////////////////////////////////////////
var
  hIphlp:           HMODULE        =  0;
  _GetTcpTable:     TGetTcpTable   =  nil;
  _SetTcpEntry:     TSetTcpEntry   =  nil;

function TcpDeleteRow(TcpRow: PTcpRow): DWORD;
begin

  // Check assignment
  if Assigned(TcpRow) then
  begin
     // Set entry state
     TcpRow^.dwState:=MIB_TCP_STATE_DELETE_TCB;
     // Call SetTcpEntry
     result:=SetTcpEntry(TcpRow);
  end
  else
     // Invalid param
     result:=ERROR_INVALID_PARAMETER;

end;

function TcpStateDescription(State: LongWord): String;
begin

  // Handle state
  if State in [MIB_TCP_STATE_CLOSED..MIB_TCP_STATE_DELETE_TCB] then
     // Return state description
     result:=MIB_TCP_STATES[State]
  else
     // Unknown state
     result:=MIB_TCP_STATES[0];

end;

function TcpAddrFromLong(Address: LongWord): String;
var  lpBytes:    TIpBytes;
     dwIndex:    Integer;
begin

  // Move dword to byte array
  Move(Address, lpBytes, SizeOf(LongWord));

  // Set start of string
  result:=IntToStr(lpBytes[0]);

  // Walk remaining bytes
  for dwIndex:=Succ(Low(lpBytes)) to High(lpBytes) do result:=result+'.'+IntToStr(lpBytes[dwIndex]);

end;

function TcpPortFromLong(Port: LongWord): Word;
begin

  // Convert from network order to common port format
  result:=(Port div 256) + (Port mod 256) * 256;

end;

function TcpOpenEnum(var TcpTable: PTcpTable): DWORD;
var  dwSize:        DWORD;
begin

  // Set the default size, this is enough to hold appx 204 entries
  dwSize:=ALLOC_SIZE;

  // Allocate memory
  TcpTable:=AllocMem(dwSize);

  // Attempt to get the full tcp table
  result:=GetTcpTable(TcpTable, @dwSize, True);

  // Check for insuffecient buffer
  if (result = ERROR_INSUFFICIENT_BUFFER) then
  begin
     // Re-alloc the table
     ReAllocMem(TcpTable, dwSize);
     // Call the function again
     result:=GetTcpTable(TcpTable, @dwSize, True);
  end;

  // Check result
  if (result <> ERROR_SUCCESS) then
  begin
     // Failed to get table, cleanup allocated memory
     FreeMem(TcpTable);
     // Clear the table
     TcpTable:=nil;
  end;

end;

procedure TcpCloseEnum(TcpTable: PTcpTable);
begin

  // Need to free the memory allocated by a call to open enum
  if Assigned(TcpTable) then FreeMem(TcpTable);

end;

function GetTcpTable(lpTcpTable: PTcpTable; lpdwSize: PDWORD; bOrder: BOOL): DWORD;
begin

  // Make sure the api function was bound
  if Assigned(@_GetTcpTable) then
     // Call the function
     result:=_GetTcpTable(lpTcpTable, lpdwSize, bOrder)
  else
     // Function was not bound
     result:=ERROR_PROC_NOT_FOUND;

end;

function SetTcpEntry(lpTcpRow: PTcpRow): DWORD;
begin

  // Make sure the api function was bound
  if Assigned(@_SetTcpEntry) then
     // Call the function
     result:=_SetTcpEntry(lpTcpRow)
  else
     // Function was not bound
     result:=ERROR_PROC_NOT_FOUND;

end;

initialization

  // Load the ip helper api library
  hIphlp:=LoadLibrary(LIB_IPHLPAPI);

  // Attempt to get the function addresses
  if (hIphlp > 0) then
  begin
     // Bind both the get table and set entry functions
     @_GetTcpTable:=GetProcAddress(hIpHlp, FUNC_GETTCPTABLE);
     @_SetTcpEntry:=GetProcAddress(hIpHlp, FUNC_SETTCPENTRY_NAME);
  end;

finalization

  // Clear bound functions
  @_GetTcpTable:=nil;
  @_SetTcpEntry:=nil;

  // Free the ip helper api library
  if (hIphlp > 0) then FreeLibrary(hIphlp);

end.
many thanks, russell, you save my rare time ;-)

i will code a little frontend to your unit tomorrow,
and if i run into problems, then i will post it here

meikl ;-)
sorry, got no time during weekend :-( -> so no problems yet :-)
Take your time, no rush from this end...

Russell
lol
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
P.S: Probably instead of returning 0 you could also return a fake DC. This should avoid crashes, as long as the DC behaves just like the screen DC would.
good point, madshi, but how do i this?

seems this question extends 500 pts ;-)
By using madCodeHook, of course!   :-)   Hooking an API is really easy that way...
>By using madCodeHook, of course!
how could i this forget :-))
madshi, could u tell me this, is it possible to hook your application into another application when the other application starts?

so if someone clicks on another program lets say notepad for an example, if they start up notepad as normal with a shortcut or a direct execute, could u have hooked your app so it will start up along side notepad and close with notepad when notepad is closed? so it opens and closes along side notepad or any other application?

if so, and u know how to do this, I'll make a 500point question on this and send u the link.

Sal.
Sal, there are multiple ways to do it. But most of them require you to run your own program (or a part of your program) before Notepad starts, so that you can detect when Notepad is loaded. Probably you don't like that. In that case you could try to rename Notepad.exe to Notepad2.exe and put your own exe as Notepad.exe. But I'm not sure what the system file protection will say about this.
ya, that way seems the only way at the moment, replace your app with the apps name and swap them around when user clicks on it and both load up.

like with, anti-cheats, they use a wrapper, so when the app loads your app loads up, gives u an option to start in ac mode or normal mode.

can this be done in the same way to a normal app?
Well, probably that's exactly what the wrapper does, namely simply replacing the app names. Maybe additionally encrypting/decrypting the original exe. But let's not hijack meikl's thread here. That's another topic and doesn't really belong here... Anyway, there's not really more I can say about this, so perhaps we should just stop here now. (No needs to give me points for what I said until now).
well, thanks to all, time to close this thread

i will split the points equal to all participants
(because this q isn't really solveable)
except for rllibby

for rllibby i will post an additional q (for blocking ports)
(hope i get no problem with the PE ;-)) and the mods,
because exciting 500 pts, but i do argument that this solution,
isn't a solution for this q)

meikl ;-)
Meikl: hope i get no problem with the PE ;-)

rofl... heheheheheh....
Meikl,

I've had the same issue: I don't like others looking over my shoulder as well. What I did is a wee bit simpler though: I wrote a small little app that runs LoggedOn.exe from www.sysinternals.com. This little gem shows you all users logged on to your machine locally and remotely. The little shell I wrote around it uses a timer to check every 5 seconds who is logged on, and if the list (usually it's only me) changes it shows an alert, pops to the foreground and shows the list of users. So I know within 5 seconds when somebody else is on my machine, you could even set the timer faster if you want (it doesn't use much in the way of CPU/memory).

Maybe that does what you want? You could easily set it to block all network trafficv instead of popping up a message, for example.

Regards,
Stefan

PS. I know the question is already closed, but still wanted to contribute this approach. IMHO a lot easier...
sounds interested, could you post the source here or forward me via email
(email is found in my profiole, just click on my name)

of course i will spent some points for you then

meikl ;-)
Sorry, I don't have the code here on my workmachine (I'm at the office now). Basically I had downloaded LoggedOn.exe from SysInternals, which is a little utility listing all users logged on to your machine. It is a little command line utility, now part of the PsTools suite. It can still be downloaded for free from http://www.sysinternals.com/ntw2k/freeware/psloggedon.shtml. I then simply wrote a little delphi interface around it, which on a timer event executed the app with a hidden window, redirecting the output to a text file. I then read the text file from my little shell and compare it with a previous list. So there's very little coding that I've done myself, the real engine behind it is from Mark Russinovich (Thanks Mark!).

You will always see yourself logged on, and if you browse to Windows Network, and find your own computer, then display a share in the explorer on your machine you will even see yourself twice in the list: once Locally and once with your domain name. SysInternals used to make the code available as well, but it is C++. Checking their site they have renewed it, for Win2K: http://www.sysinternals.com/ntw2k/source/misc.shtml#logonsessions.

HTH,
Stefan