2009年11月19日星期四

eD2k Hash in Ruby

很久以来一直都以为eD2k Hash就是MD4(罪魁祸首),这几天才发现错得很离谱。

花了点时间查资料,写了个用以生成eD2k链接的模块,包括AICH部分。代码有个偷懒的地方,读取文件的缓存大小必须为PART_SIZE,如果要拿去用的话请留意。Ruby 1.8.6和1.8.7的File.size在Windows平台下有bug,当文件尺寸比较大时会返回负数,这一点也请留意。

部分代码移植自pyaich和Python的Base64模块,GPL许可证。

require 'openssl'

module OtNtH
  module Digest
    # fit for eD2k Hash
    PART_SIZE = 9_728_000
    BLOCK_SIZE = 184_320

    class ED2K
      def initialize
        @h = ''
        @l = 0
      end
      
      def update(part)
        @h += OpenSSL::Digest::MD4.new(part).digest
        @l = part.size
      end
      
      def hexdigest
        @h += OpenSSL::Digest::MD4.new('').digest if @l == PART_SIZE
        OpenSSL::Digest::MD4.new(@h).hexdigest
      end
      
      def name
        'ED2K'
      end
    end # class ED2K

    class AICH
      class HashTree
        attr_accessor :dataSize, :isLeft, :baseSize, :hash, :leftTree, :rightTree
        
        def initialize(dataSize, isLeftBranch, baseSize)
          @dataSize = dataSize
          @isLeft = isLeftBranch
          @baseSize = baseSize
          @hash = nil
          @leftTree = nil
          @rightTree = nil
        end
        
        def loadLowestLevelHashes(hashList)
          if @dataSize <= @baseSize then
            @hash = hashList.shift
            return true
          else
            extra = @dataSize % @baseSize != 0 ? 1 : 0
            numBlocks = @dataSize / @baseSize + extra
            extra = isLeft ? 1 : 0
            sizeLeft = ((numBlocks + extra)/2) * @baseSize
            sizeRight = @dataSize - sizeLeft
            leftBaseSize = sizeLeft <= PART_SIZE ? BLOCK_SIZE : PART_SIZE
            @leftTree = HashTree.new(sizeLeft, true, leftBaseSize)
            
            rightBaseSize = sizeRight <= PART_SIZE ? BLOCK_SIZE : PART_SIZE
            @rightTree = HashTree.new(sizeRight, false, rightBaseSize)
            return @leftTree.loadLowestLevelHashes(hashList) && @rightTree.loadLowestLevelHashes(hashList)
          end
        end
        
        def reCalculateHash
          if @leftTree and @rightTree then
            return false if (not @leftTree.reCalculateHash) or (not @rightTree.reCalculateHash)
            h = OpenSSL::Digest::SHA1.new
            h.update(@leftTree.hash)
            h.update(@rightTree.hash)
            @hash = h.digest
            return true
          else
            return true
          end
        end
      end
      
      def initialize
        @hash_list = []
        @data_size = 0
      end
      
      def update(part)
        psize = part.size
        @data_size += psize
        pos = 0
        while pos < psize
          @hash_list.push OpenSSL::Digest::SHA1.new(part[pos, BLOCK_SIZE]).digest
          pos += BLOCK_SIZE
        end
      end
      
      def hexdigest
        blockSize = @data_size <= PART_SIZE ? BLOCK_SIZE : PART_SIZE
        tree = HashTree.new(@data_size, true, blockSize)
        tree.loadLowestLevelHashes(@hash_list)
        tree.reCalculateHash
        return Base32.encode32(tree.hash)
      end
      
      def name
        'ED2K'
      end
    end # class AICH
  end # module Digest
  
  module Base32
    B32TAB = 'abcdefghijklmnopqrstuvwxyz23456789'
    module_function
    
    def encode32(str)
      parts = []
      quanta, leftover = str.size.divmod(5)
      if leftover != 0 then
        str += ("\0" * (5 - leftover))
        quanta += 1
      end
      quanta.times do |i|
        c1, c2, c3 = str[i*5..(i+1)*5].unpack('nnC')
        c2 += (c1 & 1) << 16
        c3 += (c2 & 3) << 8
        parts.push(
          B32TAB[c1 >> 11],
          B32TAB[(c1 >> 6) & 0x1f],
          B32TAB[(c1 >> 1) & 0x1f],
          B32TAB[c2 >> 12],
          B32TAB[(c2 >> 7) & 0x1f],
          B32TAB[(c2 >> 2) & 0x1f],
          B32TAB[c3 >> 5],
          B32TAB[c3 & 0x1f]
        )
      end
      encoded = parts.join('')
      case leftover
      when 1
        encoded[0..-6] + '====='
      when 2
        encoded[0..-4] + '===='
      when 3
        encoded[0..-3] + '==='
      when 4
        encoded[0..-1] + '='
      else
        encoded
      end
    end
  end # module Base32
end # module OtNtH

if __FILE__ == $0 then
  exit if not File.exists?(ARGV[0])
  a = OtNtH::Digest::AICH.new
  e = OtNtH::Digest::ED2K.new
  File.open(ARGV[0], 'rb') do |f|
    buf = ''
    while f.read(OtNtH::Digest::PART_SIZE, buf)
      a.update(buf)
      e.update(buf)
    end
  end
  puts "ed2k://|file|#{File.basename(ARGV[0])}|#{File.size(ARGV[0])}|#{e.hexdigest}|h=#{a.hexdigest}|/"
end

2009年9月19日星期六

小改ImageZ

从网上抓图,Imagez是个很不错的程序。可是在该程序在选择保存目录的时候,无法设置起始目录,也不能直接输入已存在路径,只能从我的电脑开始一级一级点。虽然官网上说0.7Final会提供该功能,可一年半后的现在版本号都0.82了却仍然没有兑现,说不得只好自己想办法了。

选择目录的API函数是SHBrowseForFolder,通过LPBROWSEINFO的pidRoot和ulFlags两个成员可以实现我上面说到功能。pidRoot没办法,ulFlags只需要加上BIF_EDITBOX即可,这倒是可以动动脑筋。

用UPX解压后再用FileInfo查看可得知,ImageZ是用VB6写的。而VB6调用外部API都是动态载入DLL,用W32DASM静态分析会比较困难,所以还是请出大神OllyDbg。

在OllyDbg中打开ImageZ.exe,F9执行。Alt+E,找到shell32双击。Ctrl+N,找到SHBrowseForFolderA,F2下断点。切换到ImageZ窗口,点击保存按钮,跳回OllyDbg。按Alt+F9执行到用户代码,再切回ImageZ取消掉选择目录的窗口。

0042AE69    894D BC         mov dword ptr ss:[ebp-44],ecx
0042AE6C    C745 CC 4100000>mov dword ptr ss:[ebp-34],41
0042AE73    E8 20A8FEFF     call ImageZ.00415698
0042AE78    8B1D B0104000   mov ebx,dword ptr ds:[<&MSVBVM60.__vbaSetSy>; MSVBVM60.__vbaSetSystemError
0042AE7E    8BF8            mov edi,eax

0042AE6C这一行就是我们要找的东西,其含义为将ulFlags设置为41。光标移动到这一行,按空格,将41改为51。鼠标右键,“复制到可执行文件”,在弹出的窗口中右键,保存。

好久好久没弄类似的东西,手好生的说。

Update 18:03 2009-11-3

ImageZ 0.82p2中改用GetOpenFileName选择文件夹,虽然提供了设置初始目录的功能,但仍然不允许手工输入目录,也不能记忆上次选择的路径,真不知道作者究竟是怎么想的。

定制GetOpenFileName对话框的外观无法通过修改OPENFILENAME结构体的成员来实现,必须要挂一个消息钩子处理WM_INITDIALOG,向需要隐藏或修改内容的控件发送CDM_HIDECONTROL、CDM_SETCONTROLTEXT等消息,所以断点需要下在SendMessage上。

大概的修改过程都跟上次的差不多,以下是我找到的代码:

0045B6AE   /0F85 28010000   jnz ImageZ.0045B7DC
0045B6B4   |8D4D A0         lea ecx,dword ptr ss:[ebp-60]
0045B6B7   |895D A0         mov dword ptr ss:[ebp-60],ebx
0045B6BA   |51              push ecx
0045B6BB   |68 7C040000     push 47C
0045B6C0   |68 69040000     push 469
0045B6C5   |57              push edi
0045B6C6   |E8 5565FBFF     call ImageZ.00411C20
0045B6CB   |FFD6            call esi

CDM_HIDECONTROL的值为0x469,所以0045B6C0这一行就是我们要找的东西了,将该行以及其下共5行push 469改为push 0即可。

2009年9月18日星期五

ActiveScriptRuby和RubyInstaller的兼容性问题

近来一直在用Luis做的RubyInstaller,这个版本的Ruby用Mingw编译,比VC6编译的快了很多。而且Mingw和VC6编译出来的东西都依赖msvcrt.dll,也算解决了Ruby极端讨人厌的DLL文件名问题。

可是RubyInstaller却跟Arton写的ActiveScriptRuby有兼容性问题,regsvr32无法注册库文件,提示“找不到指定的程序”。

用dumpbin检查了一下,Arton发布的ActiveScriptRuby从msvcrt-ruby191.dll中导入了一个vsnprintf函数,但该函数在Mingw编译的msvcrt-ruby191.dll中并不存在。

抓来ActiveScriptRuby的代码,将win32oleex.cpp第568行的vsnprintf改为_vsnprintf,使用VC6版本的include和lib文件编译通过。

写信给Arton报告了这个问题,下一个版本的ASR应该就不需要自己再改一次了。