0x00 前言
看了wooyun上的一篇帖子“
安全漏洞本质扯谈之决战汇编代码 ”,觉得里面一些存在漏洞的代码例子还不错,下面对里面的示例存在的问题进行个人的分析,比较浅显,不到之处还请指出来,有些例子我也不知道问题在哪
0x01 源代码层面的例子
1. Link Attack
*unix 下的 link attack,有意识到吗?
if(access("file",W_OK)!=0){
exit(1);
}
fd = open("file",O_WRONLY);
write(fd,buffer,sizeof(buffer));
仔细看了下access函数的用法,其中有一条警告:access() always dereferences symbolic links. If you need to check the permissions on a symbolic link, use faccessat(2) with the flag AT_SYMLINK_NOFOLLOW.
意思是access函数总是会接引用符号链接,如果需要检查符号链接本身的权限,请使用faccessat替代。
也就是说如果“file”是一个符号链接的话,access检查的并不是符号链接本身,检查被绕过。不知道对不对,还请大牛们解答。
2. Integer Overflow Example in OpenSSH 3.1
发生在真实的 openssh 3.1 ,有漏洞吗?
u_int nresp;
...
nresp = packet_get_int();
if(nresp){
response = xmalloc(nresp * sizeof(char*));
for(i=0; i<nresp; i++)
response[i] = packet_get_string(NULL);
}
packet_check_eom();
这个是典型的integer overflow,xmalloc函数形式为void *xmalloc(size_t bytes),参数bytes是size_t,是无符号的。上例中nresp*sizeof(char *)在ILP32系统下相当nresp*4,明显会溢出,造成分配的内存远小于预期值。当nresp*4<nresp时,response[i] = packet_get_string(NULL)将进一步引发内存溢出。
3. Signed Integer Vulnerability Example
注意整形符号,能被绕过吗?
int read_user_data(int sockfd)
{
int length, sockfd, n;
char buffer[1024];
length = get_user_length(sockfd);
if(length > 1024){
error("illegal input, not enough room in buffer\n");
return 1;
}
if(read(sockfd, buffer, length) < 0){ error("read: %m");
return 1;
}
return 0;
}
这个是典型的无符号与有符号滥用引发的问题,length为int型,当length<0时,显然可以通过length>1024的检查。而后续又调用了read(sockfd, buffer, length),read函数的第三个参数类型为size_t类型是无符号的,length<0时,即表示的无符号值>0x80000000,造成读取的数据长度远超过1024,进而引发栈溢出。
4. Truncation Vulnerability Example in NFS
整形截断问题?
void assume_privs(unsigned short uid) {
seteuid(uid);
setuid(uid);
}
int become_user(int uid)
{
if (uid == 0)
die("root isnt allowed");
assume_privs(uid);
}
典型的整形截断问题,uid是int型,而函数assume_privs接受的参数uid为unsigned short,int转化为unsigend short发生截断,而且还存在符号问题。
5. 苹果SSL/TLS 重大安全漏洞的细节
(CVE-2014-1266)多个goto fail造成重大安全隐患。
static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,
uint8_t *signature, UInt16 signatureLen)
{
...
if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail; <---- *** DANGER ***
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
err = sslRawVerify(ctx,
ctx->peerPubKey,
dataToSign, /* plaintext */
dataToSignLen, /* plaintext length */
signature,
signatureLen);
...
fail:
SSLFreeBuffer(&signedHashes);
SSLFreeBuffer(&hashCtx);
return err;
}
这里就是 if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail; <---- *** DANGER ***
多了一个goto fail,相当于只要到了if(err = ssl...)这条语句,无论条件是否成立,都会goto fail。
6. MS-RPC DCOM Buffer Overflow
(冲击波)
冲击波蠕虫只因为一个缓冲区溢出。
HRESULT GetMachineName (WCHAR * pwszPath) {
The WCHAR wszMachineName [N + 1]);
...
LPWSTR pwszServerName = wszMachineName;
while (* pwszPath! = L '\\')
* PwszServerName + + = * pwszPath + +;
...
}
典型的栈溢出,wszMachineName[N+1]固定buffer大小为N+1,后续对wszMachineName这个buffer进行写入时,完全没有长度检查, "* PwszServerName + + = * pwszPath + +“语句不断对buffer进行写入直到*pwszPath != L'\\',比如文件路径名为'A'*N*2+L'\\',则造成溢出。
7. 有漏洞吗?
unsigned short read_length(int sockfd)
{
unsigned short len;
if(full_read(sockfd, (void *)&len, 2) != 2)
die("could not read length!\n");
return ntohs(len);
}
int read_packet(int sockfd)
{
struct header hdr;
short length;
char *buffer;
length = read_length(sockfd);
if(length > 1024){
error("read_packet: length too large: %d\n", length);
return 1;
}
buffer = (char *)malloc(length+1);
if((n = read(sockfd, buffer, length) < 0){
error("read: %m");
free(buffer);
return 1;
}
buffer[n] = '\0';
return 0;
}
这个又是无符号与有符号滥用的问题,read_length返回的unsigned short型,然后返回结果又short型的length接收,当read_length返回值大于0x8000时,后续的if length>1024的检查将被飘过。。。当length=-1,即0xffff时,malloc(length+1) <-> malloc(0),又是各种溢出。
8. 有漏洞吗?
提示: 5rOo5oSPc216ZW9m
char *read_username(int sockfd)
{
char *buffer, *style, userstring[1024];
int i;
buffer = (char *)malloc(1024);
if(!buffer){
error("buffer allocation failed: %m");
return NULL;
}
if(read(sockfd, userstring, sizeof(userstring)-1) <= 0){
free(buffer);
error("read failure: %m");
return NULL;
}
userstring[sizeof(userstring)-1] = '\0';
style = strchr(userstring, ':');
if(style)
*style++ = '\0';
sprintf(buffer, "username=%.32s", userstring);
if(style)
snprintf(buffer, sizeof(buffer)-strlen(buffer)-1,
", style=%s\n", style);
return buffer;
}
buffer和userstring大小均为1024,语句sprintf(buffer, "username=%.32s", userstring)明显会引发buffer溢出。。。
9. 有漏洞吗?
提示:
enter image description here
/* special thing for ldap.
* The parts are separated by question marks.
* From RFC 2255:
* ldapurl = scheme "://" [hostport] ["/"
* [dn ["?" [attributes] ["?" [scope]
* ["?" [filter] ["?" extensions]]]]]]
*/
if (!strncasecmp(uri, "ldap", 4))
{
char *token[5];
int c = 0;
token[0] = cp = ap_pstrdup(p, cp);
while (*cp && c < 5) {
if (*cp == '?') {
token[++c] = cp + 1;
*cp = '\0';
}
++cp;
}
while循环的判定*cp && c<5,当c=4,*(cp+4) == ‘?'时,token[++c] = cp+1语句即为token[5] = cp + 1,溢出了啊~~
10. 有漏洞吗?(Antisniff v1.1 Vulnerability)
tips
char *indx;
int count;
char nameStr[MAX_LEN]; //256
...
memset(nameStr, '\0', sizeof(nameStr));
...
indx = (char *)(pkt + rr_offset);
count = (char)*indx;
while (count){
if (strlen(nameStr) + count < ( MAX_LEN - 1) ){
(char *)indx++;
strncat(nameStr, (char *)indx, count);
indx += count;
count = (char)*indx;
strncat(nameStr, ".",
sizeof(nameStr) strlen(nameStr));
} else {
fprintf(stderr, "Alert! Someone is attempting "
"to send LONG DNS packets\n");
count = 0; }
}
nameStr[strlen(nameStr)-1] = '\0';
indx指向数据包pkt偏移为rr_offset的位置,count = (char)*indx语句即将indx指向的一字节值赋给count,关键就在count是int型,在ILP32 os下是4字节,count =(char)*indx语句会将*indx进行符号扩展到32位,对应的汇编语句为movsx。比如当*indx==0xff, 则count=0xffffffff。则当*indx>0x80时,count将<0。
while(count)只要count不为0则通过。而if (strlen(nameStr) + count < (MAX_LEN - 1)),由于count会有符号类型,虽然strlen返回的unsinged int,但无符号与有符号数运算,会提升为有符号运算,当count<0时,这个条件直接飘过。
之后strncat(nameStr, (char *)indx, count),strncat的第三个参数为size_t,即将count解释为无符号的,即(unsigned int)count,此时明显将引发nameStr溢出。。
引发这个漏洞的关键就在count = (char)*indx语句,进行了有符号扩展!!!
11. 还有漏洞吗?(Antisniff v1.1.1 Vulnerability)
char *indx;
int count;
char nameStr[MAX_LEN]; //256
...
memset(nameStr, '\0', sizeof(nameStr));
...
indx = (char *)(pkt + rr_offset);
count = (char)*indx;
while (count){
/* typecast the strlen so we aren't dependent on
the call to be properly setting to unsigned. */
if ((unsigned int)strlen(nameStr) +
(unsigned int)count < ( MAX_LEN - 1) ){
(char *)indx++;
strncat(nameStr, (char *)indx, count);
indx += count;
count = (char)*indx;
strncat(nameStr, ".",
sizeof(nameStr) strlen(nameStr));
} else {
fprintf(stderr, "Alert! Someone is attempting "
"to send LONG DNS packets\n");
count = 0;
}
}
nameStr[strlen(nameStr)-1] = '\0';
这个例子是在例11的基础上的更新版,但依然存在问题。
上面已经说了count = (char)*indx语句造成了有符号扩展,即使if判断语句修补之前的问题,但仍然没有解决问题。比如当count=0xffffffff时,(unsigned int)strlen(nameStr) + (unsigned int)count也会造成无符号加法进位啊,所以<max_len -1很好满足啊!
12. 还有漏洞吗?(Antisniff v1.1.2 Vulnerability)
unsigned char *indx;
unsigned int count;
unsigned char nameStr[MAX_LEN]; //256
...
memset(nameStr, '\0', sizeof(nameStr));
...
indx = (char *)(pkt + rr_offset);
count = (char)*indx;
while (count){
if (strlen(nameStr) + count < ( MAX_LEN - 1) ){
indx++;
strncat(nameStr, indx, count);
indx += count;
count = *indx;
strncat(nameStr, ".",
sizeof(nameStr) strlen(nameStr));
} else {
fprintf(stderr, "Alert! Someone is attempting "
"to send LONG DNS packets\n");
count = 0;
}
}
nameStr[strlen(nameStr)-1] = '\0';
这个打补丁的man仍没弄清楚bug的症结所在,把count改为unsigned int完全没用啊,count = (char)*indx语句还是有符号扩展啊,还是movsx啊。。。
0x02 汇编代码层面
1. Safe or vulnerability?
text:0040106B sub_40106B proc near ; CODE XREF: _main+58p
.text:0040106B
.text:0040106B var_10004 = byte ptr -10004h
.text:0040106B var_4 = dword ptr -4
.text:0040106B arg_0 = word ptr 8
.text:0040106B
.text:0040106B push ebp
.text:0040106C mov ebp, esp
.text:0040106E mov eax, 10004h
.text:00401073 call __alloca_probe
.text:00401078 mov eax, dword_404020
.text:0040107D xor eax, ebp
.text:0040107F mov [ebp+var_4], eax
.text:00401082 movsx eax, [ebp+arg_0]
.text:00401086 movsx eax, [ebp+eax+var_10004]
.text:0040108E push eax
.text:0040108F push offset Format ; "t %x"
.text:00401094 call ds:printf
.text:0040109A pop ecx
.text:0040109B pop ecx
.text:0040109C mov ecx, [ebp+var_4]
.text:0040109F xor ecx, ebp
.text:004010A1 xor eax, eax
.text:004010A3 call sub_401BD2
.text:004010A8 leave
汇编的这个例子,跟前文源码的例11、12、13问题是一样的,00401082处的指令movsx eax, [ebp+arg_0],对arg_0进行了有符号扩展,即只要传入参数arg_0<0,则eax<0,即eax>0x80000000,紧接着的movsx eax, [ebp+eax+var_10004],ebp+eax将可以非常大,造成内存访问异常,甚至是任意地址读啊!!
2. Safe or vulnerability?
text:004010AA sub_4010AA proc near ; CODE XREF: _main+60p
.text:004010AA
.text:004010AA var_190 = dword ptr -190h
.text:004010AA arg_0 = dword ptr 8
.text:004010AA
.text:004010AA push ebp
.text:004010AB mov ebp, esp
.text:004010AD mov eax, [ebp+arg_0]
.text:004010B0 sub esp, 190h
.text:004010B6 cmp eax, 64h
.text:004010B9 jle short loc_4010BE
.text:004010BB push 64h
.text:004010BD pop eax
.text:004010BE
.text:004010BE loc_4010BE: ; CODE XREF: sub_4010AA+Fj
.text:004010BE push [ebp+eax*4+var_190]
.text:004010C5 push offset Format ; "t %x"
.text:004010CA call ds:printf
.text:004010D0 pop ecx
cmp eax, 64h,下面是jle即进行有符号比较,当eax>0x80000000时,即eax<0<64h,完成跳转,[ebp+eax*4+var_190]将引发内存访问异常。
3. Safe or vulnerability?
.text:00401000 sub_401000 proc near ; CODE XREF: _main+48p
.text:00401000
.text:00401000 var_190 = dword ptr -190h
.text:00401000 arg_0 = dword ptr 8
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 mov eax, [ebp+arg_0]
.text:00401006 sub esp, 190h
.text:0040100C cmp eax, 64h
.text:0040100F jbe short loc_401014
.text:00401011 push 64h
.text:00401013 pop eax
.text:00401014
.text:00401014 loc_401014: ; CODE XREF: sub_401000+Fj
.text:00401014 push [ebp+eax*4+var_190]
.text:0040101B push offset Format ; "t %x"
.text:00401020 call ds:printf
.text:00401026 pop ecx
.text:00401027 pop ecx
例3将例2中jle改成了jbe即进行无符号比较,除了当arg_0==64h时,push [ebp+eax*4+var_190],即push [ebp],相当于printf原ebp外,没看到什么其他问题
,还请各位大牛解惑啊~
4. Safe or vulnerability?
.text:004010D6 sub_4010D6 proc near ; CODE XREF: _main+68p
.text:004010D6
.text:004010D6 arg_0 = dword ptr 4
.text:004010D6
.text:004010D6 push esi
.text:004010D7 push 64h ; Size
.text:004010D9 call ds:malloc
.text:004010DF mov esi, eax
.text:004010E1 mov eax, [esp+8+arg_0]
.text:004010E5 cmp eax, 64h
.text:004010E8 pop ecx
.text:004010E9 jle short loc_4010EE
.text:004010EB push 64h
.text:004010ED pop eax
.text:004010EE
.text:004010EE loc_4010EE: ; CODE XREF: sub_4010D6+13j
.text:004010EE movsx eax, byte ptr [eax+esi]
.text:004010F2 push eax
.text:004010F3 push offset Format ; "t %x"
.text:004010F8 call ds:printf
.text:004010FE push esi ; Memory
.text:004010FF call ds:free
例4与例2是一样的,cmp eax, 64h, jle有符号比较,movsx eax, byte ptr[eax+esi]将引发内存访问异常。
5. Safe or vulnerability?
. text:0040110C sub_40110C proc near ; CODE XREF: _main+70p
.text:0040110C
.text:0040110C var_19A0 = byte ptr -19A0h
.text:0040110C var_4 = dword ptr -4
.text:0040110C arg_0 = word ptr 8
.text:0040110C
.text:0040110C push ebp
.text:0040110D mov ebp, esp
.text:0040110F mov eax, 19A0h
.text:00401114 call __alloca_probe
.text:00401119 mov eax, dword_404020
.text:0040111E xor eax, ebp
.text:00401120 mov [ebp+var_4], eax
.text:00401123 movsx eax, [ebp+arg_0]
.text:00401127 movsx eax, [ebp+eax+var_19A0]
.text:0040112F push eax
.text:00401130 push offset Format ; "t %x"
.text:00401135 call ds:printf
.text:0040113B pop ecx
.text:0040113C pop ecx
.text:0040113D mov ecx, [ebp+var_4]
.text:00401140 xor ecx, ebp
例5跟上面一样,movsx eax, [ebp+arg_0]
6. Safe or vulnerability?
.text:0040102C sub_40102C proc near ; CODE XREF: _main+50p
.text:0040102C
.text:0040102C var_10004 = byte ptr -10004h
.text:0040102C var_4 = dword ptr -4
.text:0040102C arg_0 = word ptr 8
.text:0040102C
.text:0040102C push ebp
.text:0040102D mov ebp, esp
.text:0040102F mov eax, 10004h
.text:00401034 call __alloca_probe
.text:00401039 mov eax, dword_404020
.text:0040103E xor eax, ebp
.text:00401040 mov [ebp+var_4], eax
.text:00401043 movzx eax, [ebp+arg_0]
.text:00401047 movsx eax, [ebp+eax+var_10004]
.text:0040104F push eax
.text:00401050 push offset Format ; "t %x"
.text:00401055 call ds:printf
.text:0040105B pop ecx
例6将例5中movsx改成了 movzx eax, [ebp+arg_0],即不足的补0处理,除了没有对arg_0本身大小进行检查,其他没看出什么问题
7. Safe or vulnerability?
. .text:0040118A ; int __cdecl sub_40118A(LPCSTR lpMultiByteStr)
.text:0040118A sub_40118A proc near ; CODE XREF: _main+91p
.text:0040118A
.text:0040118A WideCharStr = word ptr -44h
.text:0040118A var_4 = dword ptr -4
.text:0040118A lpMultiByteStr = dword ptr 8
.text:0040118A
.text:0040118A push ebp
.text:0040118B mov ebp, esp
.text:0040118D sub esp, 44h
.text:00401190 mov eax, dword_404020
.text:00401195 xor eax, ebp
.text:00401197 mov [ebp+var_4], eax
.text:0040119A mov ecx, [ebp+lpMultiByteStr]
.text:0040119D mov eax, ecx
.text:0040119F push esi
.text:004011A0 lea esi, [eax+1]
.text:004011A3
.text:004011A3 loc_4011A3: ; CODE XREF: sub_40118A+1Ej
.text:004011A3 mov dl, [eax]
.text:004011A5 inc eax
.text:004011A6 test dl, dl
.text:004011A8 jnz short loc_4011A3
.text:004011AA push 40h ; cchWideChar
.text:004011AC lea edx, [ebp+WideCharStr]
.text:004011AF push edx ; lpWideCharStr
.text:004011B0 sub eax, esi
.text:004011B2 push eax ; cbMultiByte
.text:004011B3 push ecx ; lpMultiByteStr
.text:004011B4 push 0 ; dwFlags
.text:004011B6 push 0 ; CodePage
.text:004011B8 call ds:MultiByteToWideChar
前面代码查找lpMultiByteStr指向字符串的'\0‘结束符,从而得出字符串的大小,即sub eax, esi,eax为字符串大小,但调用MultiByteToWideChar函数进行宽字符转换时,push 40h传入了固定大小的buffer,但又没有检查原字符串的大小是否超过40个字符,即当eax>40时,溢出。
8. Safe or vulnerability?
text:00401738 Size = dword ptr 4
.text:00401738 Src = dword ptr 8
.text:00401738
.text:00401738 push esi
.text:00401739 push edi
.text:0040173A mov edi, [esp+8+Size]
.text:0040173E lea eax, [edi+1]
.text:00401741 push eax ; Size
.text:00401742 call ds:__imp__malloc
.text:00401748 mov esi, eax
.text:0040174A test esi, esi
.text:0040174C pop ecx
.text:0040174D jz short loc_401774
.text:0040174F push edi ; Size
.text:00401750 push [esp+0Ch+Src] ; Src
.text:00401754 push esi ; Dst
.text:00401755 call _memcpy
.text:0040175A push esi
.text:0040175B push offset aS ; "%s\n"
.text:00401760 mov byte ptr [esi+edi], 0
.text:00401764 call ds:__imp__printf
代码块完成两个工作:malloc(Size+1), memcpy。
问题就出在Size+1,当Size=0xffffffff时,Size+1=0,而malloc(0)的返回值并不为0,下面是msdn中对malloc的size参数为0时的解释:
If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item. Always check the return from malloc, even if the amount of memory requested is small.
因此,test esi, esi, jz不会完成跳转,但此时malloc返回的内存大小为zero-length,往该内存区域memcpy经出现异常
。
[培训]科锐逆向工程师培训第53期2025年7月8日开班!