首页
社区
课程
招聘
[原创]逆向记事本看UTF8编码判断错误
2009-11-12 16:58 11802

[原创]逆向记事本看UTF8编码判断错误

2009-11-12 16:58
11802
本人菜鸟,有不对的地方请各位大侠轻点拍砖,谢谢

  打开记事本,输入“去”字,用ANSI方式保存。再重新打开,如果不出意外的话,看到的竟然是乱码。为了搞清楚“去”到底怎么变的身,开始了我非常不熟练的IDA+WinDbg逆向,权当是练手。

  首先用UE以十六进制的方式打开txt文件,其十六进制内容竟然是 0xFEFF 0x0225。0xFEFF是UNICODE编码的签名字节,而“去”的UNICODE编码是0x53bb,此处的0x0225显然是错误的。起初怀疑是记事本在保存时,就以其他的编码当作UNICODE写入到了文件中。WinDbg载入,跟踪到notepad!SaveFile,仔细看了一下,保存的过程如下:WideCharToMultiByte( GetACP(), ... , lpEditWCharBuf, ... );然后直接WriteFile写入到txt文件中,并没有进行任何附加的操作。那么多余的0xFEFF UNICODE签字和0x0225哪儿来的呢?无奈自己手写了CreateFile、ReadFile,终于看清,txt文件里保存的确实是“去”的ANSI编码的两个字节:0xA5C8。原来UE在以16进制方式查看有问题的txt文件时,也犯了跟记事本一样的解码错误。

  重新从记事本载入文件入手。从LoadFile跟踪到NpOpenDialogHookProc,一直到fDetermineFileType,终于找到了问题的根源:notepad!IsTextUTF8函数。先说说fDetermineFileType,其汇编代码不长,如下:

; int __stdcall fDetermineFileType(LPVOID lpBuffer,int cb)
_fDetermineFileType@8 proc near         ; CODE XREF: NpOpenDialogHookProc(x,x,x,x)+24A p
lpBuffer        = dword ptr  8
cb              = dword ptr  0Ch

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                push    edi
                mov     edi, [ebp+cb]
                xor     eax, eax
                cmp     edi, 1
                jbe     short loc_10023FF
                push    esi
                mov     esi, [ebp+lpBuffer]
                movzx   ecx, word ptr [esi]
                cmp     ecx, 0BBEFh
                jz      short loc_10023F0
                cmp     ecx, 0FEFFh
                jz      short loc_10023D7
                cmp     ecx, 0FFFEh
                jz      short loc_10023EC
                push    edi             ; cb
                push    esi             ; lpBuffer
                call    _IsInputTextUnicode@8 ; IsInputTextUnicode(x,x)
                test    eax, eax
                jz      short loc_10023DC

loc_10023D7:
                xor     eax, eax
                inc     eax
                jmp     short loc_10023FE

loc_10023DC:
                push    edi
                push    esi
                call    _IsTextUTF8@8   ; IsTextUTF8(x,x)
                neg     eax
                sbb     eax, eax
                and     eax, 3
                jmp     short loc_10023FE
loc_10023EC:                           
                push    2
                jmp     short loc_10023FD
loc_10023F0:                           
                cmp     edi, 2
                jbe     short loc_10023FE
                cmp     byte ptr [esi+2], 0BFh
                jnz     short loc_10023FE
                push    3

loc_10023FD:                           
                pop     eax

loc_10023FE:                           
                                       
                pop     esi

loc_10023FF:                           
                pop     edi
                pop     ebp
                retn    8
_fDetermineFileType@8 endp

  翻译成C代码更直观:
int __stdcall fDetermineFileType(LPVOID lpBuffer,int cb)
{
        int iType = 0;
        WORD wSign = 0;

        if( cb <= 1 )
                return 0;

        wSign = *(PWORD)lpBuffer;
        switch( wSign )
        {
        case 0xBBEF:
                {
                        if( cb >= 3 && (PBYTE)lpBuffer[3] == 0xBF)
                                iType = 3;
                }
                break;
        case 0xFEFF:
                {
                        iType = 1;
                }
                break;
        case 0xFFFE:
                {
                        iType = 2;
                }
                break;
        default:
                {
                        if( !IsInputTextUnicode( lpBuffer, cb ) )
                        {
                                if( IsTextUTF8( lpBuffer, cb ) )
                                        iType = 3;
                        }
                        else
                                iType = 1;
                }
        }
        return iType;
}
  当记事本打开任何一个文件时,首先会调用这个函数确定这个文本文件的编码方式。对于ANSI编码的GBXXXX标准的中文字符来说,直到IsTextUTF8函数返回FALSE,Notepad.exe才会以CP_ACP的方式调用MultiByteToWideChar,直到这时中文字符才会被正确解析,也就是说,如果IsTextUTF8返回了TRUE,那么ANSI的中文字符就会被当成UTF8编码转换成Unicode再显示出来。问题的根源就是IsTextUTF8了。看MS的家伙们是怎么写的IsTextUTF8:
; __stdcall IsTextUTF8(x,x)
_IsTextUTF8@8   proc near               ; CODE XREF: fDetermineFileType(x,x)

Buffer          = dword ptr  8
Size            = dword ptr  0Ch

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                push    esi
                xor     esi, esi
                xor     ecx, ecx
                inc     esi
                xor     edx, edx
                cmp     [ebp+Size], ecx
                jle     short FALSE

Loop:                                 
                mov     eax, [ebp+Buffer]
                mov     al, [ecx+eax]
                test    al, al
                jns     short MayBeAscii
                xor     esi, esi

MayBeAscii:                        
                test    edx, edx
                jnz     short LeftBytes
                cmp     al, 80h
                jb      short LoopContinue

BytesCount:                           
                shl     al, 1
                inc     edx
                test    al, al
                js      short BytesCount
                dec     edx
                jz      short FALSE
                jmp     short LoopContinue

LeftBytes:                           
                and     al, 0C0h
                cmp     al, 80h
                jnz     short FALSE
                dec     edx

LoopContinue:                        
                inc     ecx
                cmp     ecx, [ebp+Size]
                jl      short Loop
                test    edx, edx
                ja      short FALSE
                test    esi, esi
                jz      short TRUE

FALSE:         
                xor     eax, eax
                jmp     short QUIT

TRUE:            
                xor     eax, eax
                inc     eax
QUIT:           
                pop     esi
                pop     ebp
                retn    8
_IsTextUTF8@8   endp
还是翻译成C:
BOOL IsTextUTF8( LPSTR lpBuffer, int iBufSize )
{
        /*
        0zzzzzzz;
        110yyyyy, 10zzzzzz
        1110xxxx, 10yyyyyy, 10zzzzzz
        11110www, 10xxxxxx, 10yyyyyy, 10zzzzzz
        */
       
        int iLeftBytes = 0;
        BOOL bUtf8 = FALSE;
        if( iBufSize <= 0 )
                return FALSE;

        for( int i=0;i<iBufSize;i++)
        {
                char c = lpBuffer[i];
                if( c < 0 )                                //至少有一个字节最高位被置位
                        bUtf8 = TRUE;
                if( iLeftBytes == 0 )//之前尚无UTF-8编码的字符的前导字节,或者是下个字符。
                {
                        if( c >= 0 )        //0000 0000 - 0100 0000
                                continue;
                        do//统计出高位连续的的个数
                        {
                                c <<= 1;
                                iLeftBytes++;
                        }while( c < 0 );
                        iLeftBytes--;                //表示本字符的剩余字节的个数;
                        if( iLeftBytes == 0 )//最高位是10,不能作为UTF-8编码的字符的前导字节。
                                return FALSE;
                }
                else                                         
                {
                        c &= 0xC0;                         //1100 0000
                        if( c != (char)0x80 )//1000 0000 对于合法的UTF-8编码,非前导字节的前两位必须为。
                                return 0;
                        else
                                iLeftBytes--;
                }
        }
        if( iLeftBytes )
                return FALSE;
        return bUtf8;
}
  根据国际相关组织的规定,UTF8用1-4个字节来表示UNICODE,对于绝对值在7F以上的字符,UTF-8用两个及以上的字节表示,其中第一个字节的高位连续的1的个数,表示“用UTF8编码表示的字符的字节数”,此字符其他的字节必须是以10开头。如某UTF8编码的字符,第一个字节为110X XXXX,那么说明表示此字符的UTF-8编码要用到两个字节,其第二个字节必须是10YY YYYY。否则就是非法的UTF8编码。
  这样说来,只要其ANSI编码值为0x8yCx— 0xByCx和0x8yDx-0xByDx字符,都被会解释成UTF-8编码:
        WORD wChar = 0;
        char szBuf[3]={0};
        for( wChar = 0x80C0; wChar < 0xBFDF; )
        {
                *(PWORD)szBuf = wChar;
                cout<<szBuf;
                if( (wChar & 0xFF) == 0xDF )//11011111
                {
                        wChar += 0x100;
                        wChar &= 0xFFC0;
                }
                else
                        wChar += 1;
        }
  这个编码范围的汉字,绝大多数我还是不认识的,但常用的还是有不少。以至于你写个“小丫头”、“泉水”、“来去”、“千十”等常用字保存,再用记事本打开,看到的都是乱码。
  至此,“去”字引起的乱码现象,终于得到了完满解决。

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞7
打赏
分享
最新回复 (12)
雪    币: 95
活跃值: (419)
能力值: ( LV9,RANK:310 )
在线值:
发帖
回帖
粉丝
wxxw 6 2009-11-12 18:27
2
0
牛,顶一下,虽然碰到过这问题,却没本事分析
雪    币: 291
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leking 2009-11-12 22:44
3
0
分析的精彩,此帖应精华
雪    币: 254
活跃值: (30)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
margen 1 2009-11-12 23:37
4
0
谢谢鼓励
雪    币: 367
活跃值: (20)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
morning 1 2009-11-13 09:27
5
0
对于没有BOM的文件,先尝试用当前页码(CP_ANSI)转成UNICODE,如果失败,再尝试用UTF-8转成UNICODE应该可以解决此问题.
另外,大多数windows下的UTF-8文件都是带有BOM的,所以,简单的把把所有没有BOM且不符合UTF-8规则的文件作为当前代码页ANSI来处理也可算是一个取巧的办法,呵呵
雪    币: 1085
活跃值: (114)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
gaollxu 2 2010-5-24 18:05
6
0
超级大牛。
拜读了。
雪    币: 2174
活跃值: (961)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Crakme 2010-5-25 11:04
7
0
功力尚浅
竟然没看明白
雪    币: 208
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hardcode 2010-5-26 21:03
8
0
if( cb >= 3 && (PBYTE)lpBuffer[3] == 0xBF)
这一行似乎应该是
if( cb >= 3 && (PBYTE)lpBuffer[2] == 0xBF)

另外,UE的二进制比较功能是可以看到其真实二进制字节的
雪    币: 208
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
iicup 2010-5-30 06:57
9
0
有这种事情,不错不错。
雪    币: 220
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sasahsasah 2010-5-30 09:04
10
0
原来是这样,佩服lz的钻研精神
雪    币: 256
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kondo 2010-6-5 17:33
11
0
WinDbg载入,跟踪到notepad!SaveFile,仔细看了一下,保存的过程如下:WideCharToMultiByte( GetACP(), ... , lpEditWCharBuf, ... );然后直接WriteFile写入到txt文件中,并没有进行任何附加的操作

请问下 这个地方你在WinDbg中是怎么操作的?非常感谢
雪    币: 403
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
baiyingli 2010-6-5 18:14
12
0
佩服 讲的 很 精彩
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cyjmas 2010-6-5 19:51
13
0
补充下,默认打开方式的制式不一样,用ansi方式保存的用ansi方式打开是正常的,同理。
游客
登录 | 注册 方可回帖
返回