I hate software

  • Subscribe to our RSS feed.
  • Twitter
  • StumbleUpon
  • Reddit
  • Facebook
  • Digg

Friday, 4 October 2013

[russian] Уязвимость загрузки APK в Android

Posted on 12:25 by Unknown
Это - перевод статьи-анализа уязвимости. Оригинал - http://blog.sina.com.cn/s/blog_be6dacae0101bksm.html

Не успели интернет-массы отбурлить после недавно обнаруженной группой Bluebox Security уязвимости в ОС Android, позволяющей заменить код установленного приложения без нарушения цифровой подписи, как в системе загрузки Apk была обнаружена еще одна дырка.

Немного теории.

Многие думают, что сам факт написания программы в управляемой среде типа Java автоматически делает их код неуязвимым и безопасным. Но забывают о том, что у JVM есть довольно строгая спецификация на низкоуровневые взаимодействия с памятью и арифметику. Давайте рассмотрим простой пример. Скорее всего, внимательные программисты сразу поймут детали уязвимости, а для тех, кто не поймет - будет пояснение.

public class JavaTest {
public static void main (String [] args) {
short a = (short) 0xFFFF;
int b;
b = a;
System.out.println (b);
b = a & 0xFFFF;
System.out.println (b);
}
}
Просто, да? Если нет, то давайте посмотрим, во что это реально исполняется. Я тут быстренько написал в REPL Clojure - он на JVM, да и результаты будут одинаковы в большинстве не-JVM языков.
Clojure 1.1.0
user=> (def a (short 0xffff))
#'user/a
user=> (def b (int a))
#'user/b
user=> (def c (int (and a 0xffff)))
#'user/c
user=> a
-1
user=> b
-1
user=> c
65535
user=>
В чем дело? В расширении знаковых типов (sign extension). Казалось бы, основа основ, которую вбивают со школьной скамьи, ан нет - эта бяка ответственна за половину известных уязвимостей (за остальную половину - неинициализированная память). 0xffff, когда мы кладем его в 16-битный short, превращается в -1 (дополнительный код). Согласно правилам расширения знака, если мы присваиваем более широкому типу (в данном случае - 32-битному int) отрицательное значение, оно преобразуется так, чтобы представление в новом формате хранило то же значение, что исходная переменная. То есть, мы получаем int b = 0xFFFFFFFF. Старший (знаковый) бит стоит => лополнительный код => -1. Когда мы отсекаем старшие биты (b & 0xffff), старший бит обнуляется, и мы получаем 0xffff. Но у нас же int, 32 бита - это всего лишь 65535 в прямом коде.

Формат ZIP

Давайте рассмотрим формат архивов ZIP, которым на самом деле являются Jar и Apk архивы (примечание переводчика - на платформе Android Apk обычно часть данных типа картинок не сжимается, и выравнивается на 4 байта утилитой ZipAlign, чтобы можно было отображать ресурсы в память через системный вызов mmap, но это не влияет на совместимость с другими реализациями ZIP). Каждая запись в Zip архиве (абстракцией для которой является Java класс ZipEntry) имеет заголовок следующего вида:

        local file header signature     4 bytes  (0x04034b50)
version needed to extract 2 bytes
general purpose bit flag 2 bytes
compression method 2 bytes
last mod file time 2 bytes
last mod file date 2 bytes
crc-32 4 bytes
compressed size 4 bytes
uncompressed size 4 bytes
filename length 2 bytes
extra field length 2 bytes

filename (variable size)
extra field (variable size)
Обратите внимание на то, что два последних поля (имя файла и дополнительное поле, которое хранится после данных файла), имеют переменную длину.

Проверка Apk в системе Android

При загрузке apk файла происходит проверка контрольной суммы. Функция getInputStream, которая позволяет получить поток для чтения данных из ZipEntry, выглядит примерно следующим образом:

RAFStream rafstrm = new RAFStream(raf, entry.mLocalHeaderRelOffset + 28);
DataInputStream is = new DataInputStream(rafstrm);
int localExtraLenOrWhatever = Short.reverseBytes(is.readShort());
is.close();
//skip the name and this "extra" data or whatever it is: 
rafstrm.skip(entry.nameLength + localExtraLenOrWhatever);
rafstrm.mLength = rafstrm.mOffset + entry.compressedSize;
if (entry.compressionMethod == ZipEntry.DEFLATED) {
int bufSize = Math.max(1024, (int) Math.min(entry.getSize(), 65535L));
return new ZipInflaterInputStream(rafstrm, new Inflater(true), bufSize, entry);
}
else {
return rafstrm;
}
Обратите внимание на выделенные фрагменты кода. Как мы видим, максимальная длина "экстра" данных - 65535 байт (для хранения используется двухбайтовое беззнаковое число). Проблема в том, что в Java нет беззнаковых типов данных, и максимальная длина - 32768 (2 ^ 15). А что, если записать в заголовок Zip большее значение? Правильно, старший бит будет интерпретирован как знаковый, и в соответствии с расширением знаковых типов, переменная localExtraLenOrWhatever получит отрицательное значение.

Таким образом, вектор атаки очевиден - записать отрицательное значение в длину поля "extra", тем самым получив возможность перезаписать имя DEX файла. Небольшое ограничение - для проведения такой атаки необходимо, чтобы размер DEX кода в исходном APK был меньше 64кб.

На сегодняшний день уязвимость исправлена, но ошибки, связанные с переполнением целочисленных типов, по-прежнему являются причиной многих уязвимостей. С учётом того, что больше и больше программистов не знают не только о различиях big/little endian, а даже о знаковых типах данных - ситуация будет только ухудшаться :)
Email ThisBlogThis!Share to XShare to FacebookShare to Pinterest
Posted in | No comments
Newer Post Older Post Home

0 comments:

Post a Comment

Subscribe to: Post Comments (Atom)

Popular Posts

  • thoughts on modern operating systems
    For a long time I've been messing with low-level code on various embedded hardware. While I generally find linux kernel code an outstand...
  • An update on OSX Touchscreen driver
    After playing with the HID interface in OS X, I have found out there exists an API for simulating input events from user space, so I've ...
  • Why I adore Java NOT.
    Ok, here are some of my thoughts about Java as both a language and a development platform. TL;DR: when all you have is a hammer, everything ...
  • Doing it wrong: application preferences
    One thing that's been always bothering me is the lack of a unified way to keep persistent settings. When you're installing a shiny n...
  • KVM on ARM Cortex A15 (OMAP5432 UEVM)
    Hi! In this post I'll summarize the steps I needed to do in order to get KVM working on the OMAP5 ARM board using the virtualization ext...
  • Multitouch touchscreen support in OS X
    Hi there! I happen to have a Dell S2340T multitouch monitor (quite an expensive toy btw) which has a touch controller from 3M. It works fine...
  • Results of 2012 as seen from 2013
    This post is a couple hours late but my excuse is that I was having amazing holidays with my nearest and dearest and without the huge distra...
  • Mobile phones suck!
    Here we go again, another rant about mobile operating systems. I'm fed up with Android. But there's no alternative for me right now....
  • linux gone wrong
    I've been hacking on linux kernel for embedded hardware (mainly PDAs) for quite a while already and I'm sick to death of the bullshi...
  • Visions on Genode OS development
    Introduction This documents summarizes my visions of what and how could and should be improved about the Genode Operating System Framework i...

Blog Archive

  • ▼  2013 (14)
    • ►  November (4)
    • ▼  October (2)
      • Sleeping considered harmful
      • [russian] Уязвимость загрузки APK в Android
    • ►  May (2)
    • ►  April (2)
    • ►  February (2)
    • ►  January (2)
  • ►  2012 (6)
    • ►  December (1)
    • ►  October (1)
    • ►  September (3)
    • ►  June (1)
Powered by Blogger.

About Me

Unknown
View my complete profile