Лёгкая интеграция tor в android приложение на примере клиента для рутрекера

Оригинал поста на хабре

Мне давно было интересно, можно ли легко добавить проксирование через тор в Android приложение. Вроде бы довольно очевидная задача, плюс тор браузеры уже под эту платформу давно есть… Но есть много задач, которые сложнее, чем кажутся. Для нетерпеливых сразу скажу - да, можно, и получается довольно легко, быстро и классно. В особенности если не копать с нуля, а воспользоваться моими наработками.

Для примера я буду использовать приложение для работы с рутрекером - никто не любит код, который работает со сферический конём в вакууме. Раньше это приложение обходило блокировку при помощи Google Compression Proxy - но увы - то ли рутрекер, то ли гугл выпилил возможность авторизации с этой проксёй. Сразу скажу, что, конечно, есть всякие впны и прочее, что вы используете для лёгкого обхода блокировки и просмотра сериальчиков. Но речь здесь идёт не про это. Как вы понимаете, тор можно использовать в мобильном приложении для огромного количества вещей - например, для доступа к веб сайтам в .onion или для реализации особо безопасного мессенджера.

Как подключить библиотеку для работы с Тором

Как собрать с нуля

Если вас не интересует сборка с нуля, то сразу перейдите к следующему заголовку.

Итак, что у нас есть на эту тему из готового инструментария. Есть особый репозиторий от неких ребят под предводительством Microsoft (ссылка в подвале). Вроде бы у них всё работало - но качество и механизм сборки просто ужасают. А ещё репозиторий устарел на два года. И скомпилированный версии библиотеки там нет, есть только довольно стрёмные инструкции по тому, как собрать её самостоятельно (в стиле - “я делал так, не знаю почему, но без этого ничего не работало”). Впрочем, имеющихся инструкций вполне достаточно для того, чтобы обновить код до актуального состояния и исправить все странные косяки.

  1. Клонируем себе этот репозиторий.
  2. Обновляем там компонент, который отвечает за управление тором - jtorctl. Они использовали форк основного репозитория с правками от briar, но эти правки уже включены в основной репозиторий, так что лучше взять с основного. Можно подключать из maven репозитория, но я такие вещи обычно забираю исходниками - можно сразу посмотреть, прогнать анализ и править на лету баги - проект-то довольно сырой, несмотря на возраст.
  3. Обновляем geoip и geoip6 — базы данных блоков IP-адресов с привязкой к географическому положению каждого блока для версий IPv4 и IPv6 соответственно. Для этого скачиваем на сайте тора windows expert bundle.
  4. Обновляем сам тор (то есть нативную библиотеку). Стандартной общедоступной нет (ну или я плохо искал) - так что идём к ребятам, которые разрабатывают тор и тор браузер под андроид (Orbot и Orfox), берём последний релиз их Orbot и вынимаем оттуда библиотеку. Тор там довольно свежий, что приятно.
  5. Правим руками всё, что перестало компилироваться в нашем проекте. Несколько функций в зависимых библиотеках изменились, но в целом всё интуитивно понятно и поправимо за 5 минут.
  6. Следуя рекомендациям ридми нашего проекта, создаём локальные мавен репозитории и строим из кучи кусков наш проект. Кстати, обратите внимание, что билд скрипт настолько кривой, что в одном месте включает в себя предыдущий релиз себя же. Жуть. Так что рекомендую переписать его заново, простым и понятным языком, чтобы получить на выходе обыкновенную библиотеку aar.

Как собрать из моих наработок

Пункты 1-6 я уже сделал за вас, так что просто соберите библиотеку из моего репозитория, или скачайте её в секции релизов. Ссылка будет в “подвале” поста. Однако обращаю внимание, что правильным будет проверить код и библиотеки на соответствие оригинальным и отсутствие закладок. Не стоит такие вещи добавлять вслепую в свои приложения.

Как перестать волноваться и начать проксировать через тор

Сначала нужно включить тор:
1
2
3
4
5
6
7
8
9
10
int totalSecondsPerTorStartup = 4 * 60;
int totalTriesPerTorStartup = 5;
try {
boolean ok = onionProxyManager.startWithRepeat(totalSecondsPerTorStartup, totalTriesPerTorStartup);
if (!ok)
Log.e("TorTest", "Couldn't start Tor!");
}
catch (InterruptedException | IOException e) {
e.printStackTrace();
}

Затем подождать, пока он подцепится:

1
2
while (!onionProxyManager.isRunning())
Thread.sleep(90);

Если всё прошло успешно - ура, он слушает у нас localhost на каком-то случайном порту:

1
Log.v("My App", "Tor initialized on port " + onionProxyManager.getIPv4LocalHostSocksPort());

Но это пока не всё. У нас теперь есть тор, который слушает порт в качестве Socks4a прокси. Однако далеко не все стандартные библиотеки умеют работать с Socks4a. Там из соображений анонимности требуется, чтобы резолв хоста происходил на прокси, а не ранее. Не знаю, какие из стандартных библиотек это умеют, и у меня был код, написанный с Apache HttpComponents. Я уже писал ранее, почему их можно использовать, да и данный пост не про то. Если вы хотите, то можете реализовать то же самое на любой другой библиотеке.

Итак, для использования httpComponents нам нужно переписать ConnectionSocketFactory и SSLConnectionSocketFactory.

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
41
42
43
44
45
46
47
48
49
50
public class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {

public MySSLConnectionSocketFactory(final SSLContext sslContext) {
super(sslContext);
}

@Override
public Socket createSocket(final HttpContext context) throws IOException {
return new Socket();
}

@Override
public Socket connectSocket(
int connectTimeout,
Socket socket,
final HttpHost host,
final InetSocketAddress remoteAddress,
final InetSocketAddress localAddress,
final HttpContext context) throws IOException {
Args.notNull(host, "HTTP host");
Args.notNull(remoteAddress, "Remote address");
InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
socket = new Socket();
connectTimeout = 100000;
socket.setSoTimeout(connectTimeout);
socket.connect(new InetSocketAddress(socksaddr.getHostName(), socksaddr.getPort()), connectTimeout);
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
outputStream.write((byte) 0x04);
outputStream.write((byte) 0x01);
outputStream.writeShort((short) host.getPort());
outputStream.writeInt(0x01);
outputStream.write((byte) 0x00);
outputStream.write(host.getHostName().getBytes());
outputStream.write((byte) 0x00);

DataInputStream inputStream = new DataInputStream(socket.getInputStream());
if (inputStream.readByte() != (byte) 0x00 || inputStream.readByte() != (byte) 0x5a) {
throw new IOException("SOCKS4a connect failed");
} else
Log.v("SSLConnectionSF", "SOCKS4a connect ok!");
inputStream.readShort();
inputStream.readInt();

SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createLayeredSocket(socket, host.getHostName(), host.getPort(), context);
prepareSocket(sslSocket);
return sslSocket;
}

}
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
41
42
public class MyConnectionSocketFactory implements ConnectionSocketFactory {

@Override
public Socket createSocket(final HttpContext context) throws IOException {
return new Socket();
}

@Override
public Socket connectSocket(
int connectTimeout,
Socket socket,
final HttpHost host,
final InetSocketAddress remoteAddress,
final InetSocketAddress localAddress,
final HttpContext context) throws IOException, ConnectTimeoutException {

InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
socket = new Socket();
connectTimeout = 100000;
socket.setSoTimeout(connectTimeout);
socket.connect(new InetSocketAddress(socksaddr.getHostName(), socksaddr.getPort()), connectTimeout);


DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
outputStream.write((byte) 0x04);
outputStream.write((byte) 0x01);
outputStream.writeShort((short) host.getPort());
outputStream.writeInt(0x01);
outputStream.write((byte) 0x00);
outputStream.write(host.getHostName().getBytes());
outputStream.write((byte) 0x00);

DataInputStream inputStream = new DataInputStream(socket.getInputStream());
if (inputStream.readByte() != (byte) 0x00 || inputStream.readByte() != (byte) 0x5a) {
throw new IOException("SOCKS4a connect failed");
} else
Log.v("SSLConnectionSF", "SOCKS4a connect ok!");
inputStream.readShort();
inputStream.readInt();
return socket;
}
}

Использовать эти фабрики легко и просто. Для этого нужно создать HttpClient, который использует эти библиотеки:

1
2
3
4
5
6
7
8
9
10
11
public HttpClient getNewHttpClient() {

Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new MyConnectionSocketFactory())
.register("https", new MySSLConnectionSocketFactory(SSLContexts.createSystemDefault()))
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
return HttpClients.custom()
.setConnectionManager(cm)
.build();
}

И указать ему наш прокси сервер:

1
2
3
4
5
HttpClient cli = getNewHttpClient();
int port = onionProxyManager.getIPv4LocalHostSocksPort();
InetSocketAddress socksaddr = new InetSocketAddress("127.0.0.1", port);
HttpClientContext context = HttpClientContext.create();
context.setAttribute("socks.address", socksaddr);

Всё, теперь мы можем использовать тор так же, как если бы делали обыкновенные запросы. Более того, мы можем так же обращаться и к веб сайтам .onion.

Результат

Получившийся код я использовал в своём приложении для рутрекера. Да, инициализация тора занимает около 20 секунд, и страницы грузятся не так быстро - но зато мы гарантированно проходим блокировку. А все ресурсы, которые не блокированы, подгружаются через обычное соединение. Можно было бы остальные ресурсы пропускать через Google Compression Proxy, но многие жаловались, что у них заблокирован этот прокси - так что я не стал этого делать. Конечно, в приложении можно было бы ещё много всего сделать - например, кэшировать статику на телефоне для экономии трафика и более быстрой работы - но это не столь критично, да и приложение я писал скорее для примера.

Заключение

Тор на андроиде - классная и удобная штука, которая достаточно работает, и её действительно можно использовать в своих приложениях. Кстати да, есть гораздо более лёгкий способ это делать - просто требовать установки Orbot, который сам поднимет вам тор. Но мне не нравятся зависимости одних приложений от других, да и 3 лишних мегабайта не так критичны в размере приложения. Так что если кому понравилось моё решение - используйте, делайте пулл реквесты, и да пребудет с вами свобода.

Ссылки:

  1. Исходная библиотека;
  2. Моя сборка библиотеки;
  3. Приложение для рутрекера;
  4. Guardian Project - ребята, которым мы обязаны наличием нативной тор библиотеки.

О чём молчит Google и почему вам стоит использовать Apache HttpComponents в Android

Оригинал поста на хабре

Эту статью нужно было публиковать гораздо раньше (почти на шесть лет), сэкономив тем самым Android разработчикам огромное количество месяцев бессмысленной разработки - но увы, не всегда есть на это время.

Введение

Если вы разрабатываете под Android, то наверняка сталкивались с тем, что открываете вы своё приложение, которое отлично работало несколько лет, и тут внезапно оказывается, что Apache httpComponents стали deprecated, и их не рекомендуется использовать. Сначала давайте разберём, что же произошло, а потом сделаем выводы, что делать.

Что произошло

Слишком далеко закапываться я не стал, однако много интересных вещей можно получить из рассылки org.apache.hc.dev и джиры Apache. Например, факт что:
Android включал в себя старый pre-beta релиз библиотеки
Более того, на протяжении всей истории версия библиотеки не менялась, и если включаете у себя apache legacy, то это всё та же pre-beta.

Подтверждение можно прочитать тут.

“Подтверждение на английском:

1
Google Android 1.0 was released with a pre-BETA snapshot of Apache HttpClient. To coincide with the first Android release Apache HttpClient 4.0 APIs had to be frozen prematurely, while many of interfaces and internal structures were still not fully worked out. As Apache HttpClient 4.0 was maturing the project was expecting Google to incorporate the latest code improvements into their code tree. Unfortunately it did not happen. Version of Apache HttpClient shipped with Android has effectively become a fork. Eventually Google decided to discontinue further development of their fork while refusing to upgrade to the stock version of Apache HttpClient citing compatibility concerns as a reason for such decision. As a result those Android developers who would like to continue using Apache HttpClient APIs on Android cannot take advantage of newer features, performance improvements and bug fixes.
Google просто забил на проблему
Разработчики из Apache активно работали над библиотекой, но назначенные им менеджеры Google не отвечали месяцами, менялись, а в конце концов сказали, что это не приоритетная задача, пользователям хватает старой версии, да и вообще надо выкинуть эту библиотеку. В подтверждение - немного из упомянутого мной листа рассылки (работа началась в 2008 году, а в 2010 внезапно проявился новый менеджер):

Новый менеджер признаёт, что задаче не уделяют внимания… Ну и ладно.

1
2
Jesse Wilson commented on HTTPCLIENT-898:
I'm Bob's successor on the Android team. If you've got questions about our use of the HTTP client code, I'll do my best to answer 'em. I regret that we haven't given this code much attention lately; that said I'm happy that it hasn't really needed it.

Тогда же прозвенел первый тревожный звоночек:

Знаете, спустя два года мы решили, что ничего не надо делать.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
I'm quite sorry that Android included an unreleased rev of Apache HTTP client, and I suspect we'll be paying for that mistake for quite a while.

Because of the strict compatibility requirements of our platform, we will be unable to make forwards-incompatible API changes to the HTTP code. Unlike your desktop and serverside users, including the API as a part of the core platform significantly reduces our ability to make API changes.

These days we aren't spending much time on the HTTP client code. Our users seem to be mostly satisfied with the ancient version in the SDK, and have been directing their complaints elsewhere (crypto, locales, XML...).

That said, we do want to figure out a long term strategy for an HTTP client API that will work for both us and the Apache HTTP client team. We're considering a variety of options...

- Discouraging our users from using the built-in Apache HTTP client, preferring the JDK's own URLConnection classes. Whether this is feasible depends mostly on whether the new APIs in Java 6 (which we're working on) will satisfy the use cases that Apache HTTP client has covered for years.

- Replacing the Apache HTTP client API with a 3rd API in a "com.google" or "android." package. Such an API would likely be based on parts of your own code, but with a more limited API.

- Tidying up the version of Apache HTTP that we're already stuck with. This includes deprecating APIs that shouldn't have ever been exposed as public, and possibly filling in any gaps using newer code from your project.

But none of this work is particularly urgent since we're actively fighting fires in other areas of the core libraries.

Они не могут производит изменения в коде существующей библиотеки из-за несовместимостей? Oh rly? А если вынесут это в свой отдельный пакет на основе кода Apache, то внезапно смогут? Вообще, breaking changes в Android это отдельная тема, выходящая за рамки данной статьи, но чего стоит только ограничение разрешений приложений в шестёрке… При этом ребята из Apache активно старались предоставить максимальную совместимость, и было готовы сделать для этого что угодно. Но увы.

Ребят, у нас мало времени, мы взяли вашу разработку и забили на неё, не беспокойте нас по пустякам.

1
I explained on several earlier occasions that Android doesn't allow binary incompatibilities of any kind (not my rule). I understand that the HttpClient team is more tolerant of binary incompatibilities. While I'm not saying it would be impossible to make these changes in Android, I am saying that it would take a lot of convincing (and time), it would annoy other people who are time-constrained and who have higher priorities, and it could likely fail anyway.

Финал

А дальше библиотеку просто без всяких адекватных объяснений выпилили с официальной рекомендацией использовать HttpUrlConnection. Вот хотя бы что-то, отдалённо похожее на объяснение ситуации от Джесси Вилсона, который был на то время в Dalvik team (к слову, он же и был вторым менеджером Google по связям с Apache. Запомните это имя).

В его статье вы можете увидеть, что среди преимуществ он видит:

  • Кеширование (которое, если надо, реализуется где угодно)
  • Малый вес библиотеки (да, но сомнительный аргумент)
  • Компрессия, которая сохраняет батарейку и ускоряет загрузку (да, но при желании мы можем использовать Google Compression Proxy и через Apache HttpComponents как обычный прокси)
Крайне сомнительные объяснения. Если вы сейчас сидите в шапочке из фольги, то рациональным доводом будет то, что гугл таким образом решил втихую заставить пользователей гонять весь свой трафик через свой прокси… UPD. Как мне справедливо указали, под компрессией имеется в виду не использование GCP, а использование стандартного gzip... Который у Apache есть уже много-много лет. Так что тем более странно.

К сожалению, большинство разработчиков слепо верят Google и сразу считают, что библиотека Apache “плохая”, и нужно бежать выкидывать её из своего кода.

Разработчики из Apache прокомментировали это кратко:

Google has used the project when it suited their goals and screwed us afterwards. There is nothing we can do about it.

Эпилог c OkHttp

Если вы разрабатываете под Android, то наверняка видели рекомендации заменять Apache HttpComponents ныне популярной библиотекой OkHttp от Square.

А вы всё ещё помните милого парня Джесси Вилсона из Dalvik team? А вы знаете, что сейчас он работает в Square? И именно он является создателем OkHttp? Более того, вы знате, что OkHttp начинался как форк куска AOSP (Android Open Source Project), который в свою очередь брал свой код из Apache Harmony?

Так что это и есть по сути создание форка Apache с последующим выкидыванием оригинала из обращения (второй вариант из озвученных ранее Джесси в общении с Apache). Звучит довольно гнусно, не правда ли? Единственное что непонятно - была ли это инициатива Google или самого Джесси. Но поступил он крайне некрасиво, выкинув конкурентов с помощью Google и придя весь в белом со своим решением.

И что же?

Давайте разберёмся, какие есть варианты того, как жить дальше.

1. Использовать HttpUrlConnection Рекомендованный подход Google. Действительно имеет смысл, если у вас простое приложение. Но не дай бог вам попробовать сделать что-то не совсем тривиальное. В моей практике таких случая было два - когда я пытался использовать SSL прокси и когда хотел обратиться к некоему айпишнику со своим именем хоста. Обе задачи при помощи HttpUrlConnection реализовать невозможно.

2. Использовать другие продвинутые альтернативы Например, тот же OkHttp. Сам его не пробовал, но говорят, что библиотека хорошая. Однако, вы потратите много времени на переписывание хорошего готового имеющегося кода (если у вас уже есть приложение). Ну и касательно именно OkHttp - я бы не стал использовать столь неприятно пахнущий форк.

3. Использовать legacy библиотеку Да, вы можете продолжать использовать всю ту же древнюю бету, что и раньше. Но зачем? Как быстрое затыкание дырки это решение неплохое, а если нет… То конечно нет. Обидно, что именно такой ответ является наиболее популярным решением на том же stack overflow - люди просто не понимают, что они используют версию библиотеки от 2008 года.

4. Использовать последние версии Apache HttpComponents В плюсах имеем то, что мы продолжаем использовать всё тот же код, не тратя время на переписывание и на изучение новой библиотеки и её багов. Более того, код написанный на HttpComponents будет у вас работать где угодно. Библиотека чудовищно мощная и позволяет вам сделать действительно что угодно. Вопрос - как её подключать? Если просто брать и подставлять в gradle, то выйдет конфликт классов с этой самой древней версией. В релизе 4.3 разработчики apache предлагали использовать специальные постфиксы “HC4” в классах для избегания конфликтов, но работало это как-то очень плохо. Зато к релизу 4.5 они уже рекомендуют другой, единственно логичный выход - использовать перепакованную под другим пространством классов библиотеку, собранную неким товарищем на гитхабе (ссылка ниже). Там, правда, на самом деле версия 4.4, а не 4.5 - но это не так принципиально. А если вас волнует, что вы используете собранную непонятно кем библиотеку (хотя она довольно популярна), то вы всегда можете собрать её сами из исходников. На данный момент я считаю это наиболее правильным выходом из сложившейся дурной ситуации (и сам делаю именно так).

Что дальше?

Заметок по использованию пятой версии библиотеки на Android пока что нету - возможно, это объясняется тем, что она пока ещё в альфа версии. Или просто в Apache решили больше не иметь дела с Google и Android. Впрочем, даже если так - всегда найдутся энтузиасты, которые смогут корректно перепаковать последнюю версию. А работать с ней - сплошное удовольствие.

Ссылки

  1. Репозиторий с перепакованной версией httpComponents;
  2. Заметки о релизе 4.5, где рекомендуют использовать этот репозиторий;
  3. Интересные фрагменты переписки ребят из Apache и Google;
  4. Любопытные задачи с комментариями в джире Apache - раз и два.
В процессе написания статьи я общался на эту тему с разработчиком из Apache, который подтвердил мои предположения, но от греха подальше упоминать его здесь не буду.

Хроника взлома Pokemon Go

Оригинал поста на хабре

Введение от переводчика

Перевёл эту историю, так как её посчитали интересной в моей предыдущей статье по поводу взаимоотношений Niantic и разработчиков всякого дополнительного софта. Эта милая история не претендует на самый великий и профессиональный взлом всех времён и народов, но позволяет окунуться в уютную атмосферу open source сообщества, занятого общим делом. Возможно, кто-то вспомнит свои прошлые дни... Автор оригинала признаётся, что он не слишком хорошо разбирается в разработке, но это возможно и к лучшему - хорошо передаётся общая атмосфера без лишних технических деталей. Если вас интересуют именно технические детали, то вы можете походить по ссылкам в тексте или просто разобрать получившееся в итоге API. Да, вас может смутить, что в тексте порой говорится то о создании API, то о его взломе. Имеются в виду два разных API - API самой игры и API, которое делают разработчики для управления игрой извне. Придётся различать их по контексту. Да, так же следует учитывать, что обновления писались в спешке и порой усталым и сонным человеком - некоторые формулировки будут звучать странно. Так же прошу прощение за обилие ссылок, часть из которых уже нерабочая. Формат реддита не очень соответствует формату статьи, но мне показалось, что это будет интересно.

Предыстория

API перестало принимать запросы от всех источниках кроме обновлённого официального клиента. Теперь ему требуется "неизвестная 6", которая была и в более ранних версиях API, но не проверялась. На данный момент только официальный клиент может создать корректную "неизвестную 6". Мы не до конца уверены, что дело в "неизвестной 6”, но очень похоже на то - это фактический единственный большой кусок данных, который мы не можем пока воссоздать.

Мы не можем просто так взять и сравнить версии приложения до и после обновления, потому что эта переменная уже была в предыдущих версиях API, но сервер её не проверял.

Само итоговое значение переменной неважно, и простой подбор не поможет - нам нужно не значение, а способ генерации “неизвестной 6”.

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

История

04/08/2016 - 00:49: Видимо, изменилась логика и протокол. 04/08/2016 - 01:37: Протокол не изменился, новых хешей не появилось. Возможно, теперь запросы подписываются какими-то криптографическими методами, но мы пока не уверены. 04/08/2016 - 02:07: Становится очевидно, что изменение нетривиально, и реверс инжиниринг может занять много времени. 04/08/2016 - 08:08: Все работают над определением “неизвестной 6”. Все известные данные пока собраны тут. 04/08/2016 - 15:06: Мы подтверждаем, что “неизвестная 6” ответственна за произошедшие изменения. 5 August 2016, 14:00 - Прорыв? Разработчики, кажется, нашли, где генерируется “неизвестная 6”. Теперь нам нужно воссоздать процесс и надеяться на то, что всё заработает. И на то, что именно “неизвестная 6” нам всё сломала. 14:30 - Канал в discord ушёл в приватный режим, потому что наш прорыв присваивают себе другие люди. Меня тоже выкинули из discord, так что апдейтов видимо больше не будет. (16:20) Меня пустили обратно. Мы теперь уверены, что дело именно в “неизвестной 6”. Это славно, иначе вся работа по её воссозданию была бы ненужной. Мы на верном пути. 16:30 - Очень много сложной работы и непонятных слов. Насколько я понял, теперь мы знаем, где создаётся большая часть “неизвестной 6”. Теперь надо понять входные данные для генерации и способы дальнейшего шифрования этой неизвестной. Работы предстоит ещё довольно много... 18:00 - Мы разобрали часть шифрования, и можем теперь частично понять исходные данные. Как только мы разберём всё шифрование, мы сможем повторить его процесс, прогнав через декомпилированный код. 19:30 - Мы ещё на шаг ближе... 20:30 - Прорыв #2: Мы связали вместе два куска кода, отвечающих за генерацию “неизвестной 6”. Мы нашли где вызывается функция шифрования. Как я говорил ранее, теперь у нас есть декомпилированная функция шифрования. 21:15 - Нам осталось сделать две вещи: Привести декомпилированную версию шифрования в человеческий вид. Это кастомное шифрование, и декомпилированный код занял бы около 200 странц текста… Люди работают над этим - медленно, но верно. Не самая сложная часть работы, но трудоёмкая. Определить оставшиеся входные данные. Это может быть довольно трудно… Осталось 3-4 поля, и мы радуемся каждому маленькому прорыву. 22:30 - Никаких новостей, кроме того, что "они работают", но я подумал, что хорошо бы что-то написать про последние сутки. Безумно круто наблюдать за работой программистов с реддита и их совместными усилиями по взлому “неизвестной 6”. К слову, это та самая неизвестная, на которой “сломались” хакеры Ингресса. Но коммьюнити разработчиков Pokemon Go гораздо больше. Некоторые работают над взломом уже 20 часов из тех 24, что произошли с момента изменения API. /u/keyphact уже не спал 40 часов (чувак, серьёзно - пойди поспи). Люди работают целеустремлённо и без устали. Я чувствую, что мы можем это сделать.

Мы нашли ядро создания “неизвестной 6” за какие-то несколько часов. Функции шифрования были декомпилированы и были найдены места их вызовов. Осталось 10% входных данных и нормальная реализация функций шифрования. Но так близко, но столь далеко. Справимся ли мы?

23:30 - Мы поняли большую часть шифрования. Однако до сих не поняли, как хранятся входные данные (протобуффер), это безумно сложно. Это требуется, чтобы опередить оставшиеся входные данные. 01:30 - У нас есть полностью рабочее шифрование! (хотя мы его так до конца и не осознали!) Это можно считать прорывом #3. Осталось справиться с протобуффером.

Это безумно интересно и познавательно и для меня тоже. Я с трудом понимаю, чем занимаются разработчики. Например, что такое “формат протобуффера”? Мне сказали, что оно где-то между входными данными и шифрованием. Оно берёт входные данные, формирует из них некий пакет и отсылает на шифрование.

Теперь у нас есть шифрование, но мы не можем определить входные данные, потому что мы не знаем, как из них формируется пакет. Так что мы делаем свой формат пакета (протобуффера). Постепенно, шаг за шагом… Надеюсь, когда мы разберёмся с протобуффером, то входные данные прояснятся. 03:30 - Никаких особых новостей. Мы работаем и продвигаемся.

Следует упомянуть, что обеспечить работоспособность сообщества было безумно сложной задачей. Это был кошмар без всякого логичного решения, и у модераторов не было на него никакого “хорошего” ответа. Начиналось всё легко и просто - открытый канал на discord, где все могли работать вместе над починкой API. Но чуть позже стало ясно, что всё не так легко и просто…

Количество людей на канале росло и росло. Что в свою очередь приводило к огромному количеству спама, вроде “Когда же будет готово API?” или “Что случилось с API?”. Работать стало невозможно, и нам пришлось ограничить права на постинг сообщений в канал.

Что ещё хуже - некоторые представляли нашу работу как свою собственную и давали сообществу ложные обещания относительно того, когда будет готово новое API. А модераторов спамили запросами на право постинга в канал. Мы решили полностью скрыть канал.

Мы пытались создать второй канал, в котором проверять пригодность для общей работы. Но там в итоге начались те же проблемы, что и в первом канале. А люди сердились на модераторов за то, что у них больше не было доступа к материалам первого канала, и им приходилось повторять ту работу, которую мы уже давно завершили. Сейчас мы опять двигаемся к прозрачности. Мы сделали первый канал снова доступным всем для чтения. Надеюсь, никто не злоупотребит этим снова. Мы так же сделали открытый для всех гитхаб и вики.

Какие ещё выводы можем сделать из этого урока? Да никаких, наверное. Я просто хочу поблагодарить модераторов за безумное количество работы, которое они проделали, чтобы решить эту нерешаемую ситуацию.

А теперь я пойду спать. Мы открыли канал для обновлений API , поскольку сообщения в основном канале несколько осложнены для понимания всякими техническими терминами. Вернусь к вам завтра.

5 August 2016, 13:00 - Вот вам подробное техническое описание нашей текущей проблемы. 13:30 - Никаких особых новостей, мы работаем работу. Есть некоторый прогресс с протобуффером, мы вычленили несколько полей, но осталось пока довольно много непонятных данных. Мы так же разбираем функции шифрования, чтобы полностью осознать, как оно происходит. Реверс инжиниринг это сложная и утомительная работа Так же я хочу ответить на несколько комментариев. Маленький FAQ: Q: Я считаю, что я могу помочь, как это сделать? A: К сожалению, основной канал закрыт для новых разработчиков. Но вы можете помочь в нашем публичном репозитории и делать пулл реквесты. Q: Разработчики должны попробовать x. A: Понятия не имею, о чём вы, но они уже наверняка это пробовали. Если вы уверены, что у вас действительно прорывная идея - отпишите её где-нибудь на discord. 15:30 - Никаких особых новостей, всё ещё работаем работу.

У нас появился новый тред на реддите. Там есть больше технических деталей, в которых вы вряд ли разберётесь, если вы не программист. Если вы не видите обновлений, то это означает, что разработчики активно работают. Мы так же подняли твиттер, в котором всё несколько более понятно - я буду писать туда при каждом обновлении, как только мне дадут туда доступ.

Мы так же сделали инвайты на discord постоянными, их срок жизни больше не должен истекать, скрестил пальцы. Мы хотим держать вас в курсе и избежать появления фейковых твиттер аккаунтов. Я достиг лимита по длине текста. Дальше обновления будут тут.

5 August 2016, 16:00 - мы определили ещё одно поле входных данных! Круто видеть наконец какой-то прогресс. Не надейтесь на решение вот прямо сейчас, у нас осталось ещё одно поле, но мы над ним уже работаем. 17:00 - Мы проверили поле, которое я упоминал ранее - да, всё отлично! Настроение у всех отличное, мы продвигаемся. 18:00 - Думаем, что последнее поле как-то связано с тем полем, которое мы только что взломали. Надеюсь, это как-то поможет. 18:30 - Мы хотим ещё раз повторить, что сообщество взлома API не занимается поддержкой ботов. Мы просто ломаем API. Так что хотим подтвердить, что Niantic может проследить любые MITM приложения, например, те, которые модифицируют броски покеболлов чтобы они всегда были точными. Если вы используете такое приложение, Niantic об этом скорее всего уже знает.

Мы не знаем, будут ли вас за это банить, мы просто удостоверились в том, что Niantic может (теоретически) это определять. Но это нас не заботит, мы занимаемся взломом API.

20:00 - Никаких особых новостей на фронте разработки. Всё ещё работаем над оставшимися входными данными. Мы приспосабливаемся к множеству каналов, через которые общаемся с вами. У нас есть Discord, Twitter, Reddit, этот пост и гитхаб репозитории. Они просто разрываются от сообщений. Однако, внутренняя коммуникация у нас налажена гораздо лучше. Настройка и поддержка взаимодействия порой долга и трудоёмка, но это увлекательно. Приятно знать, что разработчики могут спокойно заниматься своим делом, взламывая API. 23:30 - Я вернулся и жду обновлений, чтобы перевести их с программистского на человеческий. Мы ушли уже далеко, но до конечного пункта назначения ещё дальше. 00:45 - Прогресс, сделанный за последние часы, можно назвать прорывом #4. Мы раскрыли ещё 3 входных поля. Одно из полей является шифрованной (вернее, хэшированной) версией тикета авторизации, в комбинации с GPS положением получается второе поле. Третье тоже как-то связано с тикетом авторизации. “Комбинация” это сильное упрощение того, что там на самом деле. Реальная сложность работы программистов сильно превыше моего уровня понимания. Сейчас мы работаем над оставшимся полем. Или полями. GMT 03:30 - Мы особо ничего ни писали, так как прогресса довольно мало. Мы уже 12 часов пытаемся взломать одно из полей. Мы знаем больше, чем когда начинали, но прорыва пока не было. Мы знаем, что поле не комбинируется с токеном авторизации, однако зависит от сессии (возможно, не напрямую). Мы так же знаем его длину (16 байт). Мы работаем над уточнением информации. Прямо сейчас большинство разработчиков всё же ушли спать. Надо сказать, это заслуженный отдых. Пойду и я посплю.

6 august 2016, 13:00 - Так, теперь это будут чисто мои комментарии. Это единственный настоящий источник информации, остальные это подделки. Теперь я буду говорить Я про себя и ОНИ про разработчиков. Это решение было принято, чтобы убрать лишнее давление на разработчиков.

Пока я спал, ничего особо не произошло, возможно потому, что разработчики тоже спали. Поле, которое доставило нам столько геморроя, заслуживает собственного имени, теперь это будет “неизвестная 22”. Дополнительную сложность доставляет то, что оно привязано к сессии, и данные собирать довольно сложно. Мы разбираем это поле и ещё одно. 14:30 - Никаких новостей, просто хотел ответить на вопрос “как же до сих пор ничего не получилось? Вы же говорили, что осталось 3-4 поля, а взломали уже гораздо больше!”

На самом деле, эти 3-4 поля оказались комбинацией из множества других полей, которые тоже нужно взломать. Нельзя ответить на вопрос, сколько же полей осталось. Мы не хотим создавать ложных ожиданий. Да и не знаем. 17:00 - Прорыв #5: Разработчики выяснили, что “неизвестная 22” не нужна. Один из них ответил на это ожидаемым “Да твою ж мать!”. Сейчас разработчики пытаются собрать демо приложение для проверки этой гипотезы. Они очень взволнованы и молятся. Надо заметить, что даже если вызов API будет успешен, это всего лишь прототип, а не исправление API. Разработчики забили на довольно много полей - например, на поле, которое передаётся только с Android устройств. Чтобы его обойти, разработчики эмулируют использование iOS. Нужно сделать ещё много, чтобы это не работало так стрёмно. 17:30 - Первые версии прототипа не работают.

18:00 - Никаких новостей, я просто хотел рассказать, почему “неизвестная 22” оказалась таким геморроем - у нас появилась рабочая теория по этому поводу. “Неизвестная 22” это случайная переменная, генерирующаяся при инициализации приложения и привязываемая потом к сессии. Разработчики тщательно искали всё, что могло бы повлиять на “неизвестную 22”, до тех пор, пока до них постепенно не дошло, что у неё нет входных параметров. Это просто случайная величина. Я попробую объяснить, почему это было так сложно определить.

Пример из реального мира: Представьте, что мы хотим определить температуру в Нью Йорке. Есть огромное количество значений, которые с ней коррелируют. Например, продажи мороженого. Когда продажи повышаются, температура растёт. Однако, вычислять температуру на основании объёмов продажи мороженого бесполезно. Корреляция не означает причинно-следственной связи. Учитывайте это при чтении про “неизвестную 22”. Сначала разработчики пытались менять токен автризации, и каждый раз, когда они это делали, “Неизвестная 22” так же менялась! Так что они решили проверить, что авторизация влияет на “Неизвестную 22”. Для этого им требовались данные.

Сбор этих данных занял много времени, поскольку им приходилось авторизовываться и выходить обратно. Так же на “неизвестную 22” могло влиять много других полей, включая, например, SessionID, Auth_token, Auth_ticket. Они пытались понять зависимости от каждой из них, до тех пор, пока до кого-то не дошло: у “неизвестной 22” нет входных параметров.

“Неизвестная 22” генерируется случайно каждый раз при старте приложения. Так что Niantic никоим образом не может проверить, какое значение там “должно быть” - ведь оно случайно! Так что туда можно просто пихать всё что угодно. Это пока что теория, но все разработчики придерживаются её. 18:30 - Прорыв #6. Кажется, у нас первый успешный вызов API! Ждите обновлений, подтверждение будет в течении часа. 18:35 - Работает! Поверьте, у меня это вызывает столько же волнения, сколько у вас. 19:00 - Публичный чат в discord пока пуст. Всё ещё ждём обновления от разработчиков. Вы тоже обновляете чат, чтобы так же увидеть ничего? 20:00 - Уже довольно долго не было никакой информации. Однако, они говорят, что работают над реализацией- так что видимо больше ломать нечего. Следующий апдейт должен быть большим, оставайтесь с нами… 20:30 - Они убрали публичный гитхаб. Наверное, куда-то переезжают. Видимо, работа идёт. Обновление: репозиторий закрыт по всяким там причинам копирайта. 22:00 - Уже начинаю сомневаться, но всё ещё верю, что они смогли тогда сделать успешный вызов API. Хотя видимо, они думают, когда, как и что им публиковать. Нелёгкая работа, учитывая то, что им закрыли гитхаб. С самого начала все знали, что у этой работы 2 этапа. Сперва реверс инжиниринг, затем создание нового API. Успешный тестовый вызов API важен, поскольку означает, что работа где-то на середине.

Однако, это не означает, что реверс инжиниринг закончен. Некоторые обойдёнными поля не были необходимыми, но они могут таки захотеть их взломать.

Я буду выглядеть идиотом, если окажется, что у них не вышел вызов API, но я готов смириться с этой возможностью. Дошёл до лимита количества знаков, продолжу тут.

Здесь я так же буду писать свои собственные мыли, как и в предыдущем комментарии. Я не продвинутый программист, но я был с группой “неизвестной 6” с самого начала. 6 august 2016, 23:00 - Небольшое обновление. Они ищут, как обойти копирайт, чтобы их материалы опять не снесли. Так же они пишут, что работают над реализацией- что значит, что они таки сделали успешный API запрос и уже занимаются рабочей версией API. 00:00 - Они говорят, что остался “последний рывок”, будем надеяться, что это означает что-то хорошее. Их работа затрудняется диким спамом с запросом прав и обновлений. Пожалуйста, просто дайте им кодить! Быстрее они работать не станут, и поверьте, вы сможете прожить ещё один день без API.

Так же некоторые обвиняют разработчиков, в том, что они делают это ради собственной выгоды. Это неправда. Я знаю многих из этих ребят, и они делают это просто ради фана, ради челенджа. Они не собираются никому продавать API. Так же они говорят “То, что некий платный сервис говорит, что у него есть исправление API, не говорит о том, что мы им его продали.” 00:30 - Просто хотел сказать - я ненавижу ботов. 00:45 - Они только что подтвердили, что API работает (НЕ ПОЛНОСТЬЮ). Но они вообще писали не об этом:

Для всех тех, кто распускает слухи о том, что мы сначала запустили своего бота. Взволнованный нашим успехом член команды использовал наше ещё не полностью готовое API в своём боте и запостил скриншот. А некоторые другие члены команды так же добавили API в проекты не связанные с ботами (например, pgoapi и RocketAPI).

В конце концов, как только мы закончим, у всех будет доступ к завершённой версии одновременно.

1:15 - Готово, API выпущено! Победа. Разработчики сломали API за 3 дня и 5 часов. Достойное достижение. 1:30 - Это API легко отслеживается, и Niaintic может определить, что вы используете не официальный клиент. Разработчиков это вполне устраивает, и не стоит катить на них бочку. Например, не учитывается высота над уровнем моря. Так же все запросы якобы идут с IOS устройства, что крайне подозрительно, поскольку с очевидностью оно Android… Работы ещё много, но мы получили рабочее API и с этим наше участие в проекте завершено.

1:45 - Пойду-ка я спать. В последние ночи я спал совсем мало. Хочу сказать огромное спасибо всем разработчикам, модераторам и всем остальным, кто принимал участие. И тем, кто терпеливо ждал решения проблемы. Ваша поддержка была удивительна. Неделю назад я не мог и представить, что буду полноценным менеджером сообщества хакеров Pokemon Go. Всем спасибо, /u/DutchDefender

От переводчика

Как вы понимаете, история не окончена, история только начинается. Прислушается ли Niantic к просьбам о улучшениях приложения? Или попробует снова защититься? Хватит ли у сообщества сил, желания и навыков взломать новые способы защиты? Узнаем в скором времени.

Будущее безопасности мобильных приложений, или чему нас могут научить покемоны

Оригинал поста на хабре

С момента появления Pokemon Go, я с большим любопытством отслеживаю всё, касающееся этой игрушки. Как ни странно, она даёт безумное количество интересной информации - в первую очередь относительно разработки мобильных приложений. Казалось бы, занимаетесь вы разработкой банковских приложений - в чём тут может быть связь? А нет, вам тоже найдётся, чему научиться у покемонов.

Ночь в одиноком октябре

Шёл октябрь 2017 года. В огромном здании из стекла и металла шёл какой-то бурный праздник. Люди с чёрными синяками под глазами и широкими улыбками на осунувшихся лицах поздравляли друг друга с окончанием долгой и тяжёлой работы. Наконец-то “Дженезис” будет запущен - продукт нового века с непревзойдённой скоростью обработки информации, который позволит объединить в одну огромную систему все существующие на планете электронные устройства.

Но не все радовались одинаково. Нарушая все правила безопасности, группа избранных предавалась празднику и обливала стены холодной серверной комнаты шампанским. Присмотревшись, можно было заметить некоторые странности в этой группе. У одного человека из черепа шла направленная вперёд антенна, у некоторых были татуировки с рисунками сложных схем, третьи несли свои массивные или же наоборот истощённые тела в лёгких экзоскелетах.

Чего ждёт Yandex? Или статус Yandex.Store

Оригинал поста на хабре

Наверное, многие недоуменно наморщили лбы - что это такое, “Yandex.Store”? И недаром. Вокруг этого сервиса было огромное количество шумихи, а потом всё внезапно прекратилось и затихло. Почему? Попробуем разобраться.

Хабростатистика, часть 2 — опровержение с графиками

Оригинал поста на хабре

Вчера я публиковал статью на тему того, когда лучше публиковаться на хабре. С тех пор мой паук собрал статистику за весь 2015 год, и картинка немного изменилась. Так же я построил несколько графиков, и в целом картина прояснилась. Осторожно, трафик!

ХаброСтатистика — в какое время лучше публиковать статью на хабре?

Оригинал поста на хабре

Астрологи объявили месяц статистики, и пожалуй я присоединюсь на сегодня к авторам статистических статей.

Наверняка вы задумывались - когда лучше публиковать статью? В выходные, чтобы её внимательно читали? В пятницу, чтобы у всех было хорошее настроение? В понедельник, когда после выходных все выползают читать хабр? А может быть, лучше выбрать время обеденного перерыва? Сразу оговорюсь, что данный вопрос имеет значение не только для хабра - примерно то же самое можно сказать про почти любые публикации, если нету какой-то совсем суровой конкретики. У меня есть своя гипотеза, о ней я расскажу в конце статьи.

Есть ли жизнь без Google Play? Альтернативы и обновление приложений

Оригинал поста на хабре

На днях я сделал приложение для обхода блокировки rutracker, однако уже дважды получил от Google отбой по разным надуманным причинам. Это очень огорчило, и возник вопрос - что делать? Поиск по хабру показал, что такие проблемы возникли очень у многих (пруфы: один, два, три, четыре… Тысячи их). А помимо бана есть ещё много причин, по которым ваше приложение может оказаться “за бортом” Google Play - например, если оно предназначено для использования только в компании, для друзей или для другого круга лиц. Так же возможно, что ваше приложение по определению не может быть выложено на Google Play - например, если оно является само по себе установщиком приложений.

Готовой статьи на эту тему я не нашёл, так что решил, что верным решением будет разобраться и написать свою. Итак, принципиальных варианта есть всего два.

Android клиент для rutracker - обходим блокировку при помощи Google Compression proxy

Оригинал поста на хабре

Полагаю, что все пользователя хабра так или иначе нашли способ попадать на рутрекер, но порой бывает лень включать свой тор, прокси, впн или что либо ещё. Мне вот стало лень, и поэтому я решил написать свой маленький клиент. Для обхода блокировок я решил использовать google compression proxy. Интересная, хорошая и полезная штука - странно, что по её поводу на хабре не было статей. Забегая вперёд, сразу скажу, что всё получилось, и работающую версию можно попробовать на своём девайсе. Однако в процессе возникло много всяких интересных нюансов, которые любопытны несколько больше, чем само приложение. Итак, начнём!

Google Compression proxy

Чтобы не повторять гугловые мануалы (все ссылки вы можете найти в конце статьи), просто скажу, что этот прокси сервер позволяет вашему Google Chrome значительно уменьшать количество воспринимаемого трафика за счёт его сжатия серверами Google. Работать прокси умеет по HTTP и по HTTPS. В первом случае используется адрес compress.googlezip.net, во втором - proxy.googlezip.net. Интересно, что для прокси требуется свой заголовок. В официальной документации его нет, однако можно найти исходники от гугла и немного в них покопаться. Выглядят они вот так (ссылка на самый интересный файл, размещено уже у меня на гихабе, поскольку в официальном репозитории на google code уже ничего посмотреть нельзя). Оттуда получаем такое добро:
1
2
3
4
5
6
7
8
9
var authHeader = function() {
var authValue = 'ac4500dd3b7579186c1b0620614fdb1f7d61f944';
var timestamp = Date.now().toString().substring(0, 10);
var chromeVersion = navigator.appVersion.match(/Chrome\/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
return {
name: 'Chrome-Proxy',
value: 'ps=' + timestamp + '-' + Math.floor(Math.random() * 1000000000) + '-' + Math.floor(Math.random() * 1000000000) + '-' + Math.floor(Math.random() * 1000000000) + ', sid=' + MD5(timestamp + authValue + timestamp) + ', b=' + chromeVersion[3] + ', p=' + chromeVersion[4] + ', c=win'
};
};

Всё довольно очевидно, но можно остановиться подробнее.

Для каждого запроса должен присутствовать заголовок Chrome-Proxy. В нём должна быть следующая строка:

1
ps=<timestamp>-<num1>-<num2>-<num3>, sid=<md5 string>, b=<build>, p=<patch>, c=<platform>

где:

timestamp: время в linux timestamp num1, num2, num3: некие случайные числа, которые можно поставить в 0 md5 string: md5 хэш строки авторизации auth string: "<timestamp>" + "<auth key>" + "<timestamp>" auth key: ac4500dd3b7579186c1b0620614fdb1f7d61f944 - просто некий ключ… Один на всех, и все на одного. build: номер билда хрома - например, 2214 patch: номер патча хрома - например, 115 platform: платформа - например, “win” В качестве полного примера можно привести такой заголовок: Chrome-Proxy: ps=1439961190-0-0-0, sid=9fb96126616582c4be88ab7fe26ef593, b=2214, p=115, c=win

Как ни странно и не смешно, можно использовать эту самую строку при любом количестве запросов без всяких изменений… Например, на этом основано расширение для Firefox, которое занимается пересжатием трафика. Видимо, просто делалась защита от ленивого дурака. Однако, честным вариантом будет переписать это на Java так:

1
2
3
4
5
6
7
8
9
10
11
public static String[] authHeader() {
String[] result = new String[2];
result[0] = "Chrome-Proxy";
String authValue = "ac4500dd3b7579186c1b0620614fdb1f7d61f944";
String timestamp = Long.toString(System.currentTimeMillis()).substring(0, 10);
String[] chromeVersion = {"49", "0", "2623", "87"};
String sid = (timestamp + authValue + timestamp);
sid = Utils.md5(sid);
result[1] = "ps=" + timestamp + "-" + Integer.toString((int) (Math.random() * 1000000000)) + "-" + Integer.toString((int) (Math.random() * 1000000000)) + "-" + Integer.toString((int) (Math.random() * 1000000000)) + ", sid=" + sid + ", b=" + chromeVersion[2] + ", p=" + chromeVersion[3] + ", c=win";
return result;
}

Далее надо выбрать, какой именно вариант для проксирования мы выбираем. Мой провайдер суров, и блокирует запросы, если они идут по HTTP через гугловую проксю, так что пришлось идти правильным путём через SSL.

WebView через SSL

Чтобы не идти долгим и печальным путём написания клиента с нуля, я решил "просто" показывать всё как есть через стандартный WebView, благо ранее уже писал простое-клиент, которое делает примерно то же самое, и шустро работает даже на тяжёлом веб сайте. Кажется - работы на полчаса. Как же я ошибался... Если посмотреть решения по проксированию WebView в интернете, то становится очень грустно - все делают примерно так:
1
2
3
4
5
6
7
8
9
10
public static void setKitKatWebViewProxy(Context appContext, String host, int port, String exclusionList) {
Properties properties = System.getProperties();
properties.setProperty("http.proxyHost", host);
properties.setProperty("http.proxyPort", port + "");
properties.setProperty("https.proxyHost", host);
properties.setProperty("https.proxyPort", port + "");
properties.setProperty("http.nonProxyHosts", exclusionList);
properties.setProperty("https.nonProxyHosts", exclusionList);
/// ... such much shit
}

Оставшуюся часть намеренно опустил - там идёт ещё около сотни строк с условиями по версии андроида и жутким шаманством. При этом у многих это всё равно не работает, плюс есть проблемы с переключением режима проксирования - и её “решают” путём установки Thread.Sleep(1000) между операциями. Хотя я не являюсь Java разработчиком, а просто иногда балуюсь, мне поплохело. Здравый смысл подсказал мне, что нужно перехватывать запросы из Webview (для этого у WebViewClient есть чудесная функция shouldInterceptRequest), и далее самому заниматься проксированием. Это у меня даже вполне получилось:

1
2
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("compress.googlezip.net", 80));
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(proxy);

Всё отлично, всё работает! Только одна проблема. Как заметили внимательные читатели, в параметрах функции указан 80ый порт. По довольно смешной причине. А именно - потому что HttpURLConnection не умеет работать с HTTPS проксями. Совсем. Никак. У меня ушло куча времени, чтобы понять, что всё настолько плохо, и что нельзя сделать HTTPS прокси ни через HttpURLConnection, ни через популярный okHttp . Я немного призадумался, затем решительным жестом отмёл все доводы Google о том, что библиотеки Apache не подходят для Android, стряхнул пыль с проверенных jar’ов и решительно подключил их к проекту. И всё получилось! Пятый и шестой андроид на ура восприняли такой ужасный проступок. Если кто-то знает, как можно было решить проблему без использования библиотек Apache - расскажите. Конечно, можно было бы сделать всё на сокетах, но это довольно печально.

Итак, мы наконец смогли отобразить главную страницу рутрекера. Казалось бы, победа близко. Как же я ошибался.

Реклама

Практически с первой попытки отладки я столкнулся с тем, что всё чудовищно тормозит. Причина довольно очевидна - безумное количество рекламы всевозможных видов. Мне очень не хотелось с ней что-то делать - все мы знаем положение рутрекера, и чувакам явно нужно много золота для защиты от DDOS'a и родного государства - но с рекламой приложением было пользоваться вообще нереально. Правильным решением было бы находить её и вырезать из тела страницы, но более быстрым для реализации подходом было просто порезать её по хостам:

довольно очевидный код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static boolean is_adv(Uri url) {
String[] adv_hosts = {"marketgid.com", "adriver.ru", "thisclick.network", "hghit.com",
"onedmp.com", "acint.net", "yadro.ru", "tovarro.com", "marketgid.com", "rtb.com", "adx1.com",
"directadvert.ru", "rambler.ru"};
String[] adv_paths = {"brand", "iframe"};
String host = url.getHost();
for (String item : adv_hosts) {
if (StringUtils.containsIgnoreCase(host, item)) {
return true;
}
}
if (StringUtils.containsIgnoreCase(url.getHost(), "rutracker.org")) {
String path = url.getPath();
for (String item : adv_paths) {
{
if (StringUtils.containsIgnoreCase(path, item)) {
return true;
}
}
}
}
return false;
}

И после этого мы просто обрубаем её получение:

1
2
3
4
if (Utils.is_adv(url)) {
Log.d("WebView", "Not fetching advertisment");
return new WebResourceResponse("text/javascript", "UTF-8", null);
}

Задним умом я только что подумал, что возможно было бы проще сделать список разрешённых хостов… Но этим самым умом все сильны. Теперь приложение стало работать не то что с приемлимой, а с очень бодрой и приятной скоростью. Если владельцев рутрекера это огорчит, то блокировку уберу - но скорее всего вместе с приложением. С чудовищными тормозами в нём просто не будет смысла.

Отправка форм

Довольно откинувшись на стул, я обнаружил... Что не работает авторизация. Что было на самом деле весьма очевидно - поскольку в перехватываемом мной и передаваемом далее запросе я не отправлял данные, которые должны уйти в POST. Казалось бы, пара минут - и всё будет хорошо. Как же я ошибался... Выяснилось, что способов перехватить POST из WebView нету. Совсем. Никак. Лучшие рекомендованные практики - внедрять в страницу свой javascript и вызывать из него специальные Java методы. Или переводить сервер на GET запросы. От первого варианта мне несколько поплохело, а второй недоступен по понятным причинам. Да и был бы некорректен. Почесав голову и попробовав отловить POST ещё в нескольких местах, я пришёл к выводу, что нормального решения всё же нет. В результате сделал решение смешное. А именно - при получении страницы менять метод всех форм с POST на GET. А после этого при следующем обращении конвертировать переданные в адресной строке параметры в тело POST запроса. Звучит ужасно, но всё не так плохо, если у вас нет адресной строки, в которой можно опозориться, больших переменных или файлов для передачи. Хотя нет, вру конечно. Всё очень плохо, но другого адекватного пути я не нашёл.

довольно очевидный код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static UrlEncodedFormEntity get2post(Uri url) {
Set<String> params = url.getQueryParameterNames();
if (params.isEmpty())
return null;

List<NameValuePair> paramsArray = new ArrayList<>();

for (String name : params) {
Log.d("Utils", "converting parameter " + name + " to post");
paramsArray.add(new BasicNameValuePair(name, url.getQueryParameter(name)));
}
try {
return new UrlEncodedFormEntity(paramsArray, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}

Где мои куки, чувак?

Когда я в очередной раз откинулся на спинку кресла после успешно пройденной авторизации и кликнул следующую ссылку, я обнаружил, что авторизация умирает на следующей странице. Что в общем тоже довольно логично, поскольку никто волшебно куками не управляет, а посылаем мы все заголовки вручную. Но здесь я в кои-то веки не ошибся в том, что дописать будет просто.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (Utils.is_login_form(url)) {

Header[] all = response.getAllHeaders();
for (Header header1 : all) {
Log.d("WebView", "LOGIN HEADER: " + header1.getName() + " : " + header1.getValue());

}
Header[] cookies = response.getHeaders("set-cookie");
if (cookies.length > 0) {
String val = cookies[0].getValue();
val = val.substring(0, val.indexOf(";"));
authCookie = val.trim();
CookieManager.put(MainContext, authCookie);
Log.d("WebView", "=== Auth cookie: ==='" + val + "'");
//redirect does not work o_O
String msgText = "<script>window.location = \"http://rutracker.org/forum/index.php\"</script>";
ByteArrayInputStream msgStream = new ByteArrayInputStream(msgText.getBytes("UTF-8"));
return new WebResourceResponse("text/html", "UTF-8", msgStream);

}
}

Правда, внимательный читатель обнаружит, что там идёт какая-то странная переадресация яваскриптом. Да, всё так. В ответе должен был быть 302 заголовок авторизации, но почему-то его никуда не приходило. В результате я оказывался на странице форума по некорректному адресу с доменом login.rutracker.org - всё работало, но, поскольку ссылки везде относительные, при следующем же клике наступал облом. Кстати, здесь же можно заметить, что пользовательскую куку мы бережно сохраняем, чтобы не пришлось потом заново авторизоваться.

Теперь-то всё?!

Именно с этим вопросом я в который раз открывал страничку, уже уверенный, что ничего не упустил. Как же я... В общем, можно было свободно искать через нативный поиск или через гугловый без авторизации, можно было смотреть любую тему, нельзя было только одно... Скачать торрент. Поскольку по нажатию ссылок ничего хорошего не происходит. Но в этот раз всё тоже было несложно - в интерфейс добавилась программная менюшка с кнопочкой Share, которая позволяет отправить куда угодно Magnet ссылку. Было бы удобнее кидать ссылку на торрент файл, но он у вас явно будет блокирован. Конечно, можно было бы скачивать торрент файл и передавать его через шаринг - но это как-нибудь в другой раз.

Результат

Можно посмотреть по скринам - главная страница и расшаривание magnet ссылки из топика.

Осталось только опубликовать

На основании тех странных android приложений, которые я выкладывал раньше, у меня сложилось ощущение, что они пропускают вообще всё. Так что я с лёгким сердцем отправил приложение на публикацию и сел писать статью. Обычно приложение появлялось на google play в течении пары часов, так что времени как раз хватало. К сожалению, в этот раз прошло 8 часов. И в ответ пришло
1
2
3
4
5
6
7
8
After review, Rutracker free - unofficial, ru.jehy.proxy_rutracker, has been suspended and removed from Google Play as a policy strike because it violates the <strong>impersonation</strong> policy.
<strong>Next Steps</strong>
<ol> <li> Read through the <a href="https://play.google.com/about/spam.html#impersonation-intellectual-property:impersonation">Impersonation</a> article for more details and examples of policy violations.</li>
<li> Make sure your app is compliant with the <a href="https://play.google.com/about/spam.html#impersonation-intellectual-property">Impersonation and Intellectual Property</a> policy and all other policies listed in the <a href="https://play.google.com/about/developer-content-policy.html">Developer Program Policies</a>. Remember additional enforcement could occur if there are further policy issues with your apps.</li>
<li> <a href="https://play.google.com/apps/publish">Sign in to your Developer Console</a> and submit the policy compliant app using a new package name and a new app name.</li></ol>
<strong>What if I have permission to use the content?</strong>

<a href="https://support.google.com/googleplay/android-developer/troubleshooter/2993242">Contact our support team</a> to provide a justification for its use. Justification may include providing proof that you are authorized to use the content in your app or some other legal justification.Additional suspensions of any nature may result in the termination of your developer account, and investigation and possible termination of related Google accounts. If your account is terminated, payments will cease and Google may recover the proceeds of any past sales and/or the cost of any associated fees (such as chargebacks and transaction fees) from you.If you&rsquo;ve reviewed the policy and feel this suspension may have been in error,please reach out to our <a href="https://support.google.com/googleplay/android-developer/troubleshooter/2993242">policy support team</a>. One of my colleagues will get back to you within 2 business days.

Не совсем понятно, кто кого имперсонифицировал, но у меня было три варианта, что же не понравилось Google:

  1. Что я взял откуда-то иконки;
  2. Что я упомянул в комментариях, что приложение работает через google compression proxy;
  3. Что в качестве банеров я использовал варианты смешных логотипов рутрекера с нового конкурса;
Конечно,в любом случае довольно неприятно, что нужно зачем-то переименовывать pakage и закачивать его заново. Да тебя ещё и предупреждают, что "я тебя запомнил, и ещё раз так сделаешь - забаню". Ну как-то по гопнически. Не ожидал, прям обидно. Из упомянутых выше вариантов я решил, что скорее всего виноват второй - упоминание Google всуе. Ну ладно, как скажете - не стал писать, как работает приложение. Просто "приложение с проксированием, которое позволяет обходить блокировку rutracker.org". Ну и заодно поставил кривые нарисованные за пять минут иконки и аналогичные банеры. И что вы думаете - мои усилия были вознаграждены! Дальше мне пришёл следующий ответ.
After review, Rutracker free, ru.jehy.rutracker_free, has been suspended and removed from Google Play as a policy strike because it violates the webviews and affiliate spam policy.

Next Steps

Read through the Webviews and Affiliate Spam article for more details and examples of policy violations.
Make sure your app is compliant with the Spam policy and all other policies listed in the Developer Program Policies. Remember that additional enforcement could occur if there are further policy issues with your apps.
If it’s possible to bring your app into compliance, you can sign in to your Developer Console and submit the policy compliant app using a new package name and a new app name.

В общем, меня обвинили в том, что не то приложение только реферральные ссылки передаёт, то ли ничего кроме вебвьюва не делает. Ничего "левого" в приложении нет, а называть это "обычным отображением" тоже неверно - идёт довольно много работы по проксированию. Ну и особенно это меня удивило в свете того, что я уже успешно закачивал на Google Play приложения, которые фактически только состоят из вебвью на сайт. Нюансов было два - в приложении была авторизация, и я был хозяином сайта, на который шёл вебвью. Но я это никак не указывал, и Google узнать этого не мог.
В общем, на оба этих обвинения я ответил просьбой разобраться - сутки ответа нет, может быть ответ придёт ещё через сутки. Хотя надежды как-то мало. Так что ставим пока что приложение из APK. Если оно таки появится на Google Play, то можно будет обновиться оттуда.

<h1>ToDo</h1>
Если делать полноценное приятное приложение, то можно было бы добавить много всего хорошего. В том числе
<ol>
<li>Стили с адаптацией к просмотру с телефона и планшета; <b>UPD: сделано в какой-то степени;</b></li>
<li>Автоматическое обновление; <b>UPD: сделано;</b></li>
<li>Корректное вырезание рекламы;</li>
<li>Передачу торрентов торрент файлами, а не magnet ссылками;</li>
<li>Выход из авторизованного состояния на рутрекере (да, сейчас ты там авторизуешься навсегда);</li>
<li>Какие-то осмысленные сообщения о вероятных ошибках;</li>
<li>Совместимость с большим количеством устройств - сейчас можно попробовать запустить на Android от 4.0 до 6 - но результат непредсказуем - надо много тестировать. У меня работает на Nexus 5 с Android 6 и на Sony Xperia Z3 с Android 5;</li>
<li>Удобный ввод авторизации и поиска без того, чтобы тыкать на ужасные маленькие элементы веб формы;</li>
<li>Убрать из кода некоторое количество копи-пейста;</li>
<li>Добавить шифрование хранимой пользовательской куки уникальным для устройства ключом на случай, если данные с телефона украдут;</li>
<li>Реализовать монетизацию приложения, свои всплывающие банеры и ссылки, которые принесли бы мне тонны золота.</li>
</ol>
Но, к сожалению, у меня нет времени этим серьёзно заниматься - хотелось просто сделать некий работающий прототип - чтобы показать, что веб приложения в приложениях Android работают, работают хорошо и быстро. Попутно, правда, подтвердилось мнение о том, что среднее по больнице качество разработки на Android довольно сильно страдает, стандартные библиотеки не покрывают всех кейсов, и есть большое количество странных задач, которые никем толково не реализованы.
Буду рад, если кто-то возьмёт этот код для разработки более серьёзного приложения или же зашлёт пулл реквестов - обещаю их внимательно отсматривать и применять.

<h1>Q&amp;A</h1>

 - Но ведь гораздо проще зайти на рутрекер через ХХХ (например, просто включив экономию трафика на телефоне или в браузере).
 - Да.

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

 - А если рутрекер заблокирует гугл за большое количество заходов с их прокси?
 - К ним и так сейчас через неё ходят, просто используя хром. Так что вряд ли. И вообще, банить всех подряд - плохая идея, так можно и чиновником стать.

 - Ваш код ужасен!
 - Да, я уже упоминал, что я не Java разработчик.

 - У меня не заработало.
 - Да, это общая проблема Android - полноценное приложение надо тестировать в 10 раз дольше, чем писать. Увы, у меня такой возможности не было. Присылайте ошибки, пулл реквесты - поправим.

 - А можно вообще так использовать Google Compression Proxy?
 - Пока непонятно - приложение забанили ещё до этого потенциального вопроса.

 - Я не доверяю вам, наверняка вы крадёте все мои пароли и скачиваете себе мои торренты.
 - Пожалуйста - соберите приложение себе из исходников.

 - А почему иконки такие страшные?
 - Посмотрите под заголовком "осталось только опубликовать" - там всё объясняется.

 - Это же раздолье для злых роботов и пауков!
 - Да нет, не обольщайтесь. С очевидностью, там тоже есть свои лимиты и проверки на роботов. Есть гораздо более простые пути для ботов.

 - Сайт криво отображается.
 - Да, местами есть проблемы. Но связанные скорее с некачественной вёрсткой.

<h1>Привет рутрекеру</h1>
Отдельно - несколько пожеланий для администрации рутрекера на случай, если вдруг они сюда заглянут

<ol>
<li>Пожалуйста, подумайте о альтернативных вариантах монетизации. Всё равно большинство пользователей обходят эти жуткие гирлянды банеров адблоком.</li>
<li>Было бы хорошо внедрить различных провайдеров авторизации - гуглового или фейсбучного, например.</li>
<li>Обновите вёрстку и внешний вид до чего-то более красивого, удобного и желательно с мобильной версией.</li>
</ol>

<h1>Ссылки</h1>
<b>UPD</b>: <a href="https://habrahabr.ru/post/279553/">пост </a>про то, как реализовано обновление, и как вообще жить без Google Play;

<ol>
<li>Для тех, кто не осилил целиком текст - ещё раз скрины: <a href="https://habrastorage.org/files/1d7/650/2ac/1d76502ac6e547c3bdd9ea22a15e593d.png">главная страница</a> и <a href="https://habrastorage.org/files/a09/ca2/fc2/a09ca2fc22a143429ded78b4c669a2f1.png">расшаривание magnet ссылки из топика</a>.</li>
<li>Актуальные <a href="https://github.com/jehy/rutracker-free">исходники</a> и <a href="https://github.com/jehy/rutracker-free/releases">релизы </a>на гитхабе;</li>
<li><a href="http://whois.jehy.ru/">Тут </a>можно взять меня на работу - да, я её ищу;</li>
<li>Забавная <a href="http://wirama.web.id/using-google-chromes-compression-proxy-for-every-browser/ ">статья </a>о том, как использовать Google Compression Proxy+Squid. С неё я и начинал;</li>
<li><a href="https://code.google.com/archive/p/datacompressionproxy/">Тут </a>лежат исходники от compression proxy от гугла. Поскольку google code не даёт их посмотреть, можно посмотреть на <a href="https://github.com/jehy/datacompressionproxy/blob/master/background.js">моём гитхабе</a>. Его я и разбирал;</li>
<li>Что такое google compression proxy <a href="https://developer.chrome.com/multidevice/data-compression">для пользователя</a> и <a href="https://developer.chrome.com/multidevice/data-compression-for-isps">для администратора</a>;</li>
<li>Хороший <a href="http://superuser.com/questions/945924/how-do-i-use-google-data-compression-proxy-on-firefox">ответ и разбор стандарта google compression proxy</a>, правда только для HTTP траффика, не HTTPS. Есть пример реализации для firefox. Его я и переводил для статьи;</li>
<li><a href="https://github.com/miku-nyan/DCP-bridge">Реализация прокси на Java</a>, которая проксирует через google data compression server. Сделано для какого-то image board. Хороший код, есть что посмотреть;</li>
<li>Единственный найденный мной вариант по <a href="http://stackoverflow.com/questions/15048102/httprouteplanner-how-does-it-work-with-an-https-proxy">проксированию HTTP трафика через HTTPS прокси</a> при помощи библиотек apache. Его я и дорабатывал.</li>
</ol>