更多全球网络安全资讯尽在邑安全
void __noreturn ipp_server()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
addr_len = 0x10;
optval = 1;
kc_client = 0;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, 1);
sock = socket(AF_INET, SOCK_STREAM, 0);
if ( sock < 0 )
{
...
}
if ( setsockopt(sock, 1, SO_REUSEADDR, &optval, 4u) < 0 )
{
...
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = 2;
sin.sin_addr.s_addr = htonl(0);
sin.sin_port = htons(631u); // listens on TCP 631
if ( bind(sock, (const struct sockaddr *)&sin, 0x10u) < 0 )
{
...
}
// accept up to 128 clients simultaneously
listen(sock, 128);
while ( g_enabled )
{
client_sock = accept(sock, &addr, &addr_len);
if ( client_sock >= 0 )
{
update_count_client_connected(CLIENT_CONNECTED);
val[0] = 60;
val[1] = 0;
if ( setsockopt(client_sock, 1, SO_RCVTIMEO, val, 8u) < 0 )
perror("ipp_server: setsockopt SO_RCVTIMEO failed");
kc_client = (kc_client *)malloc(sizeof(kc_client));
if ( kc_client )
{
memset(kc_client, 0, sizeof(kc_client));
kc_client->client_sock = client_sock;
pthread_mutex_lock(&g_mutex);
thread_index = get_available_client_thread_index();
if ( thread_index < 0 )
{
pthread_mutex_unlock(&g_mutex);
free(kc_client);
kc_client = 0;
close(client_sock);
update_count_client_connected(CLIENT_DISCONNECTED);
}
else if ( pthread_create(
&g_client_threads[thread_index],
&attr,
(void *(*)(void *))thread_handle_client_connection,
kc_client) )
{
...
}
else
{
pthread_mutex_unlock(&g_mutex);
}
}
else
{
...
}
}
}
close(sock);
pthread_attr_destroy(&attr);
pthread_exit(0);
}
void __fastcall __noreturn thread_handle_client_connection(kc_client *kc_client)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
client_sock = kc_client->client_sock;
while ( g_enabled && !do_http(kc_client) )
;
close(client_sock);
update_count_client_connected(CLIENT_DISCONNECTED);
free(kc_client);
pthread_exit(0);
}
unsigned int __fastcall do_http(kc_client *kc_client)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
kc_client_ = kc_client;
client_sock = kc_client->client_sock;
content_len = 0xFFFFFFFF;
strcpy(http_continue, "HTTP/1.1 100 Continuernrn");
pCurrent = 0;
pUnderscoreLQ_or_CRCL = 0;
p_client_data = 0;
kc_job = 0;
strcpy(aborted_by_system, "aborted-by-system");
remaining_len = 0;
kc_chunk = 0;
// buf_read is on the stack and is 1024 bytes
memset(buf_read, 0, sizeof(buf_read));
// Read in 1024 bytes maximum
count_read = readUntil_0d0a_x2(client_sock, (unsigned __int8 *)buf_read, 0x400);
if ( (int)count_read < = 0 )
return 0xFFFFFFFF;
// if received "100-continue", sends back "HTTP/1.1 100 Continuernrn"
if ( strstr(buf_read, "100-continue") )
{
ret_1 = send(client_sock, http_continue, 0x19u, 0);
if ( ret_1 < = 0 )
{
perror("do_http() write 100 Continue xx");
return 0xFFFFFFFF;
}
}
// If POST /USB is found
pCurrent = strstr(buf_read, "POST /USB");
if ( !pCurrent )
return 0xFFFFFFFF;
pCurrent += 9; // points after "POST /USB"
// If _LQ is found
pUnderscoreLQ_or_CRCL = strstr(pCurrent, "_LQ");
if ( !pUnderscoreLQ_or_CRCL )
return 0xFFFFFFFF;
Underscore = *pUnderscoreLQ_or_CRCL;
*pUnderscoreLQ_or_CRCL = 0;
usblp_index = atoi(pCurrent);
*pUnderscoreLQ_or_CRCL = Underscore;
if ( usblp_index > 10 )
return 0xFFFFFFFF;
// by default, will exit here as no printer connected
if ( !is_printer_connected(usblp_index) )
return 0xFFFFFFFF; // exit if no printer connected
kc_client_->usblp_index = usblp_index;
// /! does not read from pCurrent
pCurrent = strstr(buf_read, "Content-Length: ");
if ( !pCurrent )
{
// Handle chunked HTTP encoding
...
}
// no chunk encoding here, normal http request
pCurrent += 0x10;
pUnderscoreLQ_or_CRCL = strstr(pCurrent, "rn");
if ( !pUnderscoreLQ_or_CRCL )
return 0xFFFFFFFF;
Underscore = *pUnderscoreLQ_or_CRCL;
*pUnderscoreLQ_or_CRCL = 0;
content_len = atoi(pCurrent);
*pUnderscoreLQ_or_CRCL = Underscore;
memset(recv_buf, 0, sizeof(recv_buf));
count_read = recv(client_sock, recv_buf, 8u, 0);// 8 bytes are read only initially
if ( count_read != 8 )
return 0xFFFFFFFF;
if ( (recv_buf[2] || recv_buf[3] != 2) && (recv_buf[2] || recv_buf[3] != 6) )
{
ret_1 = do_airippWithContentLength(kc_client_, content_len, recv_buf);
if ( ret_1 < 0 )
return 0xFFFFFFFF;
return 0;
}
...
unsigned int __fastcall do_airippWithContentLength(kc_client *kc_client, int content_len, char *recv_buf_initial)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
client_sock = kc_client->client_sock;
recv_buf2 = malloc(content_len);
if ( !recv_buf2 )
return 0xFFFFFFFF;
memcpy(recv_buf2, recv_buf_initial, 8u);
if ( toRead(client_sock, recv_buf2 + 8, content_len - 8) >= 0 )
{
if ( recv_buf2[2] || recv_buf2[3] != 0xB )
{
if ( recv_buf2[2] || recv_buf2[3] != 4 )
{
if ( recv_buf2[2] || recv_buf2[3] != 8 )
{
if ( recv_buf2[2] || recv_buf2[3] != 9 )
{
if ( recv_buf2[2] || recv_buf2[3] != 0xA )
{
if ( recv_buf2[2] || recv_buf2[3] != 5 )
Job = Response_Unk_1(kc_client, recv_buf2);
else
// recv_buf2[3] == 0x5
Job = Response_Create_Job(kc_client, recv_buf2, content_len);
}
else
{
// recv_buf2[3] == 0xA
Job = Response_Get_Jobs(kc_client, recv_buf2, content_len);
}
}
else
{
...
}
// recv_buf was allocated on the heap
unsigned int __fastcall Response_Get_Jobs(kc_client *kc_client, unsigned __int8 *recv_buf, int content_len)
{
char command[64]; // [sp+24h] [bp-1090h] BYREF
char suffix_data[2048]; // [sp+64h] [bp-1050h] BYREF
char job_data[2048]; // [sp+864h] [bp-850h] BYREF
unsigned int error; // [sp+1064h] [bp-50h]
size_t copy_len; // [sp+1068h] [bp-4Ch]
int copy_len_1; // [sp+106Ch] [bp-48h]
size_t copied_len; // [sp+1070h] [bp-44h]
size_t prefix_size; // [sp+1074h] [bp-40h]
int in_offset; // [sp+1078h] [bp-3Ch]
char *prefix_ptr; // [sp+107Ch] [bp-38h]
int usblp_index; // [sp+1080h] [bp-34h]
int client_sock; // [sp+1084h] [bp-30h]
kc_client *kc_client_1; // [sp+1088h] [bp-2Ch]
int offset_job; // [sp+108Ch] [bp-28h]
char bReadAllJobs; // [sp+1093h] [bp-21h]
char is_job_media_sheets_completed; // [sp+1094h] [bp-20h]
char is_job_state_reasons; // [sp+1095h] [bp-1Fh]
char is_job_state; // [sp+1096h] [bp-1Eh]
char is_job_originating_user_name; // [sp+1097h] [bp-1Dh]
char is_job_name; // [sp+1098h] [bp-1Ch]
char is_job_id; // [sp+1099h] [bp-1Bh]
char suffix_copy1_done; // [sp+109Ah] [bp-1Ah]
char flag2; // [sp+109Bh] [bp-19h]
size_t final_size; // [sp+109Ch] [bp-18h]
int offset; // [sp+10A0h] [bp-14h]
size_t response_len; // [sp+10A4h] [bp-10h]
char *final_ptr; // [sp+10A8h] [bp-Ch]
size_t suffix_offset; // [sp+10ACh] [bp-8h]
kc_client_1 = kc_client;
client_sock = kc_client->client_sock;
usblp_index = kc_client->usblp_index;
suffix_offset = 0; // offset in the suffix_data[] stack buffer
in_offset = 0;
final_ptr = 0;
response_len = 0;
offset = 0; // offset in the client data "recv_buf" array
final_size = 0;
flag2 = 0;
suffix_copy1_done = 0;
is_job_id = 0;
is_job_name = 0;
is_job_originating_user_name = 0;
is_job_state = 0;
is_job_state_reasons = 0;
is_job_media_sheets_completed = 0;
bReadAllJobs = 0;
// prefix_data is a heap allocated buffer to copy some bytes
// from the client input but is not super useful from an
// exploitation point of view
prefix_size = 74; // size of prefix_ptr[] heap buffer
prefix_ptr = (char *)malloc(74u);
if ( !prefix_ptr )
{
perror("Response_Get_Jobs: malloc xx");
return 0xFFFFFFFF;
}
memset(prefix_ptr, 0, prefix_size);
// copy bytes indexes 0 and 1 from client data
copied_len = memcpy_at_index(prefix_ptr, in_offset, &recv_buf[offset], 2u);
in_offset += copied_len;
// we make sure to avoid this condition to be validated
// so we keep bReadAllJobs == 0
if ( *recv_buf == 1 && !recv_buf[1] )
bReadAllJobs = 1;
offset += 2;
// set prefix_data's bytes index 2 and 3 to 0x00
prefix_ptr[in_offset++] = 0;
prefix_ptr[in_offset++] = 0;
offset += 2;
// copy bytes indexes 4,5,6,7 from client data
in_offset += memcpy_at_index(prefix_ptr, in_offset, &recv_buf[offset], 4u);
offset += 4;
copy_len_1 = 0x42;
// copy bytes indexes [8,74] from table keywords
copied_len = memcpy_at_index(prefix_ptr, in_offset, &table_keywords, 0x42u);
in_offset += copied_len;
++offset; // offset = 9 after this
// job_data[] and suffix_data[] are 2 stack buffers to copy some bytes
// from the client input but are not super useful from an
// exploitation point of view
memset(job_data, 0, sizeof(job_data));
memset(suffix_data, 0, sizeof(suffix_data));
suffix_data[suffix_offset++] = 5;
// we need to enter this to trigger the stack overflow
if ( !bReadAllJobs )
{
// iteration 1: offset == 9
// NOTE: we make sure to overwrite the "offset" local variable
// to be content_len+1 when overflowing the stack buffer to exit this loop after the 1st iteration
while ( recv_buf[offset] != 3 && offset < = content_len )
{
// we make sure to enter this as we need flag2 != 0 later
// to trigger the stack overflow
if ( recv_buf[offset] == 0x44 && !flag2 )
{
flag2 = 1;
suffix_data[suffix_offset++] = 0x44;
// we can set a copy_len == 0 to simplify this
// offset = 9 here
copy_len = (recv_buf[offset + 1] < < 8) + recv_buf[offset + 2];
copied_len = memcpy_at_index(suffix_data, suffix_offset, &recv_buf[offset + 1], copy_len + 2);
suffix_offset += copied_len;
}
++offset; // iteration 1: offset = 10 after this
// this is the same copy_len as above but just used to skip bytes here
// offset = 10 here
copy_len = (recv_buf[offset] < < 8) + recv_buf[offset + 1];
offset += 2 + copy_len; // we can set a copy_len == 0 to simplify this
// iteration 1: offset = 12 after this
// again, copy_len is pulled from client controlled data,
// this time used in a copy onto a stack buffer
// copy_len equals maximum: 0xff00 + 0xff
// and a copy is made into command[] which is a 2048-byte buffer
copy_len = (recv_buf[offset] < < 8) + recv_buf[offset + 1];
offset += 2; // iteration 1: offset = 14 after this
// we need flag2 == 1 to enter this
if ( flag2 )
{
// /! VULNERABILITY HERE /!
memset(command, 0, sizeof(command));
memcpy(command, &recv_buf[offset], copy_len);// VULN: stack overflow here
...
-00001090 command DCB 64 dup(?)
-00001050 suffix_data DCB 2048 dup(?)
-00000850 job_data DCB 2048 dup(?)
-00000050 error DCD ?
-0000004C copy_len DCD ?
-00000048 copy_len_1 DCD ?
-00000044 copied_len DCD ?
-00000040 prefix_size DCD ?
-0000003C in_offset DCD ?
-00000038 prefix_ptr DCD ? ; offset
-00000034 usblp_index DCD ?
-00000030 client_sock DCD ?
-0000002C kc_client_1 DCD ?
-00000028 offset_job DCD ?
-00000024 DCB ? ; undefined
-00000023 DCB ? ; undefined
-00000022 DCB ? ; undefined
-00000021 bReadAllJobs DCB ?
-00000020 is_job_media_sheets_completed DCB ?
-0000001F is_job_state_reasons DCB ?
-0000001E is_job_state DCB ?
-0000001D is_job_originating_user_name DCB ?
-0000001C is_job_name DCB ?
-0000001B is_job_id DCB ?
-0000001A suffix_copy1_done DCB ?
-00000019 flag2 DCB ?
-00000018 final_size DCD ?
-00000014 offset DCD ?
-00000010 response_len DCD ?
-0000000C final_ptr DCD ? ; offset
-00000008 suffix_offset DCD ?
// we need to enter this to trigger the stack overflow
if ( !bReadAllJobs )
{
// iteration 1: offset == 9
// NOTE: we make sure to overwrite the "offset" local variable
// to be content_len+1 when overflowing the stack buffer to exit this loop after the 1st iteration
while ( recv_buf[offset] != 3 && offset < = content_len )
{
...
// we need flag2 == 1 to enter this
if ( flag2 )
{
// /! VULNERABILITY HERE /!
memset(command, 0, sizeof(command));
memcpy(command, &recv_buf[offset], copy_len);// VULN: stack overflow here
// dispatch to right command
if ( !strcmp(command, "job-media-sheets-completed") )
{
is_job_media_sheets_completed = 1;
}
...
else if ( !strcmp(command, "job-id") )
{
// atm we make sure to send a "job-id " command to go here
is_job_id = 1;
}
else
{
...
}
}
offset += copy_len; // this is executed before looping
}
} // end of while loop
final_size += prefix_size;
if ( bReadAllJobs )
offset_job = read_job_value(usblp_index, 1, 1, 1, 1, 1, 1, job_data);
else
offset_job = read_job_value(
usblp_index,
is_job_id,
is_job_name,
is_job_originating_user_name,
is_job_state,
is_job_state_reasons,
is_job_media_sheets_completed,
job_data);
// offset_job is an offset inside job_data[] stack buffer
// atm we assume offset_job == 0 so we skip this condition.
// Note we assume that due to no printing job currently existing
// but it would be better to actually make sure all the is_xxx variables == 0 as explained above
if ( offset_job > 0 ) // assumed skipped for now
{
...
b_write_ipp_response2:
final_ptr[response_len++] = 3;
// the "client_sock" is a local variable that we overwrite
// when trying to reach the stack address. We need to brute
// force the socket value in order to effectively send
// us our leaked data if we really want that data back but
// otherwise the send() will silently fail
error = write_ipp_response(client_sock, final_ptr, response_len);
// From testing, it is safe to use the starting .got address for the prefix_ptr
// and free() will ignore that address hehe
// XXX - not sure why but if I use memset_ptr (offset inside
// the .got), it crashes on free() though lol
if ( prefix_ptr )
{
free(prefix_ptr);
prefix_ptr = 0;
}
// Freeing the final_ptr is no problem for us
if ( final_ptr )
{
free(final_ptr);
final_ptr = 0;
}
// this is where we get $pc control
if ( error )
return 0xFFFFFFFF;
else
return 0;
}
// we reach here if no job data
final_ptr = (char *)malloc(++final_size);
if ( final_ptr )
{
// prefix_ptr is a heap buffer that was allocated at the
// beginning of this function but pointer is stored in a
// stack variable. We actually need to corrupt this pointer
// as part of the stack overflow to reach the return address
// which means we can leak make it copy any size from any
// address which results in our leak primitive
memset(final_ptr, 0, final_size);
copied_len = memcpy_at_index(final_ptr, response_len, prefix_ptr, prefix_size);
response_len += copied_len;
goto b_write_ipp_response2;
}
// error below / never reached
...
}
# cat /proc/sys/kernel/randomize_va_space
[*] '/home/cedric/test/firmware/netgear_r6700/_R6700v3-
V1.0.4.118_10.0.90.zip.extracted/
_R6700v3-V1.0.4.118_10.0.90.chk.extracted/squashfs-root/usr/bin/KC_PRINT'
Arch: arm-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8000)
final_ptr = (char *)malloc(++final_size);
copied_len = memcpy_at_index(final_ptr, response_len, prefix_ptr, prefix_size);
error = write_ipp_response(client_sock, final_ptr, response_len);
# cat /proc/317/maps
00008000-00018000 r-xp 00000000 1f:03 1429 /usr/bin/KC_PRINT // static
00018000-00019000 rw-p 00010000 1f:03 1429 /usr/bin/KC_PRINT // static
00019000-0001c000 rw-p 00000000 00:00 0 [heap] // static
4001e000-40023000 r-xp 00000000 1f:03 376 /lib/ld-uClibc.so.0 // ASLR
4002a000-4002b000 r--p 00004000 1f:03 376 /lib/ld-uClibc.so.0
4002b000-4002c000 rw-p 00005000 1f:03 376 /lib/ld-uClibc.so.0
4002f000-40030000 rw-p 00000000 00:00 0
40154000-4015f000 r-xp 00000000 1f:03 265 /lib/libpthread.so.0 // ASLR
4015f000-40166000 ---p 00000000 00:00 0
40166000-40167000 r--p 0000a000 1f:03 265 /lib/libpthread.so.0
40167000-4016c000 rw-p 0000b000 1f:03 265 /lib/libpthread.so.0
4016c000-4016e000 rw-p 00000000 00:00 0
4016e000-401d3000 r-xp 00000000 1f:03 352 /lib/libc.so.0 // ASLR
401d3000-401db000 ---p 00000000 00:00 0
401db000-401dc000 r--p 00065000 1f:03 352 /lib/libc.so.0
401dc000-401dd000 rw-p 00066000 1f:03 352 /lib/libc.so.0
401dd000-401e2000 rw-p 00000000 00:00 0 // Broken ASLR
bcdfd000-bce00000 rwxp 00000000 00:00 0
bcffd000-bd000000 rwxp 00000000 00:00 0
bd1fd000-bd200000 rwxp 00000000 00:00 0
bd3fd000-bd400000 rwxp 00000000 00:00 0
bd5fd000-bd600000 rwxp 00000000 00:00 0
bd7fd000-bd800000 rwxp 00000000 00:00 0
bd9fd000-bda00000 rwxp 00000000 00:00 0
bdbfd000-bdc00000 rwxp 00000000 00:00 0
bddfd000-bde00000 rwxp 00000000 00:00 0
bdffd000-be000000 rwxp 00000000 00:00 0
be1fd000-be200000 rwxp 00000000 00:00 0
be3fd000-be400000 rwxp 00000000 00:00 0
beacc000-beaed000 rw-p 00000000 00:00 0 [stack] // ASLR
.text:000118A0 LDR R3, [R11,#-0x28]
.text:000118A4
.text:000118A4 loc_118A4 ; Get_JobNode_Print_Job+7D8↑j
.text:000118A4 MOV R0, R3
.text:000118A8 SUB SP, R11, #4
.text:000118AC POP {R11,PC}
原文来自: 4hou.com
原文链接: https://research.nccgroup.com/2022/02/28/brokenprint-a-netgear-stack-overflow/
推荐文章
1
2
原文始发于微信公众号(邑安全):深入考察Netgear R6700v3 KC_PRINT服务中栈溢出漏洞
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论