从附件所给的Readme以及IDA分析可以看出所给attachment是个xrdp-sesman,版本为0.9.18。
直接googl xrdp rce,很容搜到 CVE-2022-23613,并且受影响的版本为0.9.18及以前,找到相关的patch commit:
再结合IDA分析所给attachment中的sesman_data_in函数,没有size <= 8的判断,显然是未patch的状态:
可以确定这题考察的就是CVE-2022-23613的利用了。
从github上直接下0.9.18的源码 ,然后降级openssl ,在本地编译一个xrdp,解决lib的问题,即可正常跑起来。
简单来说,xrdp-sesman会在本地监听3350端口(/etc/xrdp/sesman.ini):
每次有一个socket连接进来,就会分配一个trans结构体:
然后进行相应的初始化:
简单来说,就是设置sockfd,type1,status等,并且为in_s和out_s分配空间,记录client的ip address和port,后面就进入到server和client的socket通信逻辑中了。
在通信过程中,server端接收的数据包格式为:
因此接收一个完整的数据包过程分为两步,第一步接收8 bytes,然后根据size字段计算出后续payload的长度为(size - 8)
问题在于如果header的字段size < 8,那么to_read就会整数溢出。
且由于这里涉及的size都是符号整数,因此如果设置header_size = 0x80000000,那么to_read = 0x80000000 - 8 = 0x7ffffff8,从而导致heap-based OOB write。
由于每次连接,server端都会创建一个trans结构体,因此可以创建多个连接,使得在self->in_s->end后面喷上一堆trans的结构体,且该结构体中有很多可以利用的成员,包括函数指针。
第一想法就是是否能够通过劫持trans结构体完成地址泄露. 通过分析trans_check_wait_objs()发现,server除了每次读完数据之后,还有一个额外的动作,即如果trans->wait_s如果不为NULL的话,会把缓冲区中的数据发送给client。
虽然正常情况下,trans->wait_s = NULL,但是可以通过OOB write对其进行劫持,设置trans->wait_s->p和trans->wait_s->end,就能把两者之间的数据发送给client。
但是这里存在一个问题,trans->wait_s是一个struct stream *:
需要在某个地址已知的地方布置一个stream结构体,然后再将trans->wait_s劫持到这个结构体上才能达到目的。 此时由于binary没有PIE,唯一知道的地址就是程序的地址,因此我只能先构造出一个任意地址写的原语,在bss上伪造一个stream结构体。
因此把目标选为trans->in_s,这个stream结构体存放server接收缓冲区的地址信息,根据self->trans_recv(self, self->in_s->end, to_read);,self->in_s是在堆上动态分配出来的,因此有可能可以通过OOB write将其劫持,实现任意地址写。
然而调试过程中发现,in_s chunk总是落在trans chunk之后,意味着在写到in_s结构体之前,必然会先破坏trans结构体。 因此这里需要进行堆风水,让in_s->end跳过trans结构体,落在in_s上。
很自然的想法就是首先通过OOB write,将in_s->end挪到trans + 0x20的位置上,因为前0x20 bytes的内容都是已知的。 然后通过断开连接,将这个trans给释放掉,这样继续OOB的时候,由于in_s->end此时落在chunk + 0x20的位置,不会破坏free chunk的metadata,因此可以继续OOB write,让其很安全地跨过trans结构体,落在in_s前。 最后再进行连接,将这个trans重新分配出来,这样再OOB write,就能劫持到in_s结构体了。
不过需要注意的是,内存的分配都是通过g_malloc进行的:
虽然源码里都是malloc,但是实际上g_malloc(xx, 0)最后都被优化为calloc,因为calloc不会从tcache中分配chunk,因此这里还需要通过7次连接加断开将相应的tcache填满,才能使得后来释放的trans能够重新分配回来。
这个阶段结束,就完成对trans->in_s的劫持了:
之后通过conns[11](对应被劫持的trans)发送数据给server,就能在0x410c00开始的位置布置数据了,即可以伪造任意的stream的结构体。
为了完成地址泄露,还是需要回到劫持trans->wait_s的路上去。 但是此时buffer指针已经落在trans后面了,如果想要继续OOB write劫持后面的trans结构体的话,会破坏堆上的free chunk。 因此只能将当前OOB write的trans释放再重新取回,从而重置in_s->end指针位置,使得其能够在不破坏chunk结构的情况下,劫持到trans->wait_s。
目前为止,通过在bss上构造一个wait_s结构体用来泄露got表,以及一个合法的in_s保证劫持wait_s的过程中程序的正常执行,即可完成对got表内容的泄露,从而获取到libc的地址。
最后一步显然就是通过劫持trans中的函数指针来getshell了。 最直接的目标就是trans->trans_recv,因为在调用trans->trans_recv时,第三个参数为to_read(edi寄存器),它是由header_size间接控制的,因此只要结合mov rsp, rdx; ret的gadget,将栈迁移到bss上进行rop就行了。
至于ropchain,前面已经可以在bss上写数据了,因此布置一条rop链也就轻而易举了。
只要执行`system("/bin/sh 1>&7 0>&7")就可以愉快地getshell了。
整体来说堆布局还是比较稳定的,不过显然是一次性exp,第二次再打堆布局就变了,得重启靶机环境。
__int64 print_version()
{
g_writeln("xrdp-sesman %s", "0.9.18");
g_writeln(" The xrdp session manager");
g_writeln(" Copyright (C) 2004-2020 Jay Sorg, Neutrino Labs, and all contributors.");
g_writeln(" See 69bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6F1k6i4g2@1M7X3W2F1L8$3I4S2j5Y4y4Q4x3V1k6^5M7X3c8H3 for more information.");
g_writeln("%s", "");
g_writeln(" Configure options:");
return g_writeln("%s", " \n");
}
__int64 print_version()
{
g_writeln("xrdp-sesman %s", "0.9.18");
g_writeln(" The xrdp session manager");
g_writeln(" Copyright (C) 2004-2020 Jay Sorg, Neutrino Labs, and all contributors.");
g_writeln(" See a53K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6F1k6i4g2@1M7X3W2F1L8$3I4S2j5Y4y4Q4x3V1k6^5M7X3c8H3 for more information.");
g_writeln("%s", "");
g_writeln(" Configure options:");
return g_writeln("%s", " \n");
}
struct trans
{
tbus sck; /* socket handle */
int mode; /* 1 tcp, 2 unix socket, 3 vsock */
int status;
int type1; /* 1 listener 2 server 3 client */
ttrans_data_in trans_data_in;
ttrans_conn_in trans_conn_in;
void *callback_data;
int header_size;
struct stream *in_s;
struct stream *out_s;
char *listen_filename;
tis_term is_term; /* used to test for exit */
struct stream *wait_s;
char addr[256];
char port[256];
int no_stream_init_on_data_in;
int extra_flags; /* user defined */
struct ssl_tls *tls;
const char *ssl_protocol; /* e.g. TLSv1, TLSv1.1, TLSv1.2, unknown */
const char *cipher_name; /* e.g. AES256-GCM-SHA384 */
trans_recv_proc trans_recv;
trans_send_proc trans_send;
trans_can_recv_proc trans_can_recv;
struct source_info *si;
enum xrdp_source my_source;
};
struct trans
{
tbus sck; /* socket handle */
int mode; /* 1 tcp, 2 unix socket, 3 vsock */
int status;
int type1; /* 1 listener 2 server 3 client */
ttrans_data_in trans_data_in;
ttrans_conn_in trans_conn_in;
void *callback_data;
int header_size;
struct stream *in_s;
struct stream *out_s;
char *listen_filename;
tis_term is_term; /* used to test for exit */
struct stream *wait_s;
char addr[256];
char port[256];
int no_stream_init_on_data_in;
int extra_flags; /* user defined */
struct ssl_tls *tls;
const char *ssl_protocol; /* e.g. TLSv1, TLSv1.1, TLSv1.2, unknown */
const char *cipher_name; /* e.g. AES256-GCM-SHA384 */
trans_recv_proc trans_recv;
trans_send_proc trans_send;
trans_can_recv_proc trans_can_recv;
struct source_info *si;
enum xrdp_source my_source;
};
in_trans = trans_create(self->mode, self->in_s->size,
self->out_s->size);
in_trans->sck = in_sck;
in_trans->type1 = TRANS_TYPE_SERVER;
in_trans->status = TRANS_STATUS_UP;
in_trans->is_term = self->is_term;
g_strncpy(in_trans->addr, self->addr,
sizeof(self->addr) - 1);
g_strncpy(in_trans->port, self->port,
sizeof(self->port) - 1);
g_sck_set_non_blocking(in_sck);
if (self->trans_conn_in(self, in_trans) != 0)
{
trans_delete(in_trans);
}
in_trans = trans_create(self->mode, self->in_s->size,
self->out_s->size);
in_trans->sck = in_sck;
in_trans->type1 = TRANS_TYPE_SERVER;
in_trans->status = TRANS_STATUS_UP;
in_trans->is_term = self->is_term;
g_strncpy(in_trans->addr, self->addr,
sizeof(self->addr) - 1);
g_strncpy(in_trans->port, self->port,
sizeof(self->port) - 1);
g_sck_set_non_blocking(in_sck);
if (self->trans_conn_in(self, in_trans) != 0)
{
trans_delete(in_trans);
}
| version(4 bytes) | size(4 bytes, be) | payload (size - 8) |
| version(4 bytes) | size(4 bytes, be) | payload (size - 8) |
if (self->si != 0 && self->si->source[self->my_source] > MAX_SBYTES)
{
}
else if (self->trans_can_recv(self, self->sck, 0))
{
cur_source = XRDP_SOURCE_NONE;
if (self->si != 0)
{
cur_source = self->si->cur_source;
self->si->cur_source = self->my_source;
}
// 目前已经读到的字节数
read_so_far = (int) (self->in_s->end - self->in_s->data);
// 还需要读的字节数
to_read = self->header_size - read_so_far;
if (to_read > 0)
{
// 接收数据,把都进来的数据写到 self->in_s->end 开始的位置,并更新其值
read_bytes = self->trans_recv(self, self->in_s->end, to_read);
if (read_bytes == -1)
{
if (g_tcp_last_error_would_block(self->sck))
{
/* ok, but shouldn't happen */
}
else
{
/* error */
self->status = TRANS_STATUS_DOWN;
if (self->si != 0)
{
self->si->cur_source = cur_source;
}
return 1;
}
}
else if (read_bytes == 0)
{
/* error */
self->status = TRANS_STATUS_DOWN;
if (self->si != 0)
{
self->si->cur_source = cur_source;
}
return 1;
}
else
{
self->in_s->end += read_bytes;
}
}
// 目前已经读到的字节数
read_so_far = (int) (self->in_s->end - self->in_s->data);
// 已经读到需要的字节数,进入 sesman_data_in 进行处理
if (read_so_far == self->header_size)
{
if (self->trans_data_in != 0)
{
rv = self->trans_data_in(self); // sesman_data_in()
if (self->no_stream_init_on_data_in == 0)
{
init_stream(self->in_s, 0);
}
}
}
if (self->si != 0)
{
self->si->cur_source = cur_source;
}
}
if (trans_send_waiting(self, 0) != 0)
{
/* error */
self->status = TRANS_STATUS_DOWN;
return 1;
}
if (self->si != 0 && self->si->source[self->my_source] > MAX_SBYTES)
{
}
else if (self->trans_can_recv(self, self->sck, 0))
{
cur_source = XRDP_SOURCE_NONE;
if (self->si != 0)
{
cur_source = self->si->cur_source;
self->si->cur_source = self->my_source;
}
// 目前已经读到的字节数
read_so_far = (int) (self->in_s->end - self->in_s->data);
// 还需要读的字节数
to_read = self->header_size - read_so_far;
if (to_read > 0)
{
// 接收数据,把都进来的数据写到 self->in_s->end 开始的位置,并更新其值
read_bytes = self->trans_recv(self, self->in_s->end, to_read);
if (read_bytes == -1)
{
if (g_tcp_last_error_would_block(self->sck))
{
/* ok, but shouldn't happen */
}
else
{
/* error */
self->status = TRANS_STATUS_DOWN;
if (self->si != 0)
{
self->si->cur_source = cur_source;
}
return 1;
}
}
else if (read_bytes == 0)
{
/* error */
self->status = TRANS_STATUS_DOWN;
if (self->si != 0)
{
self->si->cur_source = cur_source;
}
return 1;
}
else
{
self->in_s->end += read_bytes;
}
}
// 目前已经读到的字节数
read_so_far = (int) (self->in_s->end - self->in_s->data);
// 已经读到需要的字节数,进入 sesman_data_in 进行处理
if (read_so_far == self->header_size)
{
if (self->trans_data_in != 0)
{
rv = self->trans_data_in(self); // sesman_data_in()
if (self->no_stream_init_on_data_in == 0)
{
init_stream(self->in_s, 0);
}
}
}
if (self->si != 0)
{
self->si->cur_source = cur_source;
}
}
if (trans_send_waiting(self, 0) != 0)
{
/* error */
self->status = TRANS_STATUS_DOWN;
return 1;
}
static int
sesman_data_in(struct trans *self)
{
int version;
int size;
// 解析8字节的头部数据,更新header_size为该数据包总长
if (self->extra_flags == 0)
{
in_uint32_be(self->in_s, version);
in_uint32_be(self->in_s, size);
if (size > self->in_s->size)
{
LOG(LOG_LEVEL_ERROR, "sesman_data_in: bad message size");
return 1;
}
self->header_size = size;
self->extra_flags = 1;
}
// 解析完整的数据包,并做相应的处理
else
{
/* process message */
struct sesman_con *sc = (struct sesman_con *)self->callback_data;
self->in_s->p = self->in_s->data;
if (scp_process(self, sc->s) != SCP_SERVER_STATE_OK)
{
LOG(LOG_LEVEL_ERROR, "sesman_data_in: scp_process_msg failed");
return 1;
}
/* reset for next message */
self->header_size = 8;
self->extra_flags = 0;
init_stream(self->in_s, 0); /* Reset input stream pointers */
}
return 0;
}
static int
sesman_data_in(struct trans *self)
{
int version;
int size;
// 解析8字节的头部数据,更新header_size为该数据包总长
if (self->extra_flags == 0)
{
in_uint32_be(self->in_s, version);
in_uint32_be(self->in_s, size);
if (size > self->in_s->size)
{
LOG(LOG_LEVEL_ERROR, "sesman_data_in: bad message size");
return 1;
}
self->header_size = size;
self->extra_flags = 1;
}
// 解析完整的数据包,并做相应的处理
else
{
/* process message */
struct sesman_con *sc = (struct sesman_con *)self->callback_data;
self->in_s->p = self->in_s->data;
if (scp_process(self, sc->s) != SCP_SERVER_STATE_OK)
{
LOG(LOG_LEVEL_ERROR, "sesman_data_in: scp_process_msg failed");
return 1;
}
/* reset for next message */
self->header_size = 8;
self->extra_flags = 0;
init_stream(self->in_s, 0); /* Reset input stream pointers */
}
return 0;
}
if (trans_send_waiting(self, 0) != 0)
{
/* error */
self->status = TRANS_STATUS_DOWN;
return 1;
}
int
trans_send_waiting(struct trans *self, int block)
{
struct stream *temp_s;
int bytes;
int sent;
int timeout;
int cont;
timeout = block ? 100 : 0;
cont = 1;
while (cont)
{
// self->wait_s不为0
if (self->wait_s != 0)
{
temp_s = self->wait_s;
if (g_tcp_can_send(self->sck, timeout))
{
bytes = (int) (temp_s->end - temp_s->p);
// 通过socket向client发送数据
sent = self->trans_send(self, temp_s->p, bytes);
if (sent > 0)
{
temp_s->p += sent;
if (temp_s->source != 0)
{
temp_s->source[0] -= sent;
}
if (temp_s->p >= temp_s->end)
{
self->wait_s = temp_s->next;
free_stream(temp_s);
}
}
else if (sent == 0)
{
return 1;
}
else
{
if (!g_tcp_last_error_would_block(self->sck))
{
return 1;
}
}
}
else if (block)
{
/* check for term here */
if (self->is_term != 0)
{
if (self->is_term())
{
/* term */
return 1;
}
}
}
}
else
{
break;
}
cont = block;
}
return 0;
}
if (trans_send_waiting(self, 0) != 0)
{
/* error */
self->status = TRANS_STATUS_DOWN;
return 1;
}
int
trans_send_waiting(struct trans *self, int block)
{
struct stream *temp_s;
int bytes;
int sent;
int timeout;
int cont;
timeout = block ? 100 : 0;
cont = 1;
while (cont)
{
// self->wait_s不为0
if (self->wait_s != 0)
{
temp_s = self->wait_s;
if (g_tcp_can_send(self->sck, timeout))
{
bytes = (int) (temp_s->end - temp_s->p);
// 通过socket向client发送数据
sent = self->trans_send(self, temp_s->p, bytes);
if (sent > 0)
{
temp_s->p += sent;
if (temp_s->source != 0)
{
temp_s->source[0] -= sent;
}
if (temp_s->p >= temp_s->end)
{
self->wait_s = temp_s->next;
free_stream(temp_s);
}
}
else if (sent == 0)
{
return 1;
}
else
{
if (!g_tcp_last_error_would_block(self->sck))
{
return 1;
}
}
}
else if (block)
{
/* check for term here */
if (self->is_term != 0)
{
if (self->is_term())
{
/* term */
return 1;
}
}
}
}
else
{
break;
}
cont = block;
}
return 0;
}
struct stream
{
char *p;
char *end;
char *data;
int size;
int pad0;
/* offsets of various headers */
char *iso_hdr;
char *mcs_hdr;
char *sec_hdr;
char *rdp_hdr;
char *channel_hdr;
/* other */
char *next_packet;
struct stream *next;
int *source;
};
struct stream
{
char *p;
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!