灵感来源于:rwkgyg/sing-box-yg
目前各种 PaaS、SaaS、BaaS 等等平台,在用来托管一些常用的应用——如 Xray、Alist 等等应用时,经常会提示违规,不允许部署。在经过大量的测试后,我发现此类平台多以关键字检测作为封禁的因素,所以只要做到日志、文件名、文本文件内容不含此类关键字,就能继续部署,正常使用。
多数情况下,部署此类应用都是通过一个功能完善的 Shell 程序自动化进行,在其中把文件名和日志做好处理即可,但是最后 shell 脚本本身的内容又不可避免的会出现 Xray、Alist 等应用的下载链接,导致 Shell 脚本本身被平台查处封禁。当然通过多个变量拆分构造下载链接避开关键字也是一种处理方法,但是无疑这增加了脚本的编写复杂度,所以在通过查看 rwkgyg/sing-box-yg 时我获取了灵感,编写了一个 Python 脚本专门用于混淆 Shell 脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import osimport uuidimport base64with open ('script.sh' , 'r' ) as f: script = f.read() encoded_script = base64.b64encode(script.encode()).decode() chunk_size = 4 chunks = [encoded_script[i:i+chunk_size] for i in range (0 , len (encoded_script), chunk_size)] with open ('final_script.sh' , 'w' ) as f: variable_names = [] for chunk in chunks: while True : var_name = str (uuid.uuid4()).replace('-' , '' ) if not var_name.startswith('0' ) and not var_name.startswith('1' ) and not var_name.startswith('2' ) and not var_name.startswith('3' ) and not var_name.startswith('4' ) and not var_name.startswith('5' ) and not var_name.startswith('6' ) and not var_name.startswith('7' ) and not var_name.startswith('8' ) and not var_name.startswith('9' ): break variable_names.append(var_name) f.write(f'{var_name} =\'{chunk} \'\n' ) f.write('eval "$(echo -n "' ) for var_name in variable_names: f.write(f'${var_name} ' ) f.write('" | base64 --decode)"' ) os.chmod('final_script.sh' , 0o755 )
这个 Python 脚本的原理很简单,就是先把原 Shell 脚本使用 Base64 编码,然后分割成很多个字符串,再使用 UUID 作为变量名,把每个字符串都存储在一个变量中,最后把 UUID 变量全都连起来,使用 eval
命令执行。
简单粗暴,效果很好,用了的都说好。
第二个版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import base64import uuidimport redef generate_var_names (): chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' for first in chars: for second in chars: yield f"{first} {second} " while True : var_name = str (uuid.uuid4()).replace('-' , '' ) if re.match (r'^[a-zA-Z]' , var_name): yield var_name with open ('your_script.sh' , 'r' ) as file: script_content = file.read() first_encoded = base64.b64encode(script_content.encode('utf-8' )).decode('utf-8' ) first_eval_command = f'eval "$(echo -n "{first_encoded} " | base64 --decode)"' second_encoded = base64.b64encode(first_eval_command.encode('utf-8' )).decode('utf-8' ) var_names = generate_var_names() var_assignments = [] eval_parts = [] for i in range (0 , len (second_encoded), 4 ): chunk = second_encoded[i:i+4 ] var_name = next (var_names) var_assignments.append(f'{var_name} ="{chunk} "' ) eval_parts.append(f'${var_name} ' ) final_eval_command = f'eval "$(echo -n "{"" .join(eval_parts)} " | base64 --decode)"' var_assignment_line = ';' .join(var_assignments) with open ('obfuscated_script.sh' , 'w' ) as file: file.write(var_assignment_line + '\n' ) file.write(final_eval_command + '\n' ) print ("原脚本已混淆为 obfuscated_script.sh" )
配套解码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import osimport redef decode_script (script_content ): decoded_script = script_content while True : eval_match = re.search(r'eval\s*"\$\(\s*echo\s+-n\s+"([^"]+)"\s*\|\s*base64\s+--decode\s*\)"' , decoded_script) if not eval_match: break base64_string = eval_match.group(1 ) modified_script = decoded_script.replace('eval' , 'echo' , 1 ) result = os.popen(f'bash -c \'{modified_script} \'' ).read() print ("Decoded part:" , result) decoded_script = re.sub(r'eval\s*"\$\(\s*echo\s+-n\s+"([^"]+)"\s*\|\s*base64\s+--decode\s*\)"' , result, decoded_script, count=1 ) return result.strip() with open ('obfuscated_script.sh' , 'r' ) as file: obfuscated_script_content = file.read() deobfuscated_script_content = decode_script(obfuscated_script_content) with open ('deobfuscated_script.sh' , 'w' ) as file: file.write(deobfuscated_script_content) print ("混淆脚本已逆向生成 deobfuscated_script.sh" )