├── README.md └── signature_builder.rb /README.md: -------------------------------------------------------------------------------- 1 | Yara Signature Tool 2 | ============= 3 | 4 | Description 5 | ------------ 6 | This is a utility written in ruby using the Capstone Engine to build yara rules from functions. 7 | 8 | For now, the script will wildcard out calls and pushes that are referencing locations within the binary addr space. 9 | 10 | 11 | Requirements 12 | ------------ 13 | * Capstone Engine 14 | * Crabstone 15 | * PeDump 16 | * optparse 17 | 18 | Usage 19 | ------------ 20 | Run with the -h to get back the help (not much there so far) 21 | ``` 22 | Usage: signature_builder.rb -f ~/Desktop/wmiprivse.exe -o 0x401980 -e 0x401A08 23 | -f, --file FILE Filename 24 | -o, --offset NUMBER Beginning Offset 25 | -e, --end NUMBER Ending Offset 26 | -h, --help Show this message 27 | ``` 28 | Ending offset is optional, if you like you can leave it blank and the code will follow until a RETN is hit. The returned results printed in a form that you can copy and paste into an existing yara rule. 29 | ``` 30 | ./signature_builder.rb -f ~/Desktop/wmiprivse.exe -o 0x4017d3 -e 0x4017f9 31 | ``` 32 | Which will return 33 | ``` 34 | //6804010000 push 0x104 35 | //8D8C24A4000000 lea ecx, dword ptr [esp + 0xa4] 36 | //51 push ecx 37 | //8D54246C lea edx, dword ptr [esp + 0x6c] 38 | //6A08 push 8 39 | //52 push edx 40 | //E864310000 call 0x404950 41 | //83C408 add esp, 8 42 | //50 push eax 43 | //FF15D0904000 call dword ptr [0x4090d0] 44 | //85C0 test eax, eax 45 | //7516 jne 0x401810 46 | $a = {68 04 01 00 00 8D 8C 24 A4 00 00 00 51 8D 54 24 6C 6A 08 52 E8 ?? ?? ?? ?? 83 C4 08 50 FF ?? ?? ?? ?? ?? 85 C0 75 16 } 47 | 48 | 49 | ``` 50 | Status 51 | ------------ 52 | Alpha, still a lot of work to be done 53 | 54 | To be implemented 55 | ------------ 56 | * wildcarding of mov's 57 | * wildcarding of lea's 58 | * wildcarding of jmp's to absolute locations 59 | -------------------------------------------------------------------------------- /signature_builder.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'crabstone' 4 | require 'pedump' 5 | require 'optparse' 6 | 7 | include Crabstone 8 | 9 | ############### 10 | #You point this script at a function start addr and it'll build a signature starting at start addr and continuing until retn 11 | ############### 12 | 13 | def fetch_real_addr(offset) 14 | begin 15 | return @pe.dump.va2file(offset - @loadaddr) 16 | rescue Exception => err 17 | return nil 18 | end 19 | end 20 | 21 | def yara_format(instructions, rule) 22 | instructions.each {|line| puts "\s\s\s\s\s\s\s\s//#{line}"} 23 | print "\s\s\s\s\s\s\s\s$a = {" 24 | rule.scan(/../).each {|x| print x+"\s"} 25 | print "}" 26 | puts 27 | end 28 | 29 | options = Hash.new 30 | OptionParser.new do |opts| 31 | opts.banner = "Usage: signature_builder.rb -f ~/Desktop/wmiprivse.exe -o 0x401980 -e 0x401A08" 32 | opts.on("-f","--file FILE", "Filename") do |file| 33 | options[:file] = file 34 | end 35 | opts.on("-o","--offset NUMBER", "Beginning Offset") do |num| 36 | options[:offset] = num.hex.to_i 37 | end 38 | opts.on("-e","--end NUMBER", "Ending Offset") do |num| 39 | options[:end] = num.hex.to_i 40 | end 41 | opts.on("-h","--help", "Show this message") do 42 | puts opts 43 | exit 44 | end 45 | end.parse! 46 | 47 | if options[:file] and options[:offset] 48 | f = File.new(options[:file],'rb') 49 | file = f.read 50 | f.close 51 | @pe = PEdump.new(options[:file]).dump 52 | @loadaddr = @pe.dump.pe.ioh.ImageBase 53 | @max = @pe.pe.sections.map {|x| x['VirtualSize']+x['VirtualAddress']}.max 54 | start = fetch_real_addr(options[:offset]) 55 | if options[:end] 56 | if fetch_real_addr(options[:end]) #check to see if an error is thrown 57 | ending = fetch_real_addr(options[:end]) 58 | else 59 | ending = @max 60 | end 61 | else 62 | ending = @max 63 | end 64 | item = file[start..ending] 65 | signature = "" 66 | disasm = [] 67 | cs = Disassembler.new(ARCH_X86, MODE_32) 68 | cs.disasm(item,options[:offset]).each do |i| 69 | byte = i.bytes.first 70 | sig = "" 71 | case i.id 72 | #x86 Call 73 | when X86::INS_CALL 74 | if byte.eql?(0xff) 75 | #Call near, absolute indirect 76 | #FF15D0904000 call dword ptr [0x4090d0] 77 | #FFD0 call eax 78 | sig << "FF" 79 | (i.bytes.length - 1).times {|x| sig << "??"} 80 | elsif byte.eql?(0x9a) 81 | #Call far, absolute, address given in operand 82 | sig << "9A" 83 | (i.bytes.length - 1).times {|x| sig << "??"} 84 | else 85 | #Default case, E8 86 | #Call near, relative, displacement relative to next instruction 87 | #E864310000 call sub_404950 88 | sig << "E8" 89 | (i.bytes.length - 1).times {|x| sig << "??"} 90 | end 91 | 92 | #x86 push 93 | when X86::INS_PUSH 94 | if byte.eql?(0xff) 95 | #Push with dword to addr 96 | #FF35B0AF4100 push dword_41AFB0 97 | sig << sprintf("%02X",i.bytes.first) 98 | (i.bytes.length - 1).times {|x| sig << "??"} 99 | elsif byte.eql?(0x68) 100 | #push imm 101 | #6814954000 push offset szVerb 102 | #reverse the rest and see if they exist between imagebase and imagebase + max 103 | data = i.bytes[1..i.bytes.length].reverse.map {|x| sprintf("%02X",x) }.join.hex 104 | if data > @loadaddr && data < @loadaddr+@max 105 | sig << sprintf("%02X",i.bytes.first) 106 | (i.bytes.length - 1).times {|x| sig << "??"} 107 | else 108 | i.bytes.each {|x| sig << sprintf("%02X",x)} 109 | end 110 | else 111 | #Default case 112 | #6a Push Constant 113 | #6AFF push 0FFFFFFFFh 114 | #0x50 - 0x57 115 | #push esi/edi/eax/ecx ...... 116 | i.bytes.each {|x| sig << sprintf("%02X",x)} 117 | end 118 | when X86::INS_MOV 119 | #wildcard if it's pushing an address within our imagebase and imagebase + max 120 | #the easiest way atm to check for this is to check the length and look for a little endian 121 | #set of bytes that look like an addr 122 | if i.bytes.length >= 5 123 | #potential canidate for a mov 124 | #668B1504424100 mov dx, ds:word_414204 125 | #take the last 4 bytes for the and that should be our addr 126 | #the following example is should become 127 | #8B15FC414100 mov edx, dword ptr [0x4141fc] 128 | #8B0D00424100 mov ecx, dword ptr [0x414200] 129 | #8910 mov dword ptr [eax], edx 130 | #668B1504424100 mov dx, word ptr [0x414204] 131 | #53 push ebx 132 | #should become 133 | #8B 15 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 89 10 66 8B 15 ?? ?? ?? ?? 53 134 | #new use case 135 | #B933B14300 mov ecx, 0x43b133 136 | data = i.bytes[i.bytes.length-4..i.bytes.length].reverse.map {|x| sprintf("%02X",x) }.join.hex 137 | if data > @loadaddr && data < @loadaddr+@max 138 | i.bytes[0..(i.bytes.length-5)].each {|x| sig << sprintf("%02X",x)} 139 | 4.times {|x| sig << "??"} 140 | else 141 | i.bytes.each {|x| sig << sprintf("%02X",x)} 142 | end 143 | else 144 | i.bytes.each {|x| sig << sprintf("%02X",x)} 145 | end 146 | when X86::INS_RET 147 | break 148 | when 255..274 149 | #all of our jump cases, we'll wild card these including the actual jump 150 | i.bytes.length.times {|x| sig << "??"} 151 | else 152 | i.bytes.each {|x| sig << sprintf("%02X",x)} 153 | end 154 | disasm << "#{i.bytes.map {|x| sprintf("%02X",x)}.join}\t\t#{i.mnemonic}\t#{i.op_str}\n" 155 | signature += sig 156 | end 157 | end 158 | 159 | yara_format(disasm,signature) 160 | --------------------------------------------------------------------------------