截取字符串的一定的长度在解各种数据包中是非常常用的,怎么实现截取取决于数据包的数据类型。
1、截取两个字节长度:
定义为宏:
#define pntoh16(p) ((uint16_t) \
((uint16_t)*((const uint8_t *)(p)+0)<<8| \
(uint16_t)*((const uint8_t *)(p)+1)<<0))
定义为函数:
uint16_t pntoh16(uint8_t* p)
{
return (uint16_t)|(uint16_t)(p+0)<<8|(uint16_t)(p+1)<<0);
}
2、截取三个字节长度:
定义为宏:
#define pntoh24(p) ((uint32_t)*((const uint8_t *)(p)+0)<<16| \
(uint32_t)*((const uint8_t *)(p)+1)<<8| \
(uint32_t)*((const uint8_t *)(p)+2)<<0)
定义为函数:
uint32_t pntoh24(uint8_t* p)
{
return (uint32_t)((uint32_t)§<<16|(uint32_t)(p+1)<<8|(uint32_t)*(p+2)<<0);
}
3、截取四个字节长度:
定义为宏:
#define pntoh32(p) ((uint32_t)*((const uint8_t *)(p)+0)<<24| \
(uint32_t)*((const uint8_t *)(p)+1)<<16| \
(uint32_t)*((const uint8_t *)(p)+2)<<8| \
(uint32_t)*((const uint8_t *)(p)+3)<<0)
定义为函数:
uint32_t pntoh32(uint8_t* p)
{
return (uint32_t)((uint32_t)§<<24|(uint32_t)(p+1)<<16|(uint32_t)(p+2)<<8|(uint32_t)(p+3)<<0);
}
以下类似函数省略:
#define pntoh40(p) ((uint64_t)*((const uint8_t *)(p)+0)<<32| \
(uint64_t)*((const uint8_t *)(p)+1)<<24| \
(uint64_t)*((const uint8_t *)(p)+2)<<16| \
(uint64_t)*((const uint8_t *)(p)+3)<<8| \
(uint64_t)*((const uint8_t *)(p)+4)<<0)
#define pntoh48(p) ((uint64_t)*((const uint8_t *)(p)+0)<<40| \
(uint64_t)*((const uint8_t *)(p)+1)<<32| \
(uint64_t)*((const uint8_t *)(p)+2)<<24| \
(uint64_t)*((const uint8_t *)(p)+3)<<16| \
(uint64_t)*((const uint8_t *)(p)+4)<<8| \
(uint64_t)*((const uint8_t *)(p)+5)<<0)
#define pntoh56(p) ((uint64_t)*((const uint8_t *)(p)+0)<<48| \
(uint64_t)*((const uint8_t *)(p)+1)<<40| \
(uint64_t)*((const uint8_t *)(p)+2)<<32| \
(uint64_t)*((const uint8_t *)(p)+3)<<24| \
(uint64_t)*((const uint8_t *)(p)+4)<<16| \
(uint64_t)*((const uint8_t *)(p)+5)<<8| \
(uint64_t)*((const uint8_t *)(p)+6)<<0)
#define pntoh64(p) ((uint64_t)*((const uint8_t *)(p)+0)<<56| \
(uint64_t)*((const uint8_t *)(p)+1)<<48| \
(uint64_t)*((const uint8_t *)(p)+2)<<40| \
(uint64_t)*((const uint8_t *)(p)+3)<<32| \
(uint64_t)*((const uint8_t *)(p)+4)<<24| \
(uint64_t)*((const uint8_t *)(p)+5)<<16| \
(uint64_t)*((const uint8_t *)(p)+6)<<8| \
(uint64_t)*((const uint8_t *)(p)+7)<<0)
有时候我们数据是网络字节序(大端字节序:把值的低位存在高地址)的需要截取并转化成主机字节序(小端字节序:把值得低位存在地地址)。
附加一个判断本机大小端的简单程序
void litterorbig ()
{
unsigned int x = 0x12345678;
char c = (char)&x;
if (*c == 0x78) {
printf(“Little endian”);
} else {
printf(“Big endian”);
}
}
下面分别是截取两、三、四、五、六、七、八字节长度并转换的定义的宏,函数省略:
#define pletoh16(p) ((uint16_t) \
((uint16_t)*((const uint8_t *)(p)+1)<<8| \
(uint16_t)*((const uint8_t *)(p)+0)<<0))
#define pletoh24(p) ((uint32_t)*((const uint8_t *)(p)+2)<<16| \
(uint32_t)*((const uint8_t *)(p)+1)<<8| \
(uint32_t)*((const uint8_t *)(p)+0)<<0)
#define pletoh32(p) ((uint32_t)*((const uint8_t *)(p)+3)<<24| \
(uint32_t)*((const uint8_t *)(p)+2)<<16| \
(uint32_t)*((const uint8_t *)(p)+1)<<8| \
(uint32_t)*((const uint8_t *)(p)+0)<<0)
#define pletoh40(p) ((uint64_t)*((const uint8_t *)(p)+4)<<32| \
(uint64_t)*((const uint8_t *)(p)+3)<<24| \
(uint64_t)*((const uint8_t *)(p)+2)<<16| \
(uint64_t)*((const uint8_t *)(p)+1)<<8| \
(uint64_t)*((const uint8_t *)(p)+0)<<0)
#define pletoh48(p) ((uint64_t)*((const uint8_t *)(p)+5)<<40| \
(uint64_t)*((const uint8_t *)(p)+4)<<32| \
(uint64_t)*((const uint8_t *)(p)+3)<<24| \
(uint64_t)*((const uint8_t *)(p)+2)<<16| \
(uint64_t)*((const uint8_t *)(p)+1)<<8| \
(uint64_t)*((const uint8_t *)(p)+0)<<0)
#define pletoh56(p) ((uint64_t)*((const uint8_t *)(p)+6)<<48| \
(uint64_t)*((const uint8_t *)(p)+5)<<40| \
(uint64_t)*((const uint8_t *)(p)+4)<<32| \
(uint64_t)*((const uint8_t *)(p)+3)<<24| \
(uint64_t)*((const uint8_t *)(p)+2)<<16| \
(uint64_t)*((const uint8_t *)(p)+1)<<8| \
(uint64_t)*((const uint8_t *)(p)+0)<<0)
#define pletoh64(p) ((uint64_t)*((const uint8_t *)(p)+7)<<56| \
(uint64_t)*((const uint8_t *)(p)+6)<<48| \
(uint64_t)*((const uint8_t *)(p)+5)<<40| \
(uint64_t)*((const uint8_t *)(p)+4)<<32| \
(uint64_t)*((const uint8_t *)(p)+3)<<24| \
(uint64_t)*((const uint8_t *)(p)+2)<<16| \
(uint64_t)*((const uint8_t *)(p)+1)<<8| \
(uint64_t)*((const uint8_t *)(p)+0)<<0)
将十六进制转化为字符串
int strtohex(unsigned char *str, int len, char *hexstr)
{
int i, j;
unsigned char c;
static char binhex[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
for(i = 0, j = 0; i < len; i++)
{
c = *str++;
hexstr[j++] = binhex[c>>4];
hexstr[j++] = binhex[c&0xf];
}
hexstr[j]= '\0';
return j;
}
或者
void strtohex(unsigned char*str, int len, char*hexstr)
{
int i;
char cTemp;
for (i = 0; i < len; i++)
{
cTemp = (str[i] & 0xf0) >> 4;
hexstr[i * 2] = (cTemp > 9) ? cTemp + 0x41 - 10 : cTemp + 0x30;
cTemp = str[i] & 0xf;
hexstr[i * 2 + 1] = (cTemp > 9) ? cTemp + 0x41 - 10 : cTemp + 0x30;
}
}
或者
void strtohex(unsigned char *str, int len, char *hexstr)
{
int i, j = 0;
for(i =0 ; i < len; i++)
{
sprintf(hexstr + 2 * i,"%02X", str[i]);
}
}
将字符串转化为十六进制
void hextostr(char*hexstr, int len, unsigned char*str)
{
int i;
char cTemp;
for (i = 0; i < len; i++)
{
cTemp = hexstr[i * 2];
if (cTemp >= 'A' && cTemp <= 'F')
cTemp = cTemp - 'A' + 10;
else if (cTemp >= 'a' && cTemp <= 'f')
cTemp = cTemp - 'a' + 10;
else
cTemp &= 0x0f;
str[i] = cTemp << 4;
cTemp = hexstr[i * 2 + 1];
if (cTemp >= 'A' && cTemp <= 'F')
cTemp = cTemp - 'A' + 10;
else if (cTemp >= 'a' && cTemp <= 'f')
cTemp = cTemp - 'a' + 10;
else
cTemp &= 0x0f;
str[i] += cTemp;
}
return;
}
或者
int hextostr(char *hexstr, int len, unsigned char *str)
{
int i, j = 0;
char temp[2] = {0};
for(i =0 ; i < len; i+=2)
{
memcpy(temp, hexstr + i, 2);
sscanf(temp, "%02X", &str[j]);
j++;
}
return j;
}
注意以上函数没有检查数组的长度,需提前检查长度以防溢出。
如果要截取的长度超过4个字节,用memcpy()函数来实现比较好,如果数据包的是字符串形式的也可以用strcpy(),strncpy(),sprintf(),strlen()等函数实现,建议用memcpy函数
进行各种拷贝。
因最近需要处理IP地址,IP地址在数据包中的形式是32位数字,我们用long int 类型进行存储,点分十进制IP地址用字符串存储,下面是两种形式的转换函数。
长整型转换为字符串
void *long2str(long int num, char *str)
{
sprintf(str, "%u.%u.%u.%u", num >>24 & 0xFF, num >>16 & 0xFF, num >>8 & 0xFF, num >>0 & 0xFF);
}
字符串转换为长整型
long int str2long(char *str)
{
return ntohl(inet_addr(str));
}
说明:inet_addr()和ntohl ()函数需要包含<sys/socket.h>和<arpa/inet.h>系统头文件。
inet_addr()函数是将字符串转换为网络字节序返回值为无符号长整型,ntohl ()函数是将网络字节序转换为主机字 节序长整型。可用127.0.0.1:2130706433测试。
我们抓取smtp或者pop3等邮件协议数据包,然后打开经常看到乱码,那是因为数据经过了base64编码或者Quoted-printable 编码,下面是两种编解码的实现(仅针对字符集为utf-8比编码)。
base64 它是一种基于用64个可打印字符来表示二进制数据的表示方法。它通常用作存储、传输一些二进制数据编码方法!也是MIME(多用途互联网邮件扩展,主要用作电子邮件标准)中一种可打印字符表示二进制数据的常见编码方法!它其实只是定义用可打印字符传输内容一种方法,并不会产生新的字符集!
由于2的6次方等于64,所以可以用每6个位元为一个单元,对应某个可打印字符。我们知道三个字节有24个位元,就可以刚好对应于4个Base64单元,即3个字节需要用4个Base64的可打印字符来表示。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9 ,这样共有62个字符,此外两个可打印符号在不同的系统中一般有所不同。但是,我们经常所说的Base64另外2个字符是:“+/”
base64 编码
const char base64char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
int base64_encode(const unsigned char *src_data, int src_len, char *dst_data, int dst_len)
{
int i = 0, j = 0;
int index = 0;
for( ; i < src_len; i += 3)
{
//第一个字节,直接取第一个前6位
index = (int)((src_data[i] >> 2) & 0x3f);
dst_data[j++] = base64char[index];
//第二个字节,取第一个字节的后两位和第二个字节的前四位
index = (int)((src_data[i] << 4) & 0x30);
if(i + 1 < src_len)
{
index |= ((src_data[i + 1] >> 4) & 0x0f);
dst_data[j++] = base64char[index];
}
else
{
dst_data[j++] = base64char[index];
dst_data[j++] = '=';
dst_data[j++] = '=';
}
//第三个字节,取第二个字节后四位和第三个字节前两位
index = ((src_data[i + 1] << 2) & 0x3c);
if(i + 2 < src_len)
{
index |= ((src_data[i + 2] >> 6) & 0x03);
dst_data[j++] = base64char[index];
//第四个字节直接取第三个字节后6六位
index = src_data[i + 2] & 0x3f;
dst_data[j++] = base64char[index];
}
else
{
dst_data[j++] = base64char[index];
dst_data[j++] = '=';
}
}
dst_data[j] = '\0';
return j;
}
base64 解码
int base64_decode(const char * base64, unsigned char * dedata)
{
int i = 0, j=0;
int trans[4] = {0,0,0,0};
for ( ;base64[i] != '\0'; i += 4)
{
// 每四个一组,译码成三个字符
trans[0] = num_strchr(base64char, base64[i]);
trans[1] = num_strchr(base64char, base64[i+1]);
// 1/3
dedata[j++] = ((trans[0] << 2) & 0xfc) | ((trans[1]>>4) & 0x03);
if (base64[i+2] == '=')
{
continue;
}
else
{
trans[2] = num_strchr(base64char, base64[i + 2]);
}
// 2/3
dedata[j++] = ((trans[1] << 4) & 0xf0) | ((trans[2] >> 2) & 0x0f);
if (base64[i + 3] == '=')
{
continue;
}
else
{
trans[3] = num_strchr(base64char, base64[i + 3]);
}
// 3/3
dedata[j++] = ((trans[2] << 6) & 0xc0) | (trans[3] & 0x3f);
}
dedata[j] = '\0';
return j;
}
Quoted-printable可译为“可打印字符引用编码”,编码常用在电子邮件中, 在邮件里面我们常需要用可打印的ASCII字符 (如字母、数字与"=")表示各种编码格式下的字符!Quoted-printable将任何8-bit字节值可编码为3个字符:一个等号"=“后跟随两个十六进制数字(0–9或A–F)表示该字节的数值。例如,ASCII码换页符(十进制值为12)可以表示为”=0C", 等号"="(十进制值为61)必须表示为"=3D",gb2312下“中”表示为=D6=D0。除了可打印ASCII字符与换行符以外,所有字符必须表示为这种格式。
Quoted-printable 编码
#define MAX_LINE_LEN 76 //每行最大长度
int EncodeQuoted(const unsigned char* src_data, int src_len, char* dst_data, int dst_len)
{
int nDstLen = 0;
for(int i = 0; i < src_len; i++, src_data++)
{
if(dst_len < nDstLen + 3)
{
return nDstLen;
}
/*ASCII 33-60, 62-126原样输出,其余的需编码*/
if((*src_data >= '!') && (*src_data <= '~') && (*src_data != '='))
{
*dst_data++ = (char)*src_data;
nDstLen++;
}
else
{
sprintf(dst_data, "=%02X", *src_data);
dst_data += 3;
nDstLen += 3;
}
printf("***[%d][%s]***\n", nDstLen, dst_data);
/*软换行*/
if (nDstLen == MAX_LINE_LEN - 3)
{
sprintf(dst_data, "=/r/n");
dst_data += 3;
nDstLen += 3;
}
}
/*结束符*/
*dst_data = '\0';
return nDstLen;
}
Quoted-printable 解码
//Quote-Printable 解码
int DecodeQuoted(const char* src_data, int src_len, char* dst_data)
{
int dst_len = 0;
int i = 0;
while(i < src_len)
{
if(strncmp(src_data, "=/r/n", 3) == 0)
{
src_data += 3;
i += 3;
}
else
{
if(*src_data == '=')
{
src_data++;
char temp[2] = {0};
memcpy(temp, src_data, 2);
sscanf(temp, "%02X", &dst_data[0]);
dst_data++;
src_data += 2;
i += 3;
}
else
{
*dst_data++ = *src_data++;
i++;
}
dst_len++;
}
}
*dst_data = '\0';
return dst_len;
}
utf-8 和gbk字符集转换
将传入的gbk格式的编码转换成为utf-8形式的编码
int g2u(char *from_str, size_t fromlen, char *to_str, size_t tolen)
{
int rv = convert("gb2312", "utf-8", from_str, fromlen, to_str, tolen);
return rv;
}
//将传入的utf-8格式的编码转换成为gbk形式的编码
int u2g(char *from_str, size_t fromlen, char *to_str, size_t tolen)
{
return convert("utf-8", "gb2312", from_str, fromlen, to_str, tolen);
return 0;
}
//在两种类形的字符编码之间转换
int convert(const char *from_charset, const char *to_charset, char *from_str, size_t fromlen, char *to_str, size_t tolen)
{
iconv_t cd;
char **pin = &from_str;
char **pout = &to_str;
cd = iconv_open(to_charset, from_charset);
if (cd == 0)
{
printf("iconv_open failed!\n");
return -1;
}
memset(to_str, 0, tolen);
if (iconv(cd, pin, &fromlen, pout, &tolen) == -1)
{
printf("from_str:%s\niconv failed!\n", from_str);
iconv_close(cd);
return -1;
}
iconv_close(cd);
return 0;
}
本文章持续更新中~