잡다/크랙

엑셀(2010) 시트보호/통합문서보호 깨기_매크로 이용

산을좋아한라쯔 2019. 11. 2. 16:51
반응형

Excel은 시트보호를 위한 패스워드들 해시형태로 XML파일에 보관하는 방식을 사용한다.

Excel 2013 버전부터는 해시크기가 커져서 Bruteforce공격으로 깨기가 힘든데 반해, Excel 2010은 해시값이 2바이트여서 약 65536번의 시도에 의해 깰 수 있다.


Excel 2010 방식

Sheet보호는 해당 Sheet 이름의 xlm 파일내에 password의 해시값이, 전체 워크북 보호용으로는 workbook.xml파일내에 해시값이 저장되어 있다.


엑셀파일이 a.xlsx라면 이것을 a.zip으로 확장자를 바꾸고 압축을 풀면(7.zip 등으로) 압축해제된 여러 파일들을 볼 수 있다.

여기서 Sheet1용 xml파일은 "xl/worksheets/sheet1.xml", 워크북용은 "xl/workbook.xml"이다.



아래는 "123456789"라는 암호호 sheet1을 보호했을 때 저장되는 해시값이다. (xl/worksheets/sheet1.xml파일내에 있다)

<sheetProtection password="9690" sheet="1" objects="1" scenarios="1"/>

이것은 어떤 해시 알고리즘 F에 의해 암호 "123456789"가 변형되어 "9690"이 된 것이다.


  F("123456789") = "9690"


* 어떤 알고리즘인지는 아랫부분에서 설명


Excel 2013 방식

2013버전부터는 보안성이 더 강화되었다. 아래는 보호된 시트에 대한 xml 파일이다. 2010버전에 비해 해시길이가 더 길어졌고 salt가 사용되었음을 알 수 있다.


<sheetProtection algorithmName=”SHA-512″ hashValue=”kCXjmrTgwRXgMLSoZjx5f3XTt8ycTWnf3DLZLUBtCKgx2VmWzN1A2DDv0bEKG+4uT+7CENmR6KTYmxU3pCIuOg==” saltValue=”dUkmxPPBbFZ8PjKxfnl1xQ==” spinCount=”100000″ sheet=”1″ objects=”1″ scenarios=”1″ formatCells=”0″ formatColumns=”0″ formatRows=”0″ insertHyperlinks=”0″ selectLockedCells=”1″ pivotTables=”0″/>


해시 알고리즘으로 Sha2의 일종인 Sha512가 사용되었고, 이에 따라 해시값은 512비트(=64바이트)이고, salt가 사용되었기에 레인보우 테이블(미리 해시값을 왕창 계산해놓은 테이블)을 이용해서도 원래의 패스워드값을 유추할 수 없다.


해시값은 다음과 같이 계산된다.

hashValue = sha256(password || salt)


salt값은 24개의 문자이기에 대략 24*8=192비트, 따라서 해시 테이블을 만든다면 대략 2^192 이상 크기의 테이블을 만들어야는데 이는 슈퍼컴퓨터로 100억년을 돌려도 불가하다.


Excel 2010 방식에 대한 크랙 방법

위에서 기술한 바와 같이, Excel 2010 방식은 해시값으로 2바이트만을 사용하기에 2^16번 만큼의 다른 입력값으로 시도해보면 동일한 해시값을 갖는 입력값을 찾을 수 있다.


hashValue[[2] = F(password)라서, F가 아주 잘 만들어져서 충돌이 거의 없는 해시 알고리즘이라면, 2바이트에 해당하는 2^16 가지수의 입력값에 대해서 0x0000~0xFFFF까지의 hashValue를 만들어낼 것이다.


예를들어 hashValue가 0x9690이라면, 대략 2^16 개의 다른 입력값을 F에 집어넣어보면 그 중 하나는 0x9690을 해시값으로 내는 입력값을 찾을 수 있다.  


hashValue[2] = F(password)이고 password가 10자리까지 허용한다면 사용가능한 password 경우수는 대략 2^80 . (대략이라고 한 이유는 password로 사용가능한 것이 Ascii 코드 중에서 알파벳 문자, 숫자, 그리고 일부 특수문자이기에 password용 1자리가 온전하게 1바이트가 아니기 때문)

password로 사용가능한 가짓수는 대략 2^80개로 많은데, 이에 의해 만들어지는 해시값은 2^16이기에, 어떤 해시값 h에 대해서 굉장히 많은 다른 입력값이 존재할 수 있다. 즉, password "123456789"에 의해 나오는 해시값이 0x9690이면, 이 해시값이 나오는 수많은 입력값이 존재할 수 있다는 것. 이것이 Excel 2020 보호시트를 깨는 기본 원리가 된다.


매크로로 위의 원리를 이용해서 목표로 하는 해시값이 나오는 입력값을 찾는 프로그램을 짜볼텐데, 그 전에 Excel 2010에서 어떤 해시알고리즘을 쓰고 있는지 알아보자. 


시트 보호 패스워드 해시 생성 알고리즘

시트 보호에 쓰이는 패스워드는 아래 알고리즘에 의해 해시값이 생성되어 보관된다.


참조: https://www.openoffice.org/sc/excelfileformat.pdf 문서의 4.18.4

ALGORITHM Get_Password_Hash(Password)
1) hash ← 0 ; char_index ← char_count ← character count of password
2) char_index ← char_index - 1
3) char ← character from password with index char_index {0 is leftmost character}
4) hash ← hash XOR char
5) rotate the lower 15 bits of hash left by 1 bit
6) IF char_index > 0 THEN JUMP 2)
7) RETURN hash XOR char_count XOR CE4BH


위 문서에는 알고리즘이 동작하는 샘플 예도 들어져 있다.




이 알고리즘을 Excel VBA로 짜보면 다음과 같다.


Option Explicit
Function RotateToLeft(ByRef val As Integer)
  Dim a As Long
  a = val
  a = a * 2  'shift one left
  'If (a And &H8000) = &H8000 Then
  If (a And &H8000) = 32768 Then
    a = a And &H7FFF
    a = a + 1 'rotate left most one bit to the end
  End If
  RotateToLeft = a
End Function
Function GetPasswordHash(password As String) As Integer
  Dim hash As Integer
  Dim char_index As Integer, char_count As Integer
  Dim char As Byte
  '1) hash ← 0 ; char_index ← char_count ← character count of password
  hash = 0
  char_count = Len(password)
  char_index = char_count
  '2) char_index ← char_index - 1
JUMP_2:
  char_index = char_index - 1
  '3) char ← character from password with index char_index {0 is leftmost character}
  char = Asc(Mid(password, char_index + 1, 1))
  '4) hash ← hash XOR char
  hash = hash Xor char
  '5) rotate the lower 15 bits of hash left by 1 bit
  ' 006A(0000 0000 0110 1010) -> 00D4(0000 0000 1101 0100)
  hash = RotateToLeft(hash)
  '6) IF char_index > 0 THEN JUMP 2)
  If char_index > 0 Then GoTo JUMP_2
  '7) RETURN hash XOR char_count XOR CE4BH
  hash = hash Xor char_count
  hash = hash Xor &HCE4B
  GetPasswordHash = hash
End Function
Sub DoSample()
  Dim pwd As String
  Dim hash As Integer
  pwd = "abcdefghij"
  hash = GetPasswordHash(pwd)
  MsgBox "Hash is:" & Hex(hash)
End Sub



크랙 방법

Excel 2010의 시트보호를 크랙하는 가장 간단한 방법은 xml파일을 직접 수정하는 것이고 이 방법은 이 글에서 소개한 바 있다.

xml 파일을 수정하는 것이 아니라 매크로를 통해 크랙할 수 있다.

코드는 다음과 같다.


Function unprotect_sheet(ws As Worksheet) As String
  On Error Resume Next
  Application.Calculation = xlCalculationManual
  Dim i As Integer, j As Integer, k As Integer
  Dim l As Integer, m As Integer, n As Integer
  Dim i1 As Integer, i2 As Integer, i3 As Integer, i4 As Integer, i5 As Integer, i6 As Integer
  Dim pwd As String
  If ws.ProtectContents = False Then
    pwd = "already unprotected sheet"
    GoTo End_Loop
  End If
  'i=65, j=65로 고정: 총48,640개조합
  'origin은 194,560개 조합이기�-� 약4배 느림
  For i = 65 To 65: For j = 65 To 65: For k = 65 To 66
  For l = 65 To 66: For m = 65 To 66: For i1 = 65 To 66
  For i2 = 65 To 66: For i3 = 65 To 66: For i4 = 65 To 66
  For i5 = 65 To 66: For i6 = 65 To 66: For n = 32 To 126
    pwd = Chr(i) & Chr(j) & Chr(k) & Chr(l) & Chr(m) & _
          Chr(i1) & Chr(i2) & Chr(i3) & Chr(i4) & Chr(i5) & Chr(i6) & Chr(n)
    ws.Unprotect pwd
    If ws.ProtectContents = False Then
      GoTo End_Loop
    End If
  Next: Next: Next: Next: Next: Next
  Next: Next: Next: Next: Next: Next
  unprotect_sheet = "not found"
End_Loop:
  Application.Calculation = xlCalculationManual
  On Error GoTo 0  'error handling off를 해제
  unprotect_sheet = pwd
End Function

위 함수는 입력으로 받은 worksheet의 '시트 보호'를 해제한다.

코드에서는 부룻포스로 protection 깨기를 시도하고 있는데, 최대 시도 수는 48,640회이고, 이에 의해 0x8000~0xFFFF까지의 해시값이 만들어져서 시도된다.

엑셀에서 2바이트 해시값을 사용하기에 전체 65537개의 가능한 해시값이 존재할 것처럼 여겨지나, 엑셀의 해시 알고리즘 특징이 0x8000이하의 값은 만들어지지 않기에 전체 가능한 해시값은 0x8000~OxFFFF까지이다. 이 해시값을 만들어내기 위해서 12자리의 password 문자열을 만들어서 사용하는데, 이처럼 12자리의 큰 문자열을 사용하는 것은, 해시 알고리즘이 적은 문자열에 대해서는 모든 조합의 해시값을 만들어내지 못하기에 아예 처음부터 12자리를 사용하는 것이고, 대신 각 자릿수마다 많은 조합의 문자를 사용하는 것이 아니라 'A'~'B'값만을 사용해서 문자열 조합을 만들어 낸다. (맨 마지막 문자는 여러 문자를 사용)


인터넷에 소개된 코드는 위와 조금 다르다. 그 코드는 아래와 같다.

Sub PasswordBreaker()
  'Author unknown but submitted by brettdj of www.experts-exchange.com
  Dim i As Integer, j As Integer, k As Integer
  Dim l As Integer, m As Integer, n As Integer
  Dim i1 As Integer, i2 As Integer, i3 As Integer
  Dim i4 As Integer, i5 As Integer, i6 As Integer
  On Error Resume Next
  For i = 65 To 66: For j = 65 To 66: For k = 65 To 66
  For l = 65 To 66: For m = 65 To 66: For i1 = 65 To 66
  For i2 = 65 To 66: For i3 = 65 To 66: For i4 = 65 To 66
  For i5 = 65 To 66: For i6 = 65 To 66: For n = 32 To 126
 ActiveSheet.Unprotect Chr(i) & Chr(j) & Chr(k) & _
      Chr(l) & Chr(m) & Chr(i1) & Chr(i2) & Chr(i3) & _
      Chr(i4) & Chr(i5) & Chr(i6) & Chr(n)
  If ActiveSheet.ProtectContents = False Then
      MsgBox one usable password is " & Chr(i) & Chr(j) & _
          Chr(k) & Chr(l) & Chr(m) & Chr(i1) & Chr(i2) & _
          Chr(i3) & Chr(i4) & Chr(i5) & Chr(i6) & Chr(n)
   ActiveWorkbook.Sheets(1).Select
   Range("a1").FormulaR1C1 = Chr(i) & Chr(j) & _
          Chr(k) & Chr(l) & Chr(m) & Chr(i1) & Chr(i2) & _
          Chr(i3) & Chr(i4) & Chr(i5) & Chr(i6) & Chr(n)
       Exit Sub
  End If
  Next: Next: Next: Next: Next: Next
  Next: Next: Next: Next: Next: Next
End Sub

 

이 코드는 현재 Active된 시트를 unprotect하는데, 패스워드 조합 수가 194,560회로 좀 많다. 이를 48,560회로 약1/4정도 조합수로도 찾아낼 수 있게 개선한 것이 위쪽에 있는 코드이다.


실제로 개선된 코드가 0x8000~0xFFFF까지의 모든 해시값을 만들어낼 수 있는 지는 아래 코드를 수행해서 알아낼 수 있고, 실제 조사해보면 모든 조합을 만족한다.


Sub imporved_crack_test()
  Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("가능조합2")
  Dim i As Integer, j As Integer, k As Integer
  Dim l As Integer, m As Integer, n As Integer
  Dim i1 As Integer, i2 As Integer, i3 As Integer
  Dim i4 As Integer, i5 As Integer, i6 As Integer
  Dim pwd As String, hash_val As Integer, r As Long
  Application.Calculation = xlCalculationManual
  ws.Activate
  r = 2
  'i=65, j=65로 고정: 총48,640개조합
  'origin은 194,560개 조합이기�-� 약4배 느림
  For i = 65 To 65: For j = 65 To 65: For k = 65 To 66
  For l = 65 To 66: For m = 65 To 66: For i1 = 65 To 66
  For i2 = 65 To 66: For i3 = 65 To 66: For i4 = 65 To 66
  For i5 = 65 To 66: For i6 = 65 To 66: For n = 32 To 126
  pwd = Chr(i) & Chr(j) & Chr(k) & Chr(l) & Chr(m) & _
        Chr(i1) & Chr(i2) & Chr(i3) & Chr(i4) & Chr(i5) & Chr(i6) & Chr(n)
  Cells(r, 1).Value = pwd
  Cells(r, 2).Value = engine_module.GetPasswordHash(pwd)
  r = r + 1
  Next: Next: Next: Next: Next: Next
  Next: Next: Next: Next: Next: Next
  Application.Calculation = xlCalculationAutomatic
End Sub



이제 이렇게 만들어진 코드를 이용해서 어떤 엑셀 파일에 있는 모든 시트의 보호를 해제하는 것도 가능한데, 그 코드는 다음과 같다.

Sub unprotect_all_sheets()
  Dim ws As Worksheet
  Dim pwd As String
  For Each ws In ThisWorkbook.Sheets
    pwd = unprotect_sheet(ws)
  Next ws
End Sub



지금까지 기술한 모든 코드 및 테스트 데이터가 들어 있는 엑셀 파일은 다음과 같다.

Excel2010_CrackMethod_v1.xlsm



-끝-


Excel2010_CrackMethod_v1.xlsm
8.57MB
반응형