local getopt = require('getopt')

copyright = ''
author = 'Kenzy Carey'
version = ''
desc = [[

  .-----------------------------------------------------------------.
 /  .-.                                                         .-.  \
|  /   \                    BruteSim                           /   \  |
| |\_.  |     (bruteforce simulation for multiple tags)       |    /| |
|\|  | /|                      by                             |\  | |/|
| `---' |                 Kenzy Carey                         | `---' |
|       |                                                     |       |
|       |-----------------------------------------------------|       |
\       |                                                     |       /
 \     /                                                       \     /
  `---'                                                         `---'

*SUPPORTED TAGS: pyramid, awid, fdx, jablotron, noralsy, presco, visa2000, 14a, hid

This script uses the proxmark3 implementations of simulation to bruteforce given ranges of id.
It uses both LF and HF simulations.
																							  
    -- Author note
    -- I wrote this as i was doing a PACS audit. This is far from complete, but is easily expandable.
    -- The idea was based on proxbrute, but i needed more options, and support for different readers.
    -- I dont know LUA, so I used Brian Redbeards lf_bulk_program.lua script as a starting point, sorry if its kludgy.

]]
example = [[
    --  (the above example would bruteforce pyramid tags, starting at 10:1000, ending at 10:991, and waiting 1 second between each card)

    script run brutesim -r pyramid -f 10 -b 1000 -c 10 -t 1 -d down
]]
usage = [[

script run brutesim -r rfid_tag -f facility_code -b base_card_number -c count -t timeout -d direction

Arguments:
	-h          this help
	-r      	*see below		RFID Tag: the RFID tag to emulate
             pyramid
             awid
             fdx
             jablotron
             noralsy
             presco
             visa2000
             14a
             hid

	-f		0-999                facility code (dfx: country id, 14a: type)
	-b		0-65535              base card number to start from
	-c		1-65536			     number of cards to try
	-t		.0-99999, pause		 timeout between cards (use the word 'pause' to wait for user input)
	-d		up, down             direction to move through card numbers
]]

local DEBUG = true
local bor = bit32.bor	
local bxor = bit32.bxor	
local lshift = bit32.lshift
--- 
-- A debug printout-function
local function dbg(args)
    if type(args) == "table" then
		local i = 1
		while args[i] do
			dbg(args[i])
			i = i+1
		end
	else
		print("###", args)
	end	
end
--- 
-- This is only meant to be used when errors occur
local function oops(err)
	print("ERROR: ",err)
	return nil,err
end
--- 
-- Usage help
local function help()
	print(copyright)
	print(version)	
	print(desc)
	print("Example usage")
	print(example)
end
--
-- Exit message
function exitMsg(msg)
	print( string.rep('--',20) )
	print( string.rep('--',20) )
	print(msg)
	print()
end
--
-- Check if a string is empty
local function isempty(s)
	return s == nil or s == ''
end

-- The code below was blatantly stolen from Brian Redbeard's lf_bulk_program.lua script		
local function toBits(num, bits)
	bits = bits or math.max(1, select(2, math.frexp(num)))
	local t = {}
	for b = bits, 1, -1 do
		t[b] = math.fmod(num, 2)
		num = math.floor((num - t[b]) / 2)
	end
	return table.concat(t)
end
--
-- check for parity in bit-string. 
local function evenparity(s)
	local _, count = string.gsub(s, "1", "")
	local p = count % 2
	if (p == 0) then
		return false
	end
	return true
end
--
-- 	calcs hex for HID 
local function cardHex(i, f)
	fac = lshift(f, 16)
	id = bor(i, fac)
	stream = toBits(id, 26)
	high = evenparity(string.sub(stream, 0, 12)) and 1 or 0
	low = not evenparity(string.sub(stream, 13)) and 1 or 0
	bits = bor(lshift(id, 1), low)
	bits = bor(bits, lshift(high, 25))
	preamble = bor(0, lshift(1, 5))
	bits = bor(bits, lshift(1, 26))
	return ("%04x%08x"):format(preamble, bits)
end
--
--
local function main(args)

	print( string.rep('--',20) )
	print( string.rep('--',20) )	
	print()

	for o, a in getopt.getopt(args, 'r:f:b:c:t:d:h') do			-- Populate command like arguments
		if o == 'r' then rfidtag = a end
		if o == 'f' then facility = a end
		if o == 'b' then baseid = a end
		if o == 'c' then count = a end
		if o == 't' then timeout = a end
		if o == 'd' then direction = a end
		if o == 'h' then return print(usage) end
	end

	-- Check to see if -r argument was passed 
	if isempty(rfidtag) then
		print("You must supply the flag -r (rfid tag)")
		print(usage)
		return
	end

	-- Check what RFID Tag we are using
	if rfidtag == 'pyramid' then
		consolecommand = 'lf pyramid sim'			-- set the console command
		rfidtagname = 'Farpointe/Pyramid'			-- set the display name
		facilityrequired = 1						-- set if FC is required
	elseif rfidtag == 'awid' then
		consolecommand = 'lf awid sim'
		rfidtagname = 'AWID'
		facilityrequired = 1
	elseif rfidtag == 'fdx' then					-- I'm not sure why you would need to bruteforce this ¯\_(ツ)_/¯ 
		consolecommand = 'lf fdx sim'
		rfidtagname = 'FDX-B'
		facilityrequired = 1
	elseif rfidtag == 'jablotron' then
		consolecommand = 'lf jablotron sim'
		rfidtagname = 'Jablotron'
		facilityrequired = 0
	elseif rfidtag == 'noralsy' then
		consolecommand = 'lf noralsy sim'
		rfidtagname = 'Noralsy'
		facilityrequired = 0
	elseif rfidtag == 'presco' then
		consolecommand = 'lf presco sim d'
		rfidtagname = 'Presco'
		facilityrequired = 0
	elseif rfidtag == 'visa2000' then
		consolecommand = 'lf visa2000 sim'
		rfidtagname = 'Visa2000'
		facilityrequired = 0
	elseif rfidtag == '14a' then
		consolecommand = 'hf 14a sim'
		if facility == "1" then rfidtagname = 'MIFARE Classic'		-- Here we use the -f option to read the 14a type instead of the facility code
		elseif facility == "2" then rfidtagname = 'MIFARE Ultralight'
		elseif facility == "3" then rfidtagname = 'MIFARE Desfire'
		elseif facility == "4" then rfidtagname = 'ISO/IEC 14443-4'
		elseif facility == "5" then rfidtagname = 'MIFARE Tnp3xxx'
		else 
			print("Invalid 14a type (-f) supplied. Must be 1-5")
			print(usage)
			return
		end
		facilityrequired = 0						-- Disable the FC required check, as we used it for type instead of FC
	elseif rfidtag == 'hid' then
		consolecommand = 'lf hid sim'
		rfidtagname = 'HID'
		facilityrequired = 1
	else									-- Display error and exit out if bad RFID tag was supplied
		print("Invalid rfid tag (-r) supplied")
		print(usage)
		return
	end
	
	if isempty(baseid) then 						-- Display error and exit out if no starting id is set
		print("You must supply the flag -b (base id)")
		print(usage)
		return
	end

	if isempty(count) then 							-- Display error and exit out of no count is set
		print("You must supply the flag -c (count)")
		print(usage)
		return
	end
	
	if facilityrequired == 1 then						-- If FC is required
		facilitymessage = " - Facility Code: "				-- Add FC to status message
		if isempty(facility) then 					-- If FC was left blank, display warning and set FC to 0 
			print("Using 0 for the facility code as -f was not supplied")
			facility = 0 						
		end
	else									-- If FC is not required
		facility = ""							-- Clear FC
		facilitymessage = ""						-- Remove FC from status message
	end
	
	if isempty(timeout) then 						-- If timeout was not supplied, show warning and set timeout to 0
		print("Using 0 for the timeout as -t was not supplied")
		timeout = 0 
	end
	
	if isempty(direction) then 						-- If direction was not supplied, show warning and set direction to down
		print("Using down for direction as -d was not supplied")
		direction = 'down' 
	end
	
	if tonumber(count) < 1 then
		print("Count -c must be set to 1 or higher")
		return
	else
		count = count -1 						-- Make our count accurate by removing 1, because math
	end							
	
	if direction == 'down' then 						-- If counting down, set up our for loop to count down
		endid = baseid - count
		fordirection = -1
	elseif direction == 'up' then						-- If counting up, set our for loop to count up
		endid = baseid + count
		fordirection = 1
	else									-- If invalid direction was set, show warning and set up our for loop to count down
		print("Invalid direction (-d) supplied, using down")
		endid = baseid - count
		fordirection = -1
	end
	

	
	-- display status message
	print("")									
	print("BruteForcing "..rfidtagname..""..facilitymessage..""..facility.." - CardNumber Start: "..baseid.." - CardNumber End: "..endid.." - TimeOut: "..timeout)
	print("")
	
	-- loop through for each count (-c)
	for cardnum = baseid, endid, fordirection do
		
		-- If rfid tag is set to HID, convert card to HEX using the stolen code above 
		if rfidtag == 'hid' then cardnum = cardHex(cardnum, facility) end
		
		-- send command to proxmark
		core.console(consolecommand..' '..facility..' '..cardnum)

		if timeout == 'pause' then			
			print("Press enter to continue ...")
			io.read()
		else
			os.execute("sleep "..timeout.."")
		end
	end
	
	-- ping the proxmark to stop emulation and see if its still responding		
	core.console('hw ping')
end

main(args)