Making a wazuh server in Python from scratch for fun and maybe profit

souzo1 pts0 comments

Analyzing wazuh server and making wazuh server in python | MediumSitemapOpen in appSign up<br>Sign in

Medium Logo

Get app<br>Write

Search

Sign up<br>Sign in

Let’s analyzeAgent register enrollment — 1515

Agent Connection — 1514Wazuh network package<br>1: Get package length<br>2: Decrypt AES<br>3: Padding + Compressed Data<br>4: Read the decoded message

Control messages vs Logs<br>Answering the agent (the ACK)Putting it all together

Profit<br>My Social<br>Wazuh & Wazuh Ambassadors

Making a wazuh server in python from scratch for fun and maybe profit

Vinicius Morais

7 min read·<br>Just now

Listen

Share

Hi, souzo here, today I will show you how I created a wazuh server from scratch with python analysing wazuh agent request and wazuh server response.<br>I don’t see anyone doing this or trying to replicate the wazuh server protocol, then I made this.<br>This post have the intuit to guide users to extend wazuh and understand more about the wazuh protocol.<br>Consider becoming a wazuh ambassador.<br>Link to Wazuh Ambassadors program: Ambassadors Program | Wazuh.<br>Link to my project wazuh python server (wazoo)<br>https://github.com/souzomain/wazoo<br>Press enter or click to view image in full size

wazooLet’s analyze<br>Let’s analyze the wazuh agent (and internal) protocol.<br>Agent register enrollment — 1515<br>I created an server with SSL on port 1515 to receive the agent connection, then I received this:<br>Press enter or click to view image in full size

wazuh agent registration packageThis is the first wazuh agent connection made and got this request:<br>OSSEC A:'wazuh-agent-souzo-arch' V:'v4.14.5' G:'default'\nOSSEC agent messages field:<br>OSSEC maybe is the message ID<br>A: Looks like the agent name<br>V: Looks the wazuh agent version<br>G: Looks the group<br>\n It’s while the message finish<br>Before this, let’s send this message to wazuh server and see what they will send back.<br>Press enter or click to view image in full size

sending received wazuh agent package to serverServer has returned this:<br>OSSEC K:'001 wazuh-agent-souzo-arch any eec4b43c6411f38056d32e1b0c367f7fd21e0380777a9ff3bd29eaaddbeae8b4'closedLet’s analyze the message above:<br>OSSEC : The ossec header<br>K : This is the registration response<br>001 : the agent ID<br>wazuh-agent-souzo-arch : The agent name<br>any : is the restricted IP. any is all<br>eec4b43c6411f38056d32e1b0c367f7fd21e0380777a9ff3bd29eaaddbeae8b4 : It’s the connection password<br>The connection has closed before receive this, then I can presume this is the final and it will going to connect on 1514 using the connection password.<br>Agent Connection — 1514<br>Let’s analyze what the agent connection on 1514 do.<br>Wazuh network package<br>Following the wazuh message format, we can get a lot of interesting information without needing to read the code.<br>Press enter or click to view image in full size

wazuh secure messageThis image from wazuh talk exactly why we need to do.<br>1: Get package length<br>Wazuh agent send in the first 4 bytes the package length.<br>Press enter or click to view image in full size

package length2: Decrypt AES<br>First of all, take a look in the AES IV of the agent. Wazuh AES IV<br>This hardcoded “IV ”, allow wazuh agent to encrypt their communication with any wazuh server over the internet.<br>AES IV :<br>static unsigned char *iv = (unsigned char *)"FEDCBA0987654321";Wazuh github :

Before decrypting, we need two things out of the package:<br>who sent it and;<br>what to decrypt.<br>The agent frames every message like this:<br>!!#AES:So we scan for the two ! markers to pull the agent id, then everything after the#AES: tag is the ciphertext:<br>def parseMessageHeader(msg: bytes) -> tuple[int, bytes]:<br>"""Parse `!!#AES:` in a single pass, returning (agent_id, aes_data)."""<br>_start = msg.find(b"!")<br>_end = msg.find(b"!", _start + 1)<br>agent_id = int(msg[_start + 1 : _end])<br>aes_data = msg[_end + 1 + _aes_id_len :] # _aes_id_len == len(b"#AES:")<br>return agent_id, aes_datapWith the agent id we look up the “Agent Key” we saved back during the 1515 enrollment. But that key is not the AES key directly.<br>Wazuh derives the AES key by taking the MD5 of the agent key, and this is the part that trips everyone up.<br>It uses the hex digest string (32 ASCII chars), not the raw 16 MD5 bytes. That 32 byte string is exactly an AES-256 key:<br>def aes_key(self, key: str) -> bytes:<br>return hashlib.md5(key).hexdigest().encode() # 32 chars -> AES-256 keyNow we have everything: the ciphertext, the key, the mode (CBC) and the hardcoded IV. Decrypting is just one call:<br>hardcoded_wazuh_iv = b"FEDCBA0987654321"

def _decode(agent: WazuhAgent, msg: bytes) -> bytes:<br>cipher = AES.new(agent.aes_key, AES.MODE_CBC, iv=hardcoded_wazuh_iv)<br>return cipher.decrypt(msg)If the key is right, the bytes that come out are the ! padding followed by the zlib compressed body.<br>If the key is wrong, we get garbage that fails to decompress, a nice cheap way to reject a bad agent before doing any more work.<br>3: Padding + Compressed Data<br>Before decrypt the AES package, we can see the padding and the rest is the compressed data.<br>Let’s remove all “!” of the...

wazuh agent server package connection bytes

Related Articles