
Course 43 - Practical Malware Development | Episode 9: Finalizing Client-Side Command & Control
Get every episode summarized
Each time CyberCode Academy publishes, we email you a written briefing from the transcript — the topics, who appeared, and any specific claims, with the ad reads skipped.
Email me new episodesFree for 3 shows. No card needed.
About this episode
“This week at Vons and Albertsons, USDA Choice Tri-Tip Rost untrimmed are 599 per pound, limit 4 roasts with membership wear applicable, and medium-ripe Hassovicados are 99 cents each, with membership wear applicable.”From the transcript
- Host name
- IP address
- Operating system
- Command processing: Determines what operation should be performed and produces its result.
- Network communication: Transmits that result back to the backend.
↓
System Registration
↓
Database Client Record
↓
Periodic Task Check
↓
Task Processing
↓
Result Generation
↓
Result Submission
↓
Database Update
↓
Administrative MonitoringThis final integration demonstrates how individual components developed throughout the previous episodes can be combined into a single database-backed application architecture.Key TakeawaysBy the end of this episode, you will understand how to:
- Configure a client application to communicate with a backend service.
- Register system metadata through an HTTP POST request.
- Implement a continuous polling workflow.
- Refactor application logic to return execution results.
- Separate processing logic from network communication.
- Submit generated results back to a server-side endpoint.
- Maintain client and task state within a database.
- Automatically clear processed task states.
- Connect client registration, task management, result handling, and administration into one complete workflow.
You can listen and download our episodes for free on more than 10 different platforms:
https://linktr.ee/cybercode_academy
Get every episode summarized
Each time CyberCode Academy publishes, we email you a written briefing from the transcript — the topics, who appeared, and any specific claims, with the ad reads skipped.
Email me new episodesFree for 3 shows. No card needed.
Hosts & guests
Transcript ready
590 searchable segments. Every word is indexed and playable.
Full transcript
CyberCode Academy — Course 43 - Practical Malware Development | Episode 9: Finalizing Client-Side Command & Control. Machine-transcribed; use the interactive transcript above to jump the player to any line.
This week at Vons and Albertsons, USDA Choice Tri-Tip Rost untrimmed are 599 per pound, limit 4 roasts with membership wear applicable, and medium-ripe Hassovicados are 99 cents each, with membership wear applicable. Plus Kellogg cereals 8.8 to 16.1 ounces, selected varieties, or peppered farm goldfish, 5.9 to 8 ounces, are 190.9 each when you buy three. Limit 3 offers with membership wear applicable. Visit Vons or Albertsons.com for more deals and ways to save. Imagine for a second that you're sitting at your favorite coffee shop.
Oh nice. Right, so you open your laptop, you connect to the Wi-Fi, and you start browsing. Still normal day. Exactly. Everything looks perfectly normal. But deep in the background of your operating system, your computer is silently taking orders from this, well, this hidden digital headquarters. Oh wow. Yeah, it's continuously reaching out, asking for instructions, executing them, and then just kind of whispering the results back into the dark. I mean, it sounds like a scene from a thriller, honestly. But in the cybersecurity world, this is, it's a very standard reality. It's actually the fundamental architecture of a client-server relationship. Right, right. Specifically is how a compromised client machine, which we often call bot, how it communicates with a centralized attacker control panel. And honestly, when you strip away the whole mystique of hacking, you realize it's not dark magic at all. No. No, it's just highly organized, repetitive logic. Which is exactly our mission for you today in this deep dive.
We are going straight into the matrix, basically, to conceptually build this architecture from the ground up. Yeah, we really are. We're going to map out the logic step-by-step of how a piece of client software actually registers itself asks for commands and then reports back. We are essentially building the mental blueprint of a control panel system today. And you won't need a computer science degree to follow along, I promise. Good. Because I might need a refresher myself. Well, whether you write code every day or you're just, you know, insanely curious about how systems secretly talk to each other over the internet, we're going to break this down into a series of very logical web requests, conditions, and loops. So by the end of this deep dive, you'll understand the exact mechanics of how malware, quote unquote, phones home. Exactly. Let's jump in. So if I'm writing this client software like the bot that's going to live on the target machine, I feel like the very first hurdle is just location. Yep, hardy acquisition. Right. The bot has to know where headquarters is.
It can't just shout into the digital void and hope the attacker hears it. That's the perfect starting point. The very first task in our architecture is defining those communication routes. The client software has to know its targets. So in a standard setup, we established two crucial URLs that point back to the attacker's server IP address. Got it. Let's imagine, just for the sake of our mental model here, that the attacker's IP address ends in 145. Okay. So we need to point the software to that 145 address. What are the two routes? Well, target number one is the registration page. So the URL is going to be that IP address, the one ending in 145, followed by a slash and then register.php. Register.php. Okay. Yeah. This is the endpoint where the client basically announces its presence and then target number two is the results page. Okay. That URL is the exact same IP address, followed by a slash and get underscore results.php. That's where the client sends the intel back after it actually does some work. I picture this.
It's kind of like programming a homing pigeon, right? Oh, I like that. Yeah. The digital pigeon, two very specific addresses it needs to memorize. Address one is where it flies to clock in and say, you know, I am alive. I'm on duty. Yep. And address two is where it flies to drop off whatever loot or intelligence it gathered from the victim's machine. That analogy holds up beautifully actually. But the critical piece here from a programming perspective is how we handle those addresses in the code itself. Right. We don't just type out that 145 IP address every single time we need to send the message. Yeah. We store these URLs in distinct variables. We might name them like register URL and get results. Exactly. I'm trying to think through why that matters though. I mean, is it just because typing out the IP address 50 times is bad practice or is there a, you know, a strategic reason for an attacker to use variables? Oh, it's entirely strategic. I mean, think about the cat and mouse game of cybersecurity. Right. And if this infrastructure is constantly being hunted.
So if a security team discovers that 145 IP address, they're going to block it immediately. No. And then the attacker is locked out. Exactly. The attacker is going to be forced to move their control panel to a completely new server with a brand new IP. Ah, I see it now. So if you hard code the IP address, all of your script, you have to manually hunt down every single line of code and change it. Just a nightmare. Yeah, especially if you have thousands of bots. Yeah. But if you use variables, you just what you changed the IP address in one single spot at the very top of your script. Yep. Just update the variable once and the entire architecture instantly knows the new routes. It's really the foundation of writing resilient software. You want your code to be adaptable. So the pigeon knows the routes. The variables are set. The next phase has to be that actual clock in process, you mentioned. Right. The registration. Because the client knows where the control panel is, but it has to officially register itself before the server will even like acknowledge it. Logically, yeah. This registration sequence happens just before the software enters its main program loop.
Because when the client knocks on the door of the control panel, the server needs to know exactly who is knocking. A random ping is useless. Completely useless. The client has to build a profile of the victim's machine and send it over. What kind of intel goes into that profile? I mean, if I'm the attacker, what do I actually care about? Well, you need context to issue effective commands. So we gather data using a general info class. Typically an attacker wants three key pieces of intelligence right out of the gate. Okay. What are they? The host name of the victim machine, it's IPv4 dress and the specific version of the operating system it's running. Let me stop you there, actually, because I want to make sure I understand the why behind those three. Sure. The host name makes sense. You want to know if you're looking at like John's laptop or corporate finance server O4. Exactly. The IP address tells you where they are on the network. But why the operating system version? Why is that so crucial just for registration? Because commands aren't universal. If you send a Windows command line instruction to a machine running Mac OS or Linux.
It just fails. Yeah, it just fails. Or worse, it might trigger an error log that actually alerts the user. Oh, right. You'd give yourself away. Yeah. So the control panel needs to know the OS. So the attacker knows which specific weapons or commands will actually work on that specific target. That makes total sense. So the client gathers the host name, the IP and the OS. It bundles them together into a new variable. Let's call it register parameters. Perfect. But sending it isn't as simple as just throwing the variable at the server, is it? We have to format the delivery somehow. You do. Yeah, we have to create a web client object to handle the transmission. And part of that involves setting a very specific HTTP header. Okay. Headers are kind of like the invisible metadata of the internet. Yeah. So before we send a registration profile, we set a content type header to a value called application slash x dash www dash form dash Irland coded. Okay. Wow. Application x www form Irland coded is quite a mouthful.
It really is. Let's demystify that for anyone who isn't, you know, a web developer. What is that header actually telling the server? Think of it as instructions for the receiving doc. Okay. When the server gets the data, it needs to know how to unpack it. So by using that specific header, we're basically telling the server, hey, the data I'm sending you is formatted exactly as if a human user just filled out a standard HTML form on a website and click submit. Oh, okay. Yeah. It guarantees the server parses the host name, IP and OS correctly without, you know, mangling the text. So think of it like going to the post office. Okay. Like you can't just hand them a loose pile of items. You have to put it in a box. And sometimes you have to slap a sticker on it that says, you know, fragile, handle with these specific dimensions. Exactly. The header is that sticker. It tells the server how to treat the package. That's a great way to visualize it. So with the payload bundled and the header set, we execute the delivery using a method called upload string. We point it to our register url variable and we attach our register parameters payload.
Now wait a minute. If we're testing this like build in this architecture in 11 environment just to make sure it works. How do we know our registration actually succeeded? Ah, testing requires very strict hygiene. Meaning. If you just run the code, you might see data on your control panel, but you won't know if it's fresh data or just leftover junk from a test you ran three days ago. Right. Right. So before you ever test the registration script, you have to wipe the server's database clean. Let's say the database table holding all the bots is called victims. We use in SQL command truncate table victims. Truncate. It's such a decisive action. I mean, it completely obliterates everything in that table. The slate is wiped entirely clean. And once it's truncated, then you run your client script. If the table goes from empty to suddenly having one perfect entry with your host name, IP and OS, then your architecture is functioning. Exactly. You've removed all the false positives. So I have a mechanical question about that delivery. Sure. We send that payload. We are using an HTTP POST request, right?
Yes. I hear a lot about key requests. Like, that's what happens every time I type a website into my browser. So why use a POST request here instead of just a simple, do you wait request? It really comes down to security and payload size. GIG requests append all the data directly into the URL itself. Oh, like when you see URLs that are a mile long with all sorts of symbols at the end. Yes, exactly. That is visible to anyone monitoring network traffic. Plus, browsers impose trick-length limits on URLs. So if I used a GV request, my machine's entire profile would just be sitting naked in the web address for like any firewall to see. Exactly the problem. POST requests are designed differently. They securely submit data payloads within the actual body of the HTTP request, completely hidden from the URL string. Oh, that makes sense. It's much more robust. It handles large amounts of data, and it's just the standard for sending structured information like our victim profile. It's kind of the difference between writing your secret message on the outside of a postcard for the mail carrier to read versus folding it up and sealing it securely inside a thick
envelope. Yes, POST is the envelope. Got it. Which actually brings us to the next phase. Toyota's easy choice sales event is on. Whether you're looking for the performance of the camera, the versatility of Rav4, the efficiency of a Corolla, or all electric driving in the BZ. There's a Toyota that's just right for you. And with great deals across the lineup, now's the time to find yours. But hurry, these deals won't last long. Toyota's easy choice sales event ends soon. We make it easy. Toyota, let's go places. We're going to be so far. Try them all only at McDonald's. Toyota's easy choice sales event is on. Whether you're looking for the performance of the camera, the versatility of Rav4, the efficiency of a Corolla, or all electric driving in the BZ.
There's a Toyota that's just right for you. And with great deals across the lineup, now's the time to find yours. But hurry, these deals won't last long. Toyota's easy choice sales event ends soon. We make it easy. Toyota, let's go places. Our envelope has been received. The client is officially registered in the database. The pigeon clocked in. But now we have a pacing problem, don't we? How so? Well, the client is sitting on the victim's laptop and it needs instructions. But the attacker isn't going to send a command, the absolute millisecond, the bot registers. The attacker might be asleep. Very true. So it has to wait for the boss to speak. And in programming, waiting for an unpredictable event usually requires an infinite loop, right? Correct. We implement a continuous while loop. This is the heartbeat of a client software. As long as the program is running, this loop will continuously execute over and over. OK. But we don't just put our communication logic raw into the loop.
We place it inside a tri-block. I was actually going to ask about that. Because an infinite loop constantly reaching out over the internet sounds incredibly fragile. Like, what happens if the Wi-Fi drops for two seconds? If you don't use a tri-block, the client software crashes instantly. Yeah, the process dies. And the attacker loses the bot forever. Network communications are inherently unstable. Packets get dropped. Servers timeout. Routers reset. Right. So a tri-block acts as a safety net. It basically tells the software, hey, try to execute this web request. If it fails for literally any reason, do not panic, do not crash, just swallow the air gracefully and let the loop run again. It keeps the heartbeat going no matter what. Exactly. So inside the safe, infinite loop, the client sends another POST request. But this time, it's pointing to a different target, the get command dot PHP URL. It's constantly asking, do you have anything for me to do? But to ask that question effectively, the control panel still needs to know who is
asking. Right. The server isn't just going to hand out commands to an anonymous request. The client has to identify itself again. Wait, does that mean we have to write a whole new block of code together, the hostname, the IP and the OS all over again every single time the loop cycles? That seems incredibly inefficient. It would be, which is why we don't do it. Oh. We already gathered all that intelligence during the registration phase. We simply reuse the exact same variable. Ah, smart. The register parameters variable contains everything we need. For the sake of clean code, a developer might just rename that variable to simply parameters since it now serves a dual purpose. It registered the bot and now it identifies the bot when requesting commands. So we attach our content type header again, use the upload string method and fire the POS T request. You got it. I picture this while loop as a highly disciplined radio operator in the field. They have the radio glued to their ear, just pressing the button every few seconds. This is hostnameX at IPY, any orders, any orders, any orders.
And 99% of the time the server responds with dead air. Nothing. Just silence. Exactly. And the loop just cycles again. But eventually the attacker logs into the control panel and issues a command to check the victim's network configuration. The client captures that response from the server and saves it as a string variable. Let's call it taking command. So the radio operator finally gets an order. But I imagine we don't just blindly execute whatever comes over the wire. We need a logic check to make sure it's a real command and not just a glitch or like an empty space. Spot on. We implement a very simple condition. We write an if statement. If the length of our taking command string is greater than one, then we proceed. Why greater than what? I mean, why not just check if it's empty? Because sometimes servers return a single blank space or a hidden line break character. If you try to execute a line break as a system command, it can actually cause an error. Checking if the length is greater than one ensures we have actual substantial text like the word if config before we try to do anything with it.
That is a clever little safeguard. So the condition is met. We have a real command. Now this software has to actually do the work. It passes that string over to a dedicated function called the command parser. The parser usually lives in a separate operations class. Having it separate makes the architecture much cleaner. The parser's entire job is to read the string, translate it into something that victims operating system understands, and execute it silently in the background. So the radio operator hands the order to a field agent and the field agent actually picks the lock. Love the spy analogies today. It helps. It makes sense. But here is where I see a major flaw if we stop here. If the client machine runs the command, let's say it finds a bunch of interesting files, but headquarters never finds out what happened. The entire system is useless. The client has to report the results back. This is a critical transition in our architecture and it requires a fundamental shift in how the command parser function is programmed. Okay. So in many basic scripts, when you just want a function to do something, its return data
type is set to void. For anyone who hasn't dabbled in object oriented programming, let's unpack void. What does it actually mean for a function to be void? Think of it like giving a task to a worker and telling them not to bother you when they're finished. Okay. Void means the function does its job. It executes the system command and then it returns absolutely nothing back to the main script that called it. It works in the dark. It's like telling a sous chef to chop onions and they chop them but they never bring them back to your station. They just leave them on the counter. Yes. If we want the intel back, we can't use void. Exactly. So we can change the return type of the command parser from void to string. This tells a program, hey, when you finish executing the system command, I expect you to hand me back a string of text containing the output. Right. And to ensure that happens, we have to meticulously place the return keyword just before every single function call inside that parser. We're forcing the sous chef to bring the onions back. We are. So the results flow back up to the main script and we assign that raw intelligence to a new
variable called command result. But we face the same problem we had during the command request. We cannot just throw that raw result at the server. Why not? If the server receives a text file full of network data, it has no idea which bot sent it. Oh, it lacks context. Exactly. We have to package the loot. We create a new variable called result parameters. Inside this package, we combine three things. The host name, the IP address, and our brand new command result. We stamp our name and return address on the box, apply our trusty HTTP header, and use upload string to fire it off via a POST request to our third target that get results URL. And the loop is complete. The client asked for an order, executed it, captured the output, and radio the intelligence home. Boom. It is incredibly satisfying to see the whole cycle connect like that. It really is. But wait, let's play out the reality of this. We have a while loop running constantly. And command, executing them, shoving results into a database to be displayed on the attacker screen. If this is happening in real time, isn't the server going to absolutely choke on its own
data? It absolutely will. I know it. A digital traffic jam is unavoidable if the database state isn't managed properly. This is actually where many poorly written malware architectures completely fall apart. Let's walk through a scenario to visualize why it breaks. Let's say I'm the attacker. I'm sitting at my control panel. I type in an ipconfig command. The bot grabs it, runs it, and sends the network details back. My screen populates with the data. It feels like a flawless victory. But then you decide to dig deeper. You send a completely different command. Maybe an L's to list the files in a directory or PDUD to see the present working directory. You hit enter, you wait a second, you refresh the page, and you just see the old ipconfig network details again. New new files. The new files don't show up. Oh man. That is intensely frustrating. Why is the system feeding me stale data? Because I know the bot is executing the new command. The bot is doing its job perfectly, but the server is failing.
The database is still holding onto that very first result. When your control panel web page refreshes to show you the latest intelligence, it queries the database, grabs the first thing it sees in the result column for that bot, and displays it. It's like having a voicemail box that is completely full. People are leaving new messages, but when you hit play, you only ever hear the very first message you never deleted. We have to build a mechanism to take out the trash. The server needs to clear the pipe. Yes. We have to implement a final, crucial database fix on the control panel side. The moment the control panel successfully reads and displays a result on your screen, it must instantaneously erase that result from the database. It has to make room for the next cycle of the wild loop. Exactly. We actually code that Erasure on the server side. I imagine it's in SQL command. It is an update command. After the result is shown, the server executes this query. You paid victims set command underscore result equals quote, quote, where hostname equals question
mark. Okay. Let's break down that SQL logic for everyone. We aren't deleting the bot from the database, right? That would ruin everything. Right. No. We use you tape victims to tell the database. We just want to modify the existing table. Yep. The command underscore result equals quote, quote, is the crucial part. We are telling the database to look at the specific column holding the intelligence and change its value to an empty string. So two single quotes with nothing in between. Exactly. We are wiping that specific cell completely blank. And then there's the targeting system where hostname equals question mark. I'm guessing that question mark is vital. It is. A parameter that gets replaced dynamically with the specific victim's hostname. You have to be precise. What happens if you aren't? Well, if you don't include that wear clause, you will accidentally wipe out the results column for every single bot in your entire database simultaneously. Oh, wow. That would be bad. Yeah. By specifying the hostname, you only take out the trash for the bot you are actively viewing. It is brilliant in its simplicity.
You view the result. The server instantly fired that SQL command in the background. The database cell goes blank and the control panel is immediately ready to receive the next package of loop. The interface just goes from being a broken clunky mess to a smooth real time command and control center. And it highlights a really important lesson about architecture. We spend so much time worrying about the complex network communications. But ultimately, the system is only as strong as its basic database hygiene. Let's take a breath and recap this incredible journey. Good idea. We started by setting our targets using variables for our register and get results URLs so our code can adapt if the server IP changes. We clocked in by gathering our hostname, IP and OS, bundling it with a specific header to ensure safe delivery via a PSDRI request. Then we built resilience with the tri-block, inside an infinite while loop, constantly reaching out to ask for commands while reusing our registration parameters to keep the code efficient. We implemented a length check to avoid executing empty air.
We made the crucial switch in our command parser, changing the return type from void string, so our digital souschef would actually bring the chopped onions back to the main script. We packaged that result with our identity and shipped it home. And finally, we solved the database traffic jam. We implemented a SQL UP date command to instantly wipe old results from the database the moment they're viewed, ensuring a continuous flow of real time intelligence. The logic really is beautiful when you string it all together. It really is. But to truly understand a system, you have to know how to troubleshoot it. OK, let's do a little test. Yeah, let's put you the listener in the hot seat for a second. Imagine you've built this entire architecture. You deploy your client software. You know for a fact the bot is successfully grabbing commands and executing them on the target machine. But your control panel remains completely blank. Right. The intelligence never shows up. Based on our deep dive today, what are the two tiny oversights that are most likely causing this failure? Take a second and think about the flow of data.
Well, if the command fires, but the panel is blind, the data is getting lost on the journey back. First, you check your client code. Did you remember to change your command parser's return type to string? And did you add the return keywords? Because if it's still set to void, the bot is doing the work, but throwing the results into the void. Exactly. And you can check your server code. Did you implement the SQL query to clear the database? Because if the server isn't wiping the old data, the new intelligence hits a brick wall and gets discarded. It always comes down to following the flow of the data. Always. And that flow leaves a footprint, which is a perfect pivot to something I want to leave you with today. Oh, nice. We've spent this entire session inside the mind of the architect, right? Building the system, but turn the board around, put on your defense hat. It's a great thought experiment. Now that you understand exactly how the repetitive logic of a client server relationship is coded, this infinite loop constantly reaching out, pausing, reaching out again to a specific
set of URLs, how would you use that knowledge against the attacker? Exactly. You know how the machine breathes. You know it's heartbeat. So how would you design a network monitor to catch this exact, rhythmic pattern of behavior hiding in the noise of everyday web traffic? It's a fascinating problem to chew on. It is. We will leave you to mall that over. Until next time, keep diving deep. It's fall in Jeep country, and during the drive into fall sales event, get a great deal on four by fours that refuse to be contained, like Jeep Wrangler. Confidence built into every drive with the most awarded SUV ever, Jeep Grand Cherokee, and freedom that can't be denied, with the open air freedom and Jeep gladiator. After 85 years, it's no surprise that Jeep became America's SUV brand, get a great deal during the Jeep drive into fall sales event. Jeep is one more awards over its lifetime than any other SUV brand, Jeep and the Jeep Grill or Registered Trademarks of FCA US LLC. Kitchen and bathroom professionals know what goes behind the tile matters. That's why trade pros trust Fiber cement, party backer board to keep tile firmly in place,
resist cracking, and help block moisture. Chosen in over 40 million kitchens and bathrooms, party backer board, what the best build on. Shop now at participating Home Depot, Lowe's, and floor into core stores. For more information, visit jameshardy.com slashhardybacker.
More episodes
More from CyberCode Academy

Course 43 - Practical Malware Development | Episode 8: Building a PHP Command an...
CyberCode Academy

Course 43 - Practical Malware Development | Episode 7: Building a PHP Session Co...
CyberCode Academy

Course 43 - Practical Malware Development | Episode 6: Database Foundations and...
CyberCode Academy

Course 43 - Practical Malware Development | Episode 5: Error Handling & HTTP Pol...
CyberCode Academy