ОРИГИНАЛЬНАЯ СТАТЬЯ
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro
$600 на SSD для Solidity hacking by Jolah Milovsky---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09
В нашей предыдущей статье Еще одна ошибка в Netfilter » я описал уязвимость, обнаруженную в подсистеме netfilter ядра Linux. Во время моего расследования я обнаружил странное сравнение, которое не полностью защищает копию в буфере. Это привело к переполнению буфера кучи, которое использовалось для получения привилегий суперпользователя в Ubuntu 22.04.
В последнем эпизоде мы достигли предела в nft_set структуре ( /include/net/netfilter/nf_tables.h).
В nft_setс одержится много данных, некоторые другие поля этой структуры могут быть использованы для получения лучшего примитива записи. Я решил поискать по полям длины ( udlen, klenа также dlen), потому что может быть полезно выполнить некоторые переполнения.
Изучение различных подходов к полю dlen, звонок в memcpyфункция (1)в nft_set_elem_init( /net/netfilter/nf_tables_api.c) держи мое внимание.
Этот вызов подозрительный, поскольку используются два разных объекта. Буфер назначения хранится в nft_set_extобъект, ext, тогда как размер копии извлекается из nft_set объект. Объект ext динамически размещается в (0)с elem и размер, зарезервированный для него, tmpl->len. Я хотел проверить, что значение, хранящееся в set->dlen используется для вычисления значения, хранящегося в tmpl->len.
nft_set_elem_init называется )внутри функции nft_add_set_elem( /net/netfilter/nf_tables_api.c), который отвечает за добавление элемента в набор сетевых фильтров.
Как вы можете заметить, set->dlen не используется для резервирования места для данных, связанных с идентификатором NFT_SET_EXT_DATA, вместо этого desc.len (5). desc инициализируется внутри функции nft_setelem_parse_data( /net/netfilter/nf_tables_api.c)
Прежде всего, data а также desc заполнены nft_data_init( /net/netfilter/nf_tables_api.c) по данным, предоставленным пользователем. Важнейшей частью является проверка между desc->lenа также set->dlen в, это происходит только в том случае, если данные, связанные с добавленным элементом, имеют тип, отличный от NFT_DATA_VERDICT.
Однако, set->dlenконтролируется пользователем при создании нового набора. Единственное ограничение состоит в том, что set->dlenдолжен быть меньше 64 байт, а тип данных должен отличаться от NFT_DATA_VERDICT. Более того, когда desc->typeравно NFT_DATA_VERDICT, desc->lenравен 16 байтам.
Добавление элемента типа NFT_DATA_VERDICT в набор с типом данных NFT_DATA_VALUE обычно приводят к desc->len отличается от set->dlen. Следовательно, можно выполнить переполнение буфера кучи в nft_set_elem_init в. Это переполнение буфера может быть расширено до 48 байт.
Тем не менее, это не стандартное переполнение буфера, когда пользователь может напрямую управлять переполняющимися данными. В этом случае случайные данные будут скопированы из выделенного буфера.
Если мы проверим вызов nft_set_elem_init (5), можно заметить, что скопированные данные извлекаются из локальной переменной elem, который является nft_set_elem объект.
Как видите, 64 байта зарезервированы для временного хранения данных, связанных с новым элементом. Однако в него записывается не более 16 байт. elem.dataкогда срабатывает переполнение буфера. Поэтому при переполнении используются случайные байты.
В этот момент я обнаружил переполнение буфера без контроля над данными, используемыми для повреждения. Создание эксплойта с неконтролируемым переполнением буфера — непростая задача.
elem.data используемый в переполнении не инициализирован
Давайте посмотрим на вызывающую функцию, возможно, ранее вызванная функция может помочь контролировать данные, используемые для переполнения. nft_add_set_elem вызывается nf_tables_newsetelem( /net/netfilter/nf_tables_api.c) для каждого элемента, который пользователь хочет добавить в набор.
nla_for_each_nested используется для перебора атрибутов, отправленных пользователем, поэтому пользователь может контролировать количество выполняемых итераций. А также nla_for_each_nested использует только макросы и встроенные функции, поэтому вызов nft_add_set_elemможет непосредственно сопровождаться другим вызовом nft_add_set_elem. Это очень полезно, потому что позволяет использовать данные предыдущего элемента в переполнении, т.к. elem.dataне инициализируется. Кроме того, можно игнорировать рандомизацию расположения стека. Следовательно, способ управления переполнением не зависит от компиляции ядра.
Следующая схема суммирует различные этапы elem.dataвнутри стека для создания управляемого переполнения.
Случайные данные сохраняются в стеке, добавляя новый элемент с NFT_DATA_VALUEданные ведут к данным, контролируемым пользователем, в стеке. Наконец, добавление второго элемента с NFT_DATA_VERDICT данные вызовут переполнение буфера, а остаток данных последнего элемента будет скопирован во время переполнения.
Теперь осталось только выровнять elemна размер объекта кеша, чтобы выполнить наилучшее переполнение. Следующая схема представляет собой построение elemвыровнять его на 64 байта.
Мы использовали следующую конструкцию, чтобы ориентироваться на kmalloc-64кеш:
Теперь, когда можно контролировать переполнение данных, следующим шагом будет поиск способа извлечения базы KASLR. Поскольку переполнение произошло только в kmalloc-xтайники, классика msg_msgобъекты не могут быть использованы для утечки информации, так как они размещены в kmalloc-cg-xтайники.
Мы посмотрели на user_key_payload( /include/keys/user-type.h) объекты, обычно используемые для хранения конфиденциальной пользовательской информации в пространстве ядра, представляют собой хорошую альтернативу. Они похожи на msg_msg объекты по своей структуре: заголовок с размером объекта, затем буфер с пользовательскими данными.
Эти объекты размещаются внутри функции user_preparse( /security/keys/user_defined.c)
Распределение, сделанное в (6)учитывает длину данных, предоставленных пользователем. Затем данные сохраняются сразу после заголовка с вызовом memcpyв (7) Заголовок user_key_payloadобъекты имеют длину 24 байта, следовательно, их можно использовать для распыления нескольких кэшей, kmalloc-32к kmalloc-8k.
Как с msg_msgобъекты, цель состоит в том, чтобы перезаписать поле datalenс большим значением, чем исходное. При извлечении сохраненной информации поврежденный объект вернет больше данных, чем изначально было предоставлено пользователем.
Однако у этого спрея есть существенный недостаток. Количество выделенных объектов ограничено. sysctlпеременная kernel.keys.maxkeysопределяет ограничение на количество разрешенных ключей в наборе ключей пользователя. Более того, kernel.keys.maxbytesограничивает количество хранимых байтов в связке ключей. Значения по умолчанию для этих переменных очень низкие. Они показаны ниже для Ubuntu 22.04.
Поскольку я нашел способ утечки информации, следующим шагом будет поиск интересной информации. Работа в рамках kmalloc-64 кеш показался лучшим, это кеш с наименьшими объектами, где может произойти переполнение. Следовательно, может произойти утечка большего количества объектов.
Эти объекты хранят указатели на функции (поля releaseа также confirm_switch), которые можно использовать для вычисления базы KASLR или базы модулей при их утечке, а также указателя на динамически размещаемый объект (поле ref) полезен для вычисления базы физической карты.
Такие объекты выделяются во время вызова percpu_ref_init( /lib/percpu-refcount.c).
Самый простой способ выделить percpu_ref_dataобъектов заключается в использовании io_uring_setupсистемный вызов ( /fs/io_uring.c). А для того, чтобы запрограммировать освобождение такого объекта, достаточно простого вызова метода closeдостаточно системного вызова.
Выделение percpu_ref_data объект выполняется во время инициализации io_ring_ctx объект ( /fs/io_uring.c) внутри функции io_ring_ctx_alloc( /fs/io_uring.c).
В качестве io_uring интегрирован в ядро Linux, утечка io_ring_ctx_ref_free( /fs/io_uring.c) позволяет вычислить базу KASLR.
Во время моего расследования некоторые неожиданные percpu_ref_data объекты были в утечке но с адресом функции io_rsrc_node_ref_zero( /fs/io_uring.c) в поле release. Проанализировав происхождение этих предметов, я понял, что они тоже происходят из io_uring_setup системный вызов. Этот хороший побочный эффект io_uring_setupsyscall позволил исправить утечку в моем эксплойте.
Теперь, когда можно получить утечку полезной информации, необходим хороший примитив записи для повышения привилегий.
Несколько недель назад Лам Джун Ронг из Starlabs опубликовал статью , в которой описывается новый способ эксплуатации CVE-2021-41073. Он представляет новый примитив записи, атаку разъединения. Он основан на list_del. После повреждения list_head с двумя адресами, один адрес сохраняется по другому.
Как и в статье Л.Дж. Ронга, цель для list_headкоррупция в моем подвиге simple_xattrобъект.
Для работы этого метода необходимо знать, какой объект был поврежден. В другом случае удаление случайных элементов из списка приводит к ошибке в обходе списка. Элементы в списке обозначаются своими именами.
Чтобы идентифицировать поврежденный объект, я выполняю трюк с nameполе: размещение nameпри длине, достаточной для резервирования 256 байтов, младший значащий байт возвращаемого адреса равен нулю. порядком байтов, такие как x86_64 , позволяют нам просто стереть младший значащий байт name после двух указателей в list_head. Следовательно, можно приготовить listполе для примитива записи и в то же время идентифицировать поврежденный объект, усекая его имя. Единственное требование состоит в том, чтобы все имена имели одинаковый конец.
Следующая схема резюмирует построение названия спрея с simple_xattr объекты.
Используя этот примитив записи, можно редактировать modprobe_pathс дорожкой в /tmp/папка. Это позволяет запускать любую программу с привилегиями root и пользоваться оболочкой root!

Я хотел бы поблагодарить RandoriSec за предоставленную мне возможность провести исследование уязвимостей внутри ядра Linux во время моей стажировки, а также мою исследовательскую группу за их советы.
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro
$600 на SSD для Solidity hacking by Jolah Milovsky---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09
В нашей предыдущей статье Еще одна ошибка в Netfilter » я описал уязвимость, обнаруженную в подсистеме netfilter ядра Linux. Во время моего расследования я обнаружил странное сравнение, которое не полностью защищает копию в буфере. Это привело к переполнению буфера кучи, которое использовалось для получения привилегий суперпользователя в Ubuntu 22.04.
Небольшой скачок в прошлое
В последнем эпизоде мы достигли предела в nft_set структуре ( /include/net/netfilter/nf_tables.h).
Код:
struct nft_set {
struct list_head list;
struct list_head bindings;
struct nft_table *table;
possible_net_t net;
char *name;
u64 handle;
u32 ktype;
u32 dtype;
u32 objtype;
u32 size;
u8 field_len[NFT_REG32_COUNT];
u8 field_count;
u32 use;
atomic_t nelems;
u32 ndeact;
u64 timeout;
u32 gc_int;
u16 policy;
u16 udlen;
unsigned char *udata;
/* runtime data below here */
const struct nft_set_ops *ops ____cacheline_aligned;
u16 flags:14,
genmask:2;
u8 klen;
u8 dlen;
u8 num_exprs;
struct nft_expr *exprs[NFT_SET_EXPR_MAX];
struct list_head catchall_list;
unsigned char data[]
__attribute__((aligned(__alignof__(u64))));
};
В nft_setс одержится много данных, некоторые другие поля этой структуры могут быть использованы для получения лучшего примитива записи. Я решил поискать по полям длины ( udlen, klenа также dlen), потому что может быть полезно выполнить некоторые переполнения.
Исследование кода и аномалий
Изучение различных подходов к полю dlen, звонок в memcpyфункция (1)в nft_set_elem_init( /net/netfilter/nf_tables_api.c) держи мое внимание.
Код:
void *nft_set_elem_init(const struct nft_set *set,
const struct nft_set_ext_tmpl *tmpl,
const u32 *key, const u32 *key_end,
const u32 *data, u64 timeout, u64 expiration, gfp_t gfp)
{
struct nft_set_ext *ext;
void *elem;
elem = kzalloc(set->ops->elemsize + tmpl->len, gfp); <===== (0)
if (elem == NULL)
return NULL;
ext = nft_set_elem_ext(set, elem);
nft_set_ext_init(ext, tmpl);
if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY))
memcpy(nft_set_ext_key(ext), key, set->klen);
if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END))
memcpy(nft_set_ext_key_end(ext), key_end, set->klen);
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
memcpy(nft_set_ext_data(ext), data, set->dlen); <===== (1)
...
return elem;
}
Этот вызов подозрительный, поскольку используются два разных объекта. Буфер назначения хранится в nft_set_extобъект, ext, тогда как размер копии извлекается из nft_set объект. Объект ext динамически размещается в (0)с elem и размер, зарезервированный для него, tmpl->len. Я хотел проверить, что значение, хранящееся в set->dlen используется для вычисления значения, хранящегося в tmpl->len.
Не то место
nft_set_elem_init называется )внутри функции nft_add_set_elem( /net/netfilter/nf_tables_api.c), который отвечает за добавление элемента в набор сетевых фильтров.
Код:
static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
const struct nlattr *attr, u32 nlmsg_flags)
{
struct nlattr *nla[NFTA_SET_ELEM_MAX + 1];
struct nft_set_ext_tmpl tmpl;
struct nft_set_elem elem; <===== (2)
struct nft_data_desc desc;
...
if (nla[NFTA_SET_ELEM_DATA] != NULL) {
err = nft_setelem_parse_data(ctx, set, &desc, &elem.data.val, <===== (3)
nla[NFTA_SET_ELEM_DATA]);
if (err < 0)
goto err_parse_key_end;
...
nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, desc.len); <===== (4)
}
...
err = -ENOMEM;
elem.priv = nft_set_elem_init(set, &tmpl, elem.key.val.data, <===== (5)
elem.key_end.val.data, elem.data.val.data,
timeout, expiration, GFP_KERNEL);
if (elem.priv == NULL)
goto err_parse_data;
...
Код:
static int nft_setelem_parse_data(struct nft_ctx *ctx, struct nft_set *set,
struct nft_data_desc *desc,
struct nft_data *data,
struct nlattr *attr)
{
int err;
err = nft_data_init(ctx, data, NFT_DATA_VALUE_MAXLEN, desc, attr); <===== (6)
if (err < 0)
return err;
if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) { <===== (7)
nft_data_release(data, desc->type);
return -EINVAL;
}
return 0;
}
Прежде всего, data а также desc заполнены nft_data_init( /net/netfilter/nf_tables_api.c) по данным, предоставленным пользователем. Важнейшей частью является проверка между desc->lenа также set->dlen в, это происходит только в том случае, если данные, связанные с добавленным элементом, имеют тип, отличный от NFT_DATA_VERDICT.
Однако, set->dlenконтролируется пользователем при создании нового набора. Единственное ограничение состоит в том, что set->dlenдолжен быть меньше 64 байт, а тип данных должен отличаться от NFT_DATA_VERDICT. Более того, когда desc->typeравно NFT_DATA_VERDICT, desc->lenравен 16 байтам.
Добавление элемента типа NFT_DATA_VERDICT в набор с типом данных NFT_DATA_VALUE обычно приводят к desc->len отличается от set->dlen. Следовательно, можно выполнить переполнение буфера кучи в nft_set_elem_init в. Это переполнение буфера может быть расширено до 48 байт.
Оставайся здесь ! Я скоро вернусь !
Тем не менее, это не стандартное переполнение буфера, когда пользователь может напрямую управлять переполняющимися данными. В этом случае случайные данные будут скопированы из выделенного буфера.
Если мы проверим вызов nft_set_elem_init (5), можно заметить, что скопированные данные извлекаются из локальной переменной elem, который является nft_set_elem объект.
Код:
struct nft_set_elem elem; <===== (2)
...
elem.priv = nft_set_elem_init(set, &tmpl, elem.key.val.data, <===== (5)
elem.key_end.val.data, elem.data.val.data,
timeout, expiration, GFP_KERNEL);
nft_set_elem( /net/netfilter/nf_tables.h) используются для хранения информации о новых элементах во время их создания.
#define NFT_DATA_VALUE_MAXLEN 64
struct nft_verdict {
u32 code;
struct nft_chain *chain;
};
struct nft_data {
union {
u32 data[4];
struct nft_verdict verdict;
};
} __attribute__((aligned(__alignof__(u64))));
struct nft_set_elem {
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} key;
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} key_end;
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} data;
void *priv;
};
Наконец, не так уж случайно
В этот момент я обнаружил переполнение буфера без контроля над данными, используемыми для повреждения. Создание эксплойта с неконтролируемым переполнением буфера — непростая задача.
elem.data используемый в переполнении не инициализирован
Давайте посмотрим на вызывающую функцию, возможно, ранее вызванная функция может помочь контролировать данные, используемые для переполнения. nft_add_set_elem вызывается nf_tables_newsetelem( /net/netfilter/nf_tables_api.c) для каждого элемента, который пользователь хочет добавить в набор.
Код:
static int nf_tables_newsetelem(struct sk_buff *skb,
const struct nfnl_info *info,
const struct nlattr * const nla[])
{
...
nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags);
if (err < 0) {
NL_SET_BAD_ATTR(extack, attr);
return err;
}
}
}
nla_for_each_nested используется для перебора атрибутов, отправленных пользователем, поэтому пользователь может контролировать количество выполняемых итераций. А также nla_for_each_nested использует только макросы и встроенные функции, поэтому вызов nft_add_set_elemможет непосредственно сопровождаться другим вызовом nft_add_set_elem. Это очень полезно, потому что позволяет использовать данные предыдущего элемента в переполнении, т.к. elem.dataне инициализируется. Кроме того, можно игнорировать рандомизацию расположения стека. Следовательно, способ управления переполнением не зависит от компиляции ядра.
Следующая схема суммирует различные этапы elem.dataвнутри стека для создания управляемого переполнения.
Случайные данные сохраняются в стеке, добавляя новый элемент с NFT_DATA_VALUEданные ведут к данным, контролируемым пользователем, в стеке. Наконец, добавление второго элемента с NFT_DATA_VERDICT данные вызовут переполнение буфера, а остаток данных последнего элемента будет скопирован во время переполнения.
Выбор кеша
Последнее, что мы не обсудили перед разработкой моей стратегии эксплуатации, — это кеш, в котором происходит переполнение. elem, размещенный в (0), зависит от различных опций, выбранных пользователем, как показано в предыдущем фрагменте функции nft_add_set_elem, его размер может варьироваться. Есть несколько вариантов, которые можно использовать для его увеличения, например, NFT_SET_ELEM_KEYа также NFT_SET_ELEM_KEY_END. Они позволяют резервировать два буфера длиной до 64 байт в elem. Таким образом, это переполнение явно может произойти в нескольких кешах. elem размещен на Ubuntu 22.04 с флагом GFP_KERNEL. Таким образом, соответствующие кеши kmalloc-{64,96,128,192}.Теперь осталось только выровнять elemна размер объекта кеша, чтобы выполнить наилучшее переполнение. Следующая схема представляет собой построение elemвыровнять его на 64 байта.
Мы использовали следующую конструкцию, чтобы ориентироваться на kmalloc-64кеш:
- 20 байт для заголовка объекта
- 28 байт заполнения через NFT_SET_ELEM_KEY
- 16 байт для хранения данных элемента типа NFT_DATA_VERDICT.
Дай мне утечку
Теперь, когда можно контролировать переполнение данных, следующим шагом будет поиск способа извлечения базы KASLR. Поскольку переполнение произошло только в kmalloc-xтайники, классика msg_msgобъекты не могут быть использованы для утечки информации, так как они размещены в kmalloc-cg-xтайники.
Мы посмотрели на user_key_payload( /include/keys/user-type.h) объекты, обычно используемые для хранения конфиденциальной пользовательской информации в пространстве ядра, представляют собой хорошую альтернативу. Они похожи на msg_msg объекты по своей структуре: заголовок с размером объекта, затем буфер с пользовательскими данными.
Код:
struct user_key_payload {
struct rcu_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[] __aligned(__alignof__(u64)); /* actual data */
};
Код:
int user_preparse(struct key_preparsed_payload *prep)
{
struct user_key_payload *upayload;
size_t datalen = prep->datalen;
if (datalen <= 0 || datalen > 32767 || !prep->data)
return -EINVAL;
upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); <===== (6)
if (!upayload)
return -ENOMEM;
/* attach the data */
prep->quotalen = datalen;
prep->payload.data[0] = upayload;
upayload->datalen = datalen;
memcpy(upayload->data, prep->data, datalen); <===== (7)
return 0;
}
Как с msg_msgобъекты, цель состоит в том, чтобы перезаписать поле datalenс большим значением, чем исходное. При извлечении сохраненной информации поврежденный объект вернет больше данных, чем изначально было предоставлено пользователем.
Однако у этого спрея есть существенный недостаток. Количество выделенных объектов ограничено. sysctlпеременная kernel.keys.maxkeysопределяет ограничение на количество разрешенных ключей в наборе ключей пользователя. Более того, kernel.keys.maxbytesограничивает количество хранимых байтов в связке ключей. Значения по умолчанию для этих переменных очень низкие. Они показаны ниже для Ubuntu 22.04.
Код:
kernel.keys.maxbytes = 20000
kernel.keys.maxkeys = 200
Утечка — это хорошо, а утечка с пользой — еще лучше
Поскольку я нашел способ утечки информации, следующим шагом будет поиск интересной информации. Работа в рамках kmalloc-64 кеш показался лучшим, это кеш с наименьшими объектами, где может произойти переполнение. Следовательно, может произойти утечка большего количества объектов.
Код:
percpu_ref_data( /include/linux/percpu-refcount.h) объекты также размещаются в этом кэше. Они представляют интерес, поскольку содержат два полезных типа указателей.
struct percpu_ref_data {
atomic_long_t count;
percpu_ref_func_t *release;
percpu_ref_func_t *confirm_switch;
bool force_atomic:1;
bool allow_reinit:1;
struct rcu_head rcu;
struct percpu_ref *ref;
};
Такие объекты выделяются во время вызова percpu_ref_init( /lib/percpu-refcount.c).
Код:
int percpu_ref_init(struct percpu_ref *ref, percpu_ref_func_t *release,
unsigned int flags, gfp_t gfp)
{
struct percpu_ref_data *data;
...
data = kzalloc(sizeof(*ref->data), gfp);
...
data->release = release;
data->confirm_switch = NULL;
data->ref = ref;
ref->data = data;
return 0;
}
Выделение percpu_ref_data объект выполняется во время инициализации io_ring_ctx объект ( /fs/io_uring.c) внутри функции io_ring_ctx_alloc( /fs/io_uring.c).
Код:
static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)
{
struct io_ring_ctx *ctx;
...
if (percpu_ref_init(&ctx->refs, io_ring_ctx_ref_free,
PERCPU_REF_ALLOW_REINIT, GFP_KERNEL))
goto err;
...
}
Во время моего расследования некоторые неожиданные percpu_ref_data объекты были в утечке но с адресом функции io_rsrc_node_ref_zero( /fs/io_uring.c) в поле release. Проанализировав происхождение этих предметов, я понял, что они тоже происходят из io_uring_setup системный вызов. Этот хороший побочный эффект io_uring_setupsyscall позволил исправить утечку в моем эксплойте.
Я (G) корень
Теперь, когда можно получить утечку полезной информации, необходим хороший примитив записи для повышения привилегий.
Несколько недель назад Лам Джун Ронг из Starlabs опубликовал статью , в которой описывается новый способ эксплуатации CVE-2021-41073. Он представляет новый примитив записи, атаку разъединения. Он основан на list_del. После повреждения list_head с двумя адресами, один адрес сохраняется по другому.
Как и в статье Л.Дж. Ронга, цель для list_headкоррупция в моем подвиге simple_xattrобъект.
Код:
struct simple_xattr {
struct list_head list;
char *name;
size_t size;
char value[];
};
Чтобы идентифицировать поврежденный объект, я выполняю трюк с nameполе: размещение nameпри длине, достаточной для резервирования 256 байтов, младший значащий байт возвращаемого адреса равен нулю. порядком байтов, такие как x86_64 , позволяют нам просто стереть младший значащий байт name после двух указателей в list_head. Следовательно, можно приготовить listполе для примитива записи и в то же время идентифицировать поврежденный объект, усекая его имя. Единственное требование состоит в том, чтобы все имена имели одинаковый конец.
Следующая схема резюмирует построение названия спрея с simple_xattr объекты.
Используя этот примитив записи, можно редактировать modprobe_pathс дорожкой в /tmp/папка. Это позволяет запускать любую программу с привилегиями root и пользоваться оболочкой root!

Примечания
Этот метод эксплуатации основан на гипотезе о том, что конкретный адрес отображается в пространстве ядра, что не всегда так. Таким образом, эксплойт не является полностью надежным, но все же имеет хорошие шансы на успех. Вторым недостатком атаки с отключением связи является паника ядра, которая возникает после завершения эксплойта. Этого можно было бы избежать, найдя объекты, которые могут остаться в памяти ядра в конце процесса эксплойта.Конец истории
Об этой уязвимости было сообщено команде безопасности Linux, и ей был присвоен код CVE-2022-34918. Они предложили патч, который я протестировал и рассмотрел, и он был выпущен в дереве основной ветки в рамках коммита 7e6bc1f6cabcd30aba0b11219d8e01b952eacbb6 .Вывод
Подводя итог, я обнаружил переполнение буфера кучи в подсистеме Netfilter ядра Linux. Эта уязвимость может быть использована для повышения привилегий в Ubuntu 22.04. Исходный код эксплойта доступен на нашем GitHub .Я хотел бы поблагодарить RandoriSec за предоставленную мне возможность провести исследование уязвимостей внутри ядра Linux во время моей стажировки, а также мою исследовательскую группу за их советы.