diff --git a/.travis.yml b/.travis.yml index ad23bf6f8..7f3d79a46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ addons: - gcc-arm-none-eabi - libnewlib-dev - libbluetooth-dev + - python3-dev homebrew: packages: - readline diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 64cfca623..28735b9b7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -30,6 +30,7 @@ set(QT_PACKAGELIST set(Qt5_FOUND ON) foreach(_qt_package IN LISTS QT_PACKAGELIST) find_package(${_qt_package} QUIET ${QT_FIND_PACKAGE_OPTIONS}) + set(Qt5_LIBRARIES ${${_qt_package}_LIBRARIES} ${Qt5_LIBRARIES}) if(NOT ${_qt_package}_FOUND) set(Qt5_FOUND OFF) endif(NOT ${_qt_package}_FOUND) @@ -37,6 +38,8 @@ endforeach() find_package(PkgConfig) pkg_search_module(BLUEZ QUIET bluez) +pkg_search_module(PYTHON3 QUIET python3) +pkg_search_module(PYTHON3EMBED QUIET python3-embed) SET (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") @@ -188,7 +191,8 @@ add_custom_command( set(ADDITIONAL_SRC "") set(ADDITIONAL_LNK "") - +set(ADDITIONAL_DIRS "") +set(ADDITIONAL_LNKDIRS "") set(X86_CPUS x86 x86_64 i686) message(STATUS "CMAKE_SYSTEM_PROCESSOR := ${CMAKE_SYSTEM_PROCESSOR}") @@ -210,7 +214,7 @@ if (Qt5_FOUND) ${TARGET_SOURCES}) add_definitions("-DHAVE_GUI") - set(ADDITIONAL_LNK Qt5::Core Qt5::Widgets Qt5::Gui ${ADDITIONAL_LNK}) + set(ADDITIONAL_LNK ${Qt5_LIBRARIES} ${ADDITIONAL_LNK}) else (Qt5_FOUND) message("Qt5 library not found, not building gui") set(TARGET_SOURCES @@ -221,9 +225,23 @@ endif (Qt5_FOUND) if (BLUEZ_FOUND) message("Bluez library found, building native Bluetooth support :)") add_definitions("-DHAVE_BLUEZ") - set(ADDITIONAL_LNK bluetooth ${ADDITIONAL_LNK}) + set(ADDITIONAL_LNK ${BLUEZ_LIBRARIES} ${ADDITIONAL_LNK}) endif (BLUEZ_FOUND) +if (PYTHON3EMBED_FOUND) + message("Python3-embed library found, building with python3 support :)") + add_definitions("-DHAVE_PYTHON") + set(ADDITIONAL_DIRS ${PYTHON3EMBED_INCLUDE_DIRS} ${ADDITIONAL_DIRS}) + set(ADDITIONAL_LNK ${PYTHON3EMBED_LIBRARIES} ${ADDITIONAL_LNK}) + set(ADDITIONAL_LNKDIRS ${PYTHON3EMBED_LIBRARY_DIRS} ${ADDITIONAL_LNKDIRS}) +elseif (PYTHON3_FOUND) + message("Python3 library found, building with python3 support :)") + add_definitions("-DHAVE_PYTHON") + set(ADDITIONAL_DIRS ${PYTHON3_INCLUDE_DIRS} ${ADDITIONAL_DIRS}) + set(ADDITIONAL_LNK ${PYTHON3_LIBRARIES} ${ADDITIONAL_LNK}) + set(ADDITIONAL_LNKDIRS ${PYTHON3_LIBRARY_DIRS} ${ADDITIONAL_LNKDIRS}) +endif (PYTHON3EMBED_FOUND) + add_executable( proxmark3 ${TARGET_SOURCES} @@ -248,6 +266,7 @@ target_include_directories(proxmark3 PRIVATE ../common_fpga ../include src + ${ADDITIONAL_DIRS} ) if (APPLE) @@ -285,8 +304,17 @@ target_link_libraries(proxmark3 PRIVATE pm3rrg_rdv4_whereami ${ADDITIONAL_LNK}) +# OSX have a hard time compiling python3 dependency with older cmake. +if (PYTHON3EMBED_FOUND OR PYTHON3_FOUND) + if (NOT CMAKE_VERSION VERSION_LESS 3.13) + target_link_directories(proxmark3 PRIVATE ${ADDITIONAL_LNKDIRS}) + elseif (APPLE) + message( SEND_ERROR "Your CMAKE version is too old for Apple platform, please update to a version >=3.13" ) + endif() +endif() + install(TARGETS proxmark3 DESTINATION "bin") -install(DIRECTORY cmdscripts lualibs luascripts resources dictionaries DESTINATION "share/proxmark3") +install(DIRECTORY cmdscripts lualibs luascripts pyscripts resources dictionaries DESTINATION "share/proxmark3") add_custom_command(OUTPUT lualibs/pm3_cmd.lua COMMAND "awk -f pm3_cmd_h2lua.awk ../include/pm3_cmd.h > lualibs/pm3_cmd.lua" diff --git a/client/Makefile b/client/Makefile index 35adc71eb..52ccfc23f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -10,7 +10,7 @@ ROOT_DIR:=$(dir $(realpath $(lastword $(MAKEFILE_LIST)))) include ../Makefile.defs INSTALLBIN = proxmark3 -INSTALLSHARE = cmdscripts lualibs luascripts resources dictionaries +INSTALLSHARE = cmdscripts lualibs luascripts pyscripts resources dictionaries VPATH = ../common src vpath %.dic dictionaries @@ -200,13 +200,6 @@ endif LDLIBS += $(BTLIB) INCLUDES += $(BTLIBINC) -## Readline -ifeq ($(platform),Darwin) - LDLIBS += -L/usr/local/opt/readline/lib - INCLUDES += -I/usr/local/opt/readline/include -endif -LDLIBS += -lreadline - ## Math LDLIBS += -lm @@ -216,6 +209,28 @@ ifneq ($(SKIPPTHREAD),1) LDLIBS += -lpthread endif +## Python3 (optional) +ifneq ($(SKIPPYTHON),1) + PYTHONINCLUDES = $(shell $(PKG_CONFIG_ENV) pkg-config --cflags python3 2>/dev/null) + PYTHONLDLIBS = $(shell $(PKG_CONFIG_ENV) pkg-config --libs python3 2>/dev/null) + ifneq ($(PYTHONLDLIBS),) + PYTHONLIB = $(PYTHONLDLIBS) + PYTHONLIBINC = $(PYTHONINCLUDES) + PYTHON_FOUND = 1 + else + # since python3.8, applications willing to embed python must use -embed: + PYTHONINCLUDES = $(shell $(PKG_CONFIG_ENV) pkg-config --cflags python3-embed 2>/dev/null) + PYTHONLDLIBS = $(shell $(PKG_CONFIG_ENV) pkg-config --libs python3-embed 2>/dev/null) + ifneq ($(PYTHONLDLIBS),) + PYTHONLIB = $(PYTHONLDLIBS) + PYTHONLIBINC = $(PYTHONINCLUDES) + PYTHON_FOUND = 1 + endif + endif +endif +LDLIBS += $(PYTHONLIB) +INCLUDES += $(PYTHONLIBINC) + ## QT5 (or QT4 fallback) (optional) ifneq ($(SKIPQT),1) # Check for correctly configured Qt5 @@ -256,6 +271,13 @@ endif LDLIBS += $(QTLDLIBS) CXXINCLUDES += $(QTINCLUDES) +## Readline +ifeq ($(platform),Darwin) + LDLIBS += -L/usr/local/opt/readline/lib + INCLUDES += -I/usr/local/opt/readline/include +endif +LDLIBS += -lreadline + ####################################################################################################### CFLAGS ?= $(DEFCFLAGS) # We cannot just use CFLAGS+=... because it has impact on sub-makes if CFLAGS is defined in env: @@ -278,6 +300,11 @@ ifeq ($(BT_FOUND),1) PM3CFLAGS += -DHAVE_BLUEZ endif +ifeq ($(PYTHON_FOUND),1) + PM3CFLAGS += -DHAVE_PYTHON +endif + + CXXFLAGS ?= -Wall -Werror -O3 PM3CXXFLAGS = $(CXXFLAGS) PM3CXXFLAGS += -I../include @@ -345,6 +372,16 @@ else endif endif +ifeq ($(SKIPPYTHON),1) + $(info Python3 library: skipped) +else + ifeq ($(PYTHON_FOUND),1) + $(info Python3 library: Python3 v$(shell pkg-config --modversion python3) found, enabled) + else + $(info Python3 library: Python3 not found, disabled) + endif +endif + ifeq ($(SKIPWHEREAMISYSTEM),1) $(info Whereami library: local library forced) else diff --git a/client/pyscripts/findbits.py b/client/pyscripts/findbits.py new file mode 100755 index 000000000..809465a2e --- /dev/null +++ b/client/pyscripts/findbits.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +# findbits.py - find Binary, Octal, Decimal or Hex number in bitstream +# +# Adam Laurie +# http://rfidiot.org/ +# +# This code is copyright (c) Adam Laurie, 2009, All rights reserved. +# For non-commercial use only, the following terms apply - for all other +# uses, please contact the author: +# +# This code is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +import sys +import os + +# invert binary string +def invert(data): + return ''.join('0' if c == '1' else '1' for c in data) + +# do the actual search +def search(target,data): + location = data.find(target) + if location >= 0: + print('*** Match at bit {:d}: {}<{}>{}'.format(location, data[:location],target,data[location+len(target):])) + else: + print('Not found') + +# convert integer to binary string +def binstring(number): + return bin(number)[2:] if number > 0 else '' + +# reverse string order +def stringreverse(data): + return data[::-1] + +# match forward, backward and inverted +def domatch(binary,number): + reversed= stringreverse(number) + inverted= invert(binary) + + print(' Forward: (%s) ' % number, end = '') + search(binary,number) + print(' Reverse: (%s) ' % reversed, end = '') + search(binary,reversed) + print(' Inverse: (%s) ' % inverted) + print(' Forward: (%s) ' % number, end = '') + search(inverted,number) + print(' Reverse: (%s) ' % reversed, end = '') + search(inverted,reversed) + +def main(): + if(len(sys.argv) < 3): + print(""" +\t{0} - Search bitstream for a known number + +Usage: {0} + +\tNUMBER will be converted to it\'s BINARY equivalent for all valid +\tinstances of BINARY, OCTAL, DECIMAL and HEX, and the bitstream +\tand it\'s inverse will be searched for a pattern match. Note that +\tNUMBER must be specified in BINARY to match leading zeros. + +Example: + +\t{0} 73 0110010101110011 +""".format(sys.argv[0])) + os._exit(True) + + bases= { + 2:'BINARY', + 8:'OCTAL', + 10:'DECIMAL', + 16:'HEX', + } + + for base, base_name in sorted(bases.items()): + try: + number= int(sys.argv[1],base) + print('\nTrying ' + base_name) + # do BINARY as specified to preserve leading zeros + if base == 2: + domatch(sys.argv[1],sys.argv[2]) + else: + domatch(binstring(number),sys.argv[2]) + except: + continue + +if __name__ == '__main__': + main() diff --git a/client/pyscripts/ice.py b/client/pyscripts/ice.py new file mode 100644 index 000000000..9a223a059 --- /dev/null +++ b/client/pyscripts/ice.py @@ -0,0 +1,4 @@ +import os +import sys + +print("SP %s" % sys.path) diff --git a/client/pyscripts/parity.py b/client/pyscripts/parity.py new file mode 100644 index 000000000..6be9d979d --- /dev/null +++ b/client/pyscripts/parity.py @@ -0,0 +1,78 @@ +# This code is contributed by +# Shubham Singh(SHUBHAMSINGH10) +# 2020, modified (@iceman1001) + +import sys + +# Python3 program to illustrate Compute the +# parity of a number using XOR +# Generating the look-up table while pre-processing +def P2(n, table): + table.extend([n, n ^ 1, n ^ 1, n]) +def P4(n, table): + return (P2(n, table), P2(n ^ 1, table), + P2(n ^ 1, table), P2(n, table)) +def P6(n, table): + return (P4(n, table), P4(n ^ 1, table), + P4(n ^ 1, table), P4(n, table)) +def LOOK_UP(table): + return (P6(0, table), P6(1, table), + P6(1, table), P6(0, table)) + +# LOOK_UP is the macro expansion to generate the table +table = [0] * 256 +LOOK_UP(table) + +# Function to find the parity +def Parity(num) : + # Number is considered to be of 32 bits + max = 16 + + # Dividing the number o 8-bit + # chunks while performing X-OR + while (max >= 8): + num = num ^ (num >> max) + max = max // 2 + + # Masking the number with 0xff (11111111) + # to produce valid 8-bit result + return table[num & 0xff] + +def main(): + if(len(sys.argv) < 2): + print(""" + \t{0} - Calculate parity of a given number + + Usage: {0} <2,10,16> + + \t Specify type as in 2 Bin, 10 Decimal, 16 Hex, and number in that particular format + \t number can only be 32bit long. + + Example: + + \t{0} 10 1234 + + Should produce the output: + + \tOdd parity\n""".format(sys.argv[0])) + return 0 + + + numtype= int(sys.argv[1], 10) + print("numtype: {0}".format(numtype)) + input= int(sys.argv[2], numtype) + print("num: {0} 0x{0:X}".format(input)) + + #num = "001111100010100011101010111101011110" + # Result is 1 for odd parity + # 0 for even parity +# result = Parity( int(input, numtype) ) + result = Parity(input) + print("Odd parity") if result else print("Even parity") + + +if __name__ == "__main__": + main() + + + diff --git a/client/pyscripts/pm3_eml2mfd.py b/client/pyscripts/pm3_eml2mfd.py new file mode 100755 index 000000000..90803cf5e --- /dev/null +++ b/client/pyscripts/pm3_eml2mfd.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +''' +# Andrei Costin , 2011 +# pm3_eml2mfd.py +# Converts PM3 Mifare Classic emulator EML text file to MFD binary dump file +''' + +import sys +import binascii + +def main(argv): + argc = len(argv) + if argc < 3: + print('Usage:', argv[0], 'input.eml output.mfd') + return 1 + + with open(argv[1], "r") as file_inp, open(argv[2], "wb") as file_out: + for line in file_inp: + line = line.rstrip('\n').rstrip('\r') + print(line) + data = binascii.unhexlify(line) + file_out.write(data) + +if __name__ == '__main__': + main(sys.argv) diff --git a/client/pyscripts/pm3_eml_mfd_test.py b/client/pyscripts/pm3_eml_mfd_test.py new file mode 100755 index 000000000..8fae5965e --- /dev/null +++ b/client/pyscripts/pm3_eml_mfd_test.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +from tempfile import mkdtemp +from shutil import rmtree +from string import hexdigits +import unittest, os +import pm3_eml2mfd, pm3_mfd2eml + +class TestEmlMfd(unittest.TestCase): + def setUp(self): + self.tmpdir = mkdtemp() + + def tearDown(self): + rmtree(self.tmpdir) + + EML2MFD_TESTCASES = [ + ('', ''), + ("41424344\r\n45464748\n494A4B4C\n", "ABCDEFGHIJKL") + ] + def test_eml2mfd(self): + self.three_argument_test(pm3_eml2mfd.main, self.EML2MFD_TESTCASES) + + def test_mfd2eml(self): + self.three_argument_test(pm3_mfd2eml.main, + map(reversed, self.EML2MFD_TESTCASES), c14n=hex_c14n) + + def three_argument_test(self, operation, cases, c14n=str): + for case_input, case_output in cases: + try: + inp_name = os.path.join(self.tmpdir, 'input') + out_name = os.path.join(self.tmpdir, 'output') + with open(inp_name, 'w') as in_file: + in_file.write(case_input) + operation(['', inp_name, out_name]) + with open(out_name, 'r') as out_file: + self.assertEqual(c14n(case_output), c14n(out_file.read())) + finally: + for file_name in inp_name, out_name: + if os.path.exists(file_name): + os.remove(file_name) + + +def hex_c14n(inp): + """ + Canonicalizes the input string by removing non-hexadecimal + characters and making everything uppercase + """ + return ''.join(c.upper() for c in inp if c in hexdigits) + +if __name__ == '__main__': + unittest.main() diff --git a/client/pyscripts/pm3_mfd2eml.py b/client/pyscripts/pm3_mfd2eml.py new file mode 100755 index 000000000..ae7a79825 --- /dev/null +++ b/client/pyscripts/pm3_mfd2eml.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +''' +# Andrei Costin , 2011 +# pm3_eml2mfd.py +# Converts PM3 Mifare Classic MFD binary dump file to emulator EML text file +''' +import sys + +READ_BLOCKSIZE = 16 + +def main(argv): + argc = len(argv) + if argc < 3: + print('Usage:', argv[0], 'input.mfd output.eml') + return 1 + + with open(argv[1], "rb") as file_inp, open(argv[2], "w") as file_out: + while True: + byte_s = file_inp.read(READ_BLOCKSIZE) + if not byte_s: + break + hex_char_repr = byte_s.hex() + file_out.write(hex_char_repr) + file_out.write("\n") + +if __name__ == '__main__': + main(sys.argv) diff --git a/client/pyscripts/xorcheck.py b/client/pyscripts/xorcheck.py new file mode 100755 index 000000000..f4605a5e7 --- /dev/null +++ b/client/pyscripts/xorcheck.py @@ -0,0 +1,51 @@ +# xorcheck.py - find xor values for 8-bit LRC +# +# Adam Laurie +# http://rfidiot.org/ +# +# This code is copyright (c) Adam Laurie, 2009, All rights reserved. +# For non-commercial use only, the following terms apply - for all other +# uses, please contact the author: +# +# This code is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 2020, Modified (@iceman1001) + +import sys + +def main(): + if(len(sys.argv) < 3): + print(""" + \t{0} - Generate final byte for XOR LRC + + Usage: {0} ... + + \tSpecifying the bytes of a UID with a known LRC will find the last byte value + \tneeded to generate that LRC with a rolling XOR. All bytes should be specified in HEX. + + Example: + + \t{0} 04 00 80 64 ba + + Should produce the output: + + \tTarget (BA) requires final LRC XOR byte value: 5A\n""".format(sys.argv[0])) + return 0 + + target= int(sys.argv[len(sys.argv) - 1],16) + + lrc= 0x00 + for i in range(len(sys.argv) - 1): + lrc ^= int(sys.argv[i + 1],16) + print('\nTarget (%02X) requires final LRC XOR byte value: %02X\n' % (target,lrc)) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/client/src/cmdscript.c b/client/src/cmdscript.c index 4f4a372d8..38c980765 100644 --- a/client/src/cmdscript.c +++ b/client/src/cmdscript.c @@ -7,10 +7,19 @@ //----------------------------------------------------------------------------- // Some lua scripting glue to proxmark core. //----------------------------------------------------------------------------- +// 2020, added Python support (@iceman100) + #include #include +#ifdef HAVE_PYTHON +//#define PY_SSIZE_T_CLEAN +#include +#include +#endif + + #include "cmdparser.h" // command_t #include "scripting.h" #include "comms.h" @@ -24,8 +33,98 @@ #include "ui.h" #include "fileutils.h" +typedef enum { + PM3_UNSPECIFIED, + PM3_LUA, + PM3_CMD, +#ifdef HAVE_PYTHON + PM3_PY +#endif +} pm3_scriptfile_t; + static int CmdHelp(const char *Cmd); +#ifdef HAVE_PYTHON + +#define PYTHON_LIBRARIES_WILDCARD "?.py" + +static int split(char *str, char **arr) { + int begin_index = 0; + int word_cnt = 0; + + while (1) { + while (isspace(str[begin_index])) { + ++begin_index; + } + if (str[begin_index] == '\0') { + break; + } + int end_index = begin_index; + while (str[end_index] && !isspace(str[end_index])) { + ++end_index; + } + int len = end_index - begin_index; + char *tmp = calloc(len + 1, sizeof(char)); + memcpy(tmp, &str[begin_index], len); + arr[word_cnt++] = tmp; + begin_index = end_index; + } + return word_cnt; +} + +static void set_python_path(char *path) { + PyObject *syspath = PySys_GetObject("path"); + if (syspath == 0) { + PrintAndLogEx(WARNING, "Python failed to getobject"); + } + + PyObject *pName = PyUnicode_FromString(path); + if (PyList_Insert(syspath, 0, pName)) { + PrintAndLogEx(WARNING, "Error inserting extra path into sys.path list"); + } + + if (PySys_SetObject("path", syspath)) { + PrintAndLogEx(WARNING,"Error setting sys.path object"); + } +} + +static void set_python_paths(void) { + //--add to the LUA_PATH (package.path in lua) + // so we can load scripts from various places: + const char *exec_path = get_my_executable_directory(); + if (exec_path != NULL) { + // from the ./luascripts/ directory + char scripts_path[strlen(exec_path) + strlen(PYTHON_SCRIPTS_SUBDIR) + strlen(PYTHON_LIBRARIES_WILDCARD) + 1]; + strcpy(scripts_path, exec_path); + strcat(scripts_path, PYTHON_SCRIPTS_SUBDIR); +// strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD); + set_python_path(scripts_path); + } + + const char *user_path = get_my_user_directory(); + if (user_path != NULL) { + // from the $HOME/.proxmark3/luascripts/ directory + char scripts_path[strlen(user_path) + strlen(PM3_USER_DIRECTORY) + strlen(PYTHON_SCRIPTS_SUBDIR) + strlen(PYTHON_LIBRARIES_WILDCARD) + 1]; + strcpy(scripts_path, user_path); + strcat(scripts_path, PM3_USER_DIRECTORY); + strcat(scripts_path, PYTHON_SCRIPTS_SUBDIR); +// strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD); + set_python_path(scripts_path); + + } + + if (exec_path != NULL) { + // from the $PREFIX/share/proxmark3/luascripts/ directory + char scripts_path[strlen(exec_path) + strlen(PM3_SHARE_RELPATH) + strlen(PYTHON_SCRIPTS_SUBDIR) + strlen(PYTHON_LIBRARIES_WILDCARD) + 1]; + strcpy(scripts_path, exec_path); + strcat(scripts_path, PM3_SHARE_RELPATH); + strcat(scripts_path, PYTHON_SCRIPTS_SUBDIR); +// strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD); + set_python_path(scripts_path); + } +} +#endif + /** * Generate a sorted list of available commands, what it does is * generate a file listing of the script-directory for files @@ -36,7 +135,15 @@ static int CmdScriptList(const char *Cmd) { int ret = searchAndList(LUA_SCRIPTS_SUBDIR, ".lua"); if (ret != PM3_SUCCESS) return ret; - return searchAndList(CMD_SCRIPTS_SUBDIR, ".cmd"); + + ret = searchAndList(CMD_SCRIPTS_SUBDIR, ".cmd"); + if (ret != PM3_SUCCESS) + return ret; +#ifdef HAVE_PYTHON + return searchAndList(PYTHON_SCRIPTS_SUBDIR, ".py"); +#else + return ret; +#endif } /** @@ -54,9 +161,28 @@ static int CmdScriptRun(const char *Cmd) { int arg_len = 0; static uint8_t luascriptfile_idx = 0; sscanf(Cmd, "%127s%n %255[^\n\r]%n", preferredName, &name_len, arguments, &arg_len); + if (strlen(preferredName) == 0) { + PrintAndLogEx(FAILED, "no script name provided"); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`script list`") " to see available scripts"); + return PM3_EINVARG; + } + char *extension_chk; + extension_chk = str_dup(preferredName); + str_lower(extension_chk); + pm3_scriptfile_t ext = PM3_UNSPECIFIED; + if (str_endswith(extension_chk, ".lua")) { + ext = PM3_LUA; + } else if (str_endswith(extension_chk, ".cmd")) { + ext = PM3_CMD; + } +#ifdef HAVE_PYTHON + else if (str_endswith(extension_chk, ".py")) { + ext = PM3_PY; + } +#endif char *script_path = NULL; - if ((!str_endswith(preferredName, ".cmd")) && (searchFile(&script_path, LUA_SCRIPTS_SUBDIR, preferredName, ".lua", true) == PM3_SUCCESS)) { + if (((ext == PM3_LUA) || (ext == PM3_UNSPECIFIED)) && (searchFile(&script_path, LUA_SCRIPTS_SUBDIR, preferredName, ".lua", true) == PM3_SUCCESS)) { int error; if (luascriptfile_idx == MAX_NESTED_LUASCRIPT) { PrintAndLogEx(ERR, "too many nested scripts, skipping %s\n", script_path); @@ -112,7 +238,7 @@ static int CmdScriptRun(const char *Cmd) { return PM3_SUCCESS; } - if ((!str_endswith(preferredName, ".lua")) && (searchFile(&script_path, CMD_SCRIPTS_SUBDIR, preferredName, ".cmd", true) == PM3_SUCCESS)) { + if (((ext == PM3_CMD) || (ext == PM3_UNSPECIFIED)) && (searchFile(&script_path, CMD_SCRIPTS_SUBDIR, preferredName, ".cmd", true) == PM3_SUCCESS)) { PrintAndLogEx(SUCCESS, "executing Cmd " _YELLOW_("%s"), script_path); PrintAndLogEx(SUCCESS, "args " _YELLOW_("'%s'"), arguments); @@ -124,18 +250,106 @@ static int CmdScriptRun(const char *Cmd) { return ret; } + /* + For apt (Ubuntu, Debian...): + sudo apt-get install python3-dev # for python3.x installs + + For yum (CentOS, RHEL...): + sudo yum install python3-devel # for python3.x installs + + For dnf (Fedora...): + sudo dnf install python3-devel # for python3.x installs + + For zypper (openSUSE...): + sudo zypper in python3-devel # for python3.x installs + + For apk (Alpine...): + + # This is a departure from the normal Alpine naming + # scheme, which uses py2- and py3- prefixes + + sudo apk add python3-dev # for python3.x installs + + For apt-cyg (Cygwin...): + apt-cyg install python3-devel # for python3.x installs + */ + +#ifdef HAVE_PYTHON + + if (((ext == PM3_PY) || (ext == PM3_UNSPECIFIED)) && (searchFile(&script_path, PYTHON_SCRIPTS_SUBDIR, preferredName, ".py", true) == PM3_SUCCESS)) { + + PrintAndLogEx(SUCCESS, "executing python " _YELLOW_("%s"), script_path); + PrintAndLogEx(SUCCESS, "args " _YELLOW_("'%s'"), arguments); + + wchar_t *program = Py_DecodeLocale(preferredName, NULL); + if (program == NULL) { + PrintAndLogEx(ERR, "could not decode " _YELLOW_("%s"), preferredName); + free(script_path); + return PM3_ESOFT; + } + + // optional but recommended + Py_SetProgramName(program); + Py_Initialize(); + + //int argc, char ** argv + char *argv[128]; + int argc = split(arguments, argv); + wchar_t *py_args[argc]; + py_args[0] = Py_DecodeLocale(preferredName, NULL); + for (int i = 0; i < argc; i++) { + py_args[i+1] = Py_DecodeLocale(argv[i], NULL); + } + + PySys_SetArgv(argc+1, py_args); + + // clean up + for (int i = 0; i < argc; ++i) { + free(argv[i]); + } + + // setup search paths. + set_python_paths(); + + FILE *f = fopen(script_path, "r"); + if (f == NULL) { + PrintAndLogEx(ERR, "Could open file " _YELLOW_("%s"), script_path); + free(script_path); + return PM3_ESOFT; + } + + PyRun_SimpleFileExFlags(f, preferredName, 1, NULL); + Py_Finalize(); + PyMem_RawFree(program); + free(script_path); + PrintAndLogEx(SUCCESS, "\nfinished " _YELLOW_("%s"), preferredName); + return PM3_SUCCESS; + } +#endif + // file not found, let's search again to display the error messages int ret = PM3_EUNDEF; - if (!str_endswith(preferredName, ".cmd")) ret = searchFile(&script_path, LUA_SCRIPTS_SUBDIR, preferredName, ".lua", false); - if (!str_endswith(preferredName, ".lua")) ret = searchFile(&script_path, CMD_SCRIPTS_SUBDIR, preferredName, ".cmd", false); + if (ext == PM3_LUA) + ret = searchFile(&script_path, LUA_SCRIPTS_SUBDIR, preferredName, ".lua", false); + else if (ext == PM3_CMD) + ret = searchFile(&script_path, CMD_SCRIPTS_SUBDIR, preferredName, ".cmd", false); +#ifdef HAVE_PYTHON + else if (ext == PM3_PY) + ret = searchFile(&script_path, PYTHON_SCRIPTS_SUBDIR, preferredName, ".py", false); + else if (ext == PM3_UNSPECIFIED) + PrintAndLogEx(FAILED, "Error - can't find %s.[lua|cmd|py]", preferredName); +#else + else if (ext == PM3_UNSPECIFIED) + PrintAndLogEx(FAILED, "Error - can't find %s.[lua|cmd]", preferredName); +#endif free(script_path); return ret; } static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"help", CmdHelp, AlwaysAvailable, "Usage info"}, {"list", CmdScriptList, AlwaysAvailable, "List available scripts"}, - {"run", CmdScriptRun, AlwaysAvailable, " -- Execute a script"}, + {"run", CmdScriptRun, AlwaysAvailable, " -- execute a script"}, {NULL, NULL, NULL, NULL} }; @@ -147,7 +361,11 @@ static command_t CommandTable[] = { */ static int CmdHelp(const char *Cmd) { (void)Cmd; // Cmd is not used so far - PrintAndLogEx(NORMAL, "This is a feature to run Lua-scripts. You can place Lua-scripts within the luascripts/-folder. "); +#ifdef HAVE_PYTHON + PrintAndLogEx(NORMAL, "This is a feature to run Lua/Cmd/Python scripts. You can place scripts within the luascripts/cmdscripts/pyscripts folders. "); +#else + PrintAndLogEx(NORMAL, "This is a feature to run Lua/Cmd scripts. You can place scripts within the luascripts/cmdscripts folders. "); +#endif return PM3_SUCCESS; } diff --git a/client/src/fileutils.c b/client/src/fileutils.c index a650e3929..d5315e3b9 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -1446,6 +1446,7 @@ static int searchFinalFile(char **foundpath, const char *pm3dir, const char *sea (strcmp(LUA_LIBRARIES_SUBDIR, pm3dir) == 0) || (strcmp(LUA_SCRIPTS_SUBDIR, pm3dir) == 0) || (strcmp(CMD_SCRIPTS_SUBDIR, pm3dir) == 0) || + (strcmp(PYTHON_SCRIPTS_SUBDIR, pm3dir) == 0) || (strcmp(RESOURCES_SUBDIR, pm3dir) == 0))) { char *path = calloc(strlen(exec_path) + strlen(pm3dir) + strlen(filename) + 1, sizeof(char)); if (path == NULL) diff --git a/client/src/ui.c b/client/src/ui.c index 798ed165a..cc256d8e3 100644 --- a/client/src/ui.c +++ b/client/src/ui.c @@ -207,6 +207,8 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { strncpy(prefix, _BLUE_("[#] "), sizeof(prefix) - 1); break; case HINT: + strncpy(prefix, _YELLOW_("[?] "), sizeof(prefix) - 1); + break; case SUCCESS: strncpy(prefix, _GREEN_("[+] "), sizeof(prefix) - 1); break; diff --git a/doc/md/Development/Maintainers.md b/doc/md/Development/Maintainers.md index 0ea33b924..293fd3999 100644 --- a/doc/md/Development/Maintainers.md +++ b/doc/md/Development/Maintainers.md @@ -61,6 +61,7 @@ It's also possible to skip parts even if libraries are present in the compilatio * `make client SKIPQT=1` to skip GUI even if Qt is present * `make client SKIPBT=1` to skip native Bluetooth support even if libbluetooth is present +* `make client SKIPPYTHON=1` to skip embedded Python 3 interpreter even if libpython3 is present * `make client SKIPLUASYSTEM=1` to skip system Lua lib even if liblua5.2 is present, use embedded Lua lib instead * `make client SKIPJANSSONSYSTEM=1` to skip system Jansson lib even if libjansson is present, use embedded Jansson lib instead * `make client SKIPWHEREAMISYSTEM=1` to skip system Whereami lib even if libwhereami is present, use embedded whereami lib instead diff --git a/include/common.h b/include/common.h index 4d6efcce2..9f04d75c7 100644 --- a/include/common.h +++ b/include/common.h @@ -24,6 +24,7 @@ #define PM3_USER_DIRECTORY PATHSEP ".proxmark3" PATHSEP // PM3 subdirectories: +#define PYTHON_SCRIPTS_SUBDIR "pyscripts" PATHSEP #define CMD_SCRIPTS_SUBDIR "cmdscripts" PATHSEP #define DICTIONARIES_SUBDIR "dictionaries" PATHSEP #define LUA_LIBRARIES_SUBDIR "lualibs" PATHSEP