ida에서 __usercall로 해석되는 함수의 경우는 2~3개 정도의 인자를 불특정 레지스터에 저장하고 사용하기 때문에
C에서 제공하는 calling convention으로 함수를 정의할 수 없었습니다.
(있다면 댓글로 자비좀 베풀어주세요.. )

어쩔 수 없이 naked형태로 후킹 함수를 구현하였습니다.


후킹 대상인 __usercall 함수는 4개의 인자를 사용하고 있습니다.
int __usercall 대상함수(int a1@<edx>, int a2@<esi>, int a3, int a4)
호출 시 레지스터는 유지하고, a3, a4만 push합니다.
호출이 끝나면 push한 만큼 esp를 복원해주고(hook함수 종료 시 mov esp, ebp를 해주니 복원할 필요가 없을듯하나 습관적으로 해주는것이 좋을듯) , 리턴값이 담긴 eax를 백업합니다.
esi와 edx는 __usercall 함수 내부에서 retn전에 pop을 해주기 때문에 따로 백업하지 않아도 됩니다.

이후에 각종 파라미터를 빼내는 함수를 호출하는데, __stdcall로 선언했으므로 따로 스택정리는 하지 않았습니다. 

 pTrampoline는 jmp 다음 명령어를 가리키도록 해주면 됩니다.



다른 컴파일러로 개발할 일 생기면 추가 예정


 

 Visual C

 

GCC 

 

 Bit

32 

64 

32 

64 

 INT 

 4

 LONG

 4

 FLOAT

 4

8

 DOUBLE

 4

8

8

8


멀티 소스로 개발시 LONG형이 문제가 될 수 있다. 특히 long 으로 리턴받는 api사용 시 주의.


8바이트를 쓰고자 한다면, int64_t / __int64_t 로 선언하면 어느 쪽에서도 확실하게 8바이트를 사용할 수 있다.

ScanBase64Function.dll


메모리에서 base64의 인코딩 및 디코딩 테이블을 검색한 후, 해당 테이블 주소를 text섹션에서 참조하는 코드 주소를 찾아냅니다.

char MimeBase64[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };   char(혹은 int) DecodeMimeBase64[256] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, -1, 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,-1,-1,-1,-1,-1, -1,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,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 };  


사용 방법 : 플러그인 메뉴에서 Scan을 클릭하면, 검색 결과를 로그에 출력합니다.

속도가 매우 느립니다. (-_-)


실행 결과 예시: 

jscript.dll의 0x3F1E8630에서 base64 테이블을 검색하였습니다. encode용도일 가능성이 있습니다.

해당 주소를 6개의 코드에서 참조하고 있습니다.


개선해야할 내용

- 테이블을 사용하지 않는 BASE64 함수 검색

- encode / decode 용도 정확하게 구분하기

- 패커 (더미다 등) 적용시 확인해보지 못하였음




Windows 10 기준으로,

C:\Program Files\NPKI\yessign\USER 디렉토리를 보면 공인인증서가 있습니다.

signCert.crt / signPri.key

signPri.key에서 개인키를 추출하고 RSA구조체로 변환할 것입니다.


1. SEED 대칭키 / IV 구하기

1) 개인키 파일에서 salt와 iteration 값을 추출합니다. PKCS#8 규격에 맞춰서 추출합니다.

# 8바이트 salt 추출하기 (20번째 바이트에서 8바이트 추출)

BYTE bySalt[8] = { 0, };

memcpy_s(bySalt, 8, (BYTE*)byFileBuffer + 20, 8);

# iteration 값 추출하기 (30번째 바이트에서 2바이트 추출)

WORD wIteration = _byteswap_ushort(*((WORD*)byFileBuffer + 15));

빅 엔디언을 리틀 엔디언으로 변경해줍니다.


2) KEY와 IV만들기

공인인증서 개인키는 SEED-CBC-128로 암호화되어 있습니다.

복호화할 KEY와 IV를 PBKDF1규격에 따라 만듭니다.

공인인증서 패스워드에 salt를 붙인다음 iteration 값 만큼 SHA1하며, 최종 값에서 첫 16바이트를 KEY로 사용합니다.


최종 HASH값에서 뒤 4바이트를 잘라낸 뒤 SHA1하여 DIV를 만듭니다. DIV에서 앞 16바이트를 잘라서 IV로 사용합니다.


2. SEED로 개인키 복호화

1에서 구한 키와 IV로 SEED-CBC-128 복호화하면 ASN.1 DER형태의 바이너리가 나옵니다.

복호화 할 오프셋은 파일의 36번째부터 끝까지입니다.


3. RSA 구조체로 변경하기

ASN.1을 PKCS8 구조체로 변환 후 RSA구조체로 변환합니다.

4. 개인키로 평문 암호화 


'PKI' 카테고리의 다른 글

전자서명(pkcs#7)과 전자봉투  (0) 2019.12.22
openssl rsa 키 생성 테스트  (1) 2016.01.01
[OpenSSL] Convert RSA Structure to PEM  (0) 2014.12.16

git 서버가 클라이언트 인증서를 요청한다면 git에 클라이언트 인증서를 설정하여야 한다.

만약 유효한 클라이언트 인증서를 설정을 하지 않으면 403에러가 발생한다.



클라이언트 인증서를 설정하기 위해 cmd에서 아래 두 줄을 실행한다.

git config --global http.sslKey "개인키파일 절대경로"

git config --global http.sslCert "공개키파일 절대경로"

경로가 올바르지 않다면 에러가 발생하므로 주의한다. 만약 --global로 되지 않을 경우 --local, --system으로도 시도해본다.


# 만약 인증서가 PKCS12형태로 저장되어 있다면 openssl로 포팅을 한다.

openssl pkcs12 -in 인증서.pfx -clcerts -nokeys -out 공개키파일

openssl pkcs12 -in 인증서.pfx -nocerts -out 개인키파일 -nodes

# 설정을 잘못한 경우 삭제한다.

git config --global --unset http.sslKey

git config --global --unset http.sslcert




SourceTree에서 사설인증서를 사용하는 git 서버에 접근할 경우 아래와 같은 에러가 발생한다.



git 툴은 자체 CA 파일을 참조하기 때문에 Windows의 certmgr에 사설 인증서를 추가해도 인식을 하지 못한다. C:\Program Files\Git\mingw64\ssl\certs\ca-bundle.crt 파일의 맨 끝에 직접 인증서를 추가하면 된다.

(인증서 체인 상의 모든 인증서(루트, 중간 등)를 여기에 추가하면 된다.)

Visual Studio에서 git을 사용하는 경우에는 certmgr에 사설 인증서를 추가하면 된다.



다운로드 페이지 : https://bonobogitserver.com/

테스트 환경 : Windows 2012 r2 Datacenter / bonobo 5.1.1


1.     관리에서 역할 및 기능 추가선택


2.     위 그림의 창이 뜰 때까지 다음을 클릭하고 웹 서버(IIS)”를 선택 후 다음클릭


3.     “ASP.NET 4.5”를 선택하고 다음클릭


4.     “ASP.NET 4.5”를 선택하고 다음 클릭


5.     필요한 경우 자동으로 대상 서버 다시 시작선택 후 설치클릭


6.     c:\inetpub\wwwroot에 압축을 해제한 후 c:\inetpub\wwwroot\Bonobo.Git.Server\App_Data 폴더를 마우스 우 클릭 후 속성” -> “보안” 탭을 클릭한 후 편집을 클릭



7.     “IIS_IUSRS” 선택 후 모든 권한허용을 선택한 후 확인을 클릭


8.     도구에서 “IIS(인터넷 정보 서비스) 관리자클릭


9.     좌측 트리 메뉴를 위 그림과 같이 펼친 후 “Bonobo.Git.Server”를 마우스 우 클릭한 후 응용 프로그램으로 변환클릭


10.     선택을 클릭


11.     “.NET v4.5”를 선택하고 확인


12.     브라우저에서 http://localhost/Bonobo.Git.Server 를 입력하여 접속이 되는지 확인한다.




Microsoft 오피스 패스워드 검증과 문서 암호 해제

오피스 문서 포맷에 걸린 패스워드 검증과 문서 암호 해제에 대하여 알아봅니다.

테스트 환경 : Windows 7 & Office 2013 (엑셀 및 워드)

# 패스워드 검증 

먼저, 패스워드를 걸은 문서를 준비합니다.
오피스에서 패스워드를 설정할때 읽기 패스워드와 쓰기 패스워드를 설정해야 하는데 본 포스팅에서는 읽기 패스워드 검증만 다룹니다.

SSView.zip

오피스 문서의 구조를 볼 수 있는 Structured Storage Viewer로 문서 파일을 열어보면, EncryptionInfo에서 암호화 정보를 XML로 확인할 수 있습니다. 제 문서의 경우는 아래와 같았습니다.



아래 링크에 각 항목에 대한 설명이 있습니다.

읽기 패스워드를 검증하기 위해선 아래의 keyEncryptor 구조만 보면 됩니다. 
대략 살펴보면 반복 해시 10만번, salt 16바이트, AES256-CBC, SHA512 정도가 보입니다.
해당 정보들은 오피스 버전마다 다릅니다. 버전이 올라갈수록 암호화 강도도 세집니다.
(오피스 2007의 경우 반복 해시 0번에 SHA을 사용합니다.)

패스워드를 검증하는데 필요한 항목은 saltValue, encryptedVerifierHashInput, encryptedVerifierHashValue 이 세가지 입니다. 디코드하면 각각 16바이트, 16바이트, 64바이트입니다. 

salt는 사용자 입력 패스워드에 덧붙이는 랜덤 난수 생성 값으로 복호화 시 IV에 사용됩니다.
encryptedVerifierHashInput와 encryptedVerifierHashValue는 암호화 된 검증용 값들로 패스워드 해시로 유도한 대칭키로 복호화 한 후 패스워드가 올바른지 검증하는데 사용합니다.

패스워드를 검증하는 일련의 과정을 간략하게 도식화하면 아래와 같습니다.


[키 생성]


[키 검증]


첫번째 그림은 키 생성부입니다. 2개의 대칭키(복호화 키)를 뽑아내고 있습니다. 1 ~ 5)번까지의 과정입니다. 두번째 그림은 키 검증부입니다. 6 ~ 8)번까지의 과정입니다.

 

1) 16바이트 salt와 사용자 입력 패스워드를 sha512 해시(이하 해시)합니다.

2) 현재의 spin count를 입력하고 1)에서 생성한 해시를 붙입니다. (spin count (4바이트) + hash (64바이트))

3) 2)를 해시하고 spin count 뒤에 붙입니다. 이를 10만번 반복합니다. 최종적으로 패스워드 해시가 생성됩니다.


4) hash input 블록키를 패스워드 해시 뒤에 붙이고 해시합니다. 해당 해시는 encryptedVerifierHashInput의 대칭키가 됩니다.

5) hash value 블록키를 패스워드 해시 뒤에 붙이고 해시합니다. encryptedVerifierHashValue의 대칭키가 됩니다.

이제 얻어낸 키로 암호화 된 2개의 데이터를 복호화 해야 합니다.

AES 256비트와 CBC모드를 사용중이며 IV는 32바이트인데 salt (16바이트) + 패딩 0 (16바이트)로 되어 있습니다.

6) 4, 5)에서 얻어낸 각각의 대칭키를 넣고 복호화를 하여 16바이트 / 64바이트 데이터를 얻습니다.

7) 복호화 된 hash input 데이터를 해시한 후 복호화 된 hash value와 비교합니다.

8) 일치하면 올바른 패스워드입니다. 


# 문서 암호 해제 

복호화를 하기 전에 원본 문서 길이(복호화 길이)와 암호화된 문서 데이터를 알아야 합니다.


문서의 구조를 보면 EncryptedPackage 라는 항목이 있는데 원본 문서를 암호화 한 데이터가 들어가 있는 항목입니다.
첫번째 8바이트는 복호화 길이로 0x2071 = 8305바이트를 포함한다고 명시되어 있습니다.
암호화 된 데이터의 위치는 오프셋 0x8부터 끝까지입니다.

다시 xml구조를 봅니다. 문서를 복호화하는데 필요한 항목은 패스워드 검증할 때와 조금 다릅니다. 
keyEncryptor 구조의 encryptedKeyValue와 keyData 구조의 saltValue만 있으면 됩니다.

encryptedKeyValue는 암호화 된 대칭키입니다. 이 키로 문서를 복호화합니다. saltValue는 IV를 생성할 때 사용하는 salt입니다. 디코드를 하면 각각 32바이트, 16바이트입니다.

복호화 시 중요한 점은 문서 전체를 한번에 복호화 하는 것이 아닌 블록 단위 (4096바이트)로 합니다. 이 과정에서 IV는 매번 다른 값을 사용합니다.

1) key 블록키를 패스워드 해시 뒤에 붙이고 해시합니다. 해당 해시는 encryptedKeyValue의 대칭키가 됩니다. (블록키는 소스를 참조)

2) 1)에서 얻어낸 키로 복호화를 하여 32바이트 대칭키를 얻습니다.


3) IV연산은 16바이트 salt 에 4바이트 블록키를 더해서 생성합니다. 디폴트 블록키는 0이며 한 사이클당 1씩 증가합니다.

4) salt+블록키 = 20바이트를 해시합니다.

5) 해시된 데이터를 32바이트 컷합니다. (해시에 1 2 3  ... 64가 담겼다고 가정할 경우 32에서 컷) 해당 데이터를 IV로 사용합니다.

6) 대칭키와 IV, 그리고 암호화된 문서 블록(4096바이트)를 가지고 복호화합니다.

7) 3)부터 N번만큼 복호화를 반복합니다. 마지막 복호화의 길이는 원본문서길이 % 블록 사이즈입니다. 원본 길이가 8305이면 N은 3, 마지막 암/복호화 길이는 113입니다.

복호화된 데이터는 오피스에서 바로 오픈할 수 있습니다.


클라이언트 : 192.168.123.129 (Windows 7 + chrome)

서버 : 192.168.123.127 (Ubuntu + apache 기본 설정)

클라이언트의 웹 브라우저에서 http://192.168.123.127을 입력했을 시 TCP계층에서 어떤 데이터들이 오고가는지 와이어샤크를 통해서 간략하게 살펴봅니다. 

 와이어샤크 패킷 다운로드 

wireshark.pcapng


[클릭클릭 확대해서 봅니다]


[간략 표]



#1. 3-way 핸드셰이크

SYN : 통신 개시를 요청

ACK : 통신 확인에 대해 알겠다고 확인 응답

[No. 1] 클라이언트는 서버에게 통신 시작을 요청합니다. (SYN)

[No. 2] 서버는 알겠다고 응답함과 동시에 클라이언트에게 통신 시작을 요청합니다. (SYN & ACK)

[No. 3] 클라이언트는 서버에게 알겠다고 응답합니다. (ACK)


이 일련의 과정을 3-way 핸드셰이크라 합니다.


#2. web page 요청 및 다운로드

[No. 4] 악수(?)가 끝나고 난 후 클라이언트는 웹 페이지를 요청합니다.

[No. 5] 서버는 알겠다고 응답합니다.

[No. 6~14] 서버가 index.html을 클라이언트에 전송합니다. 총 7517바이트를 전송하는데 한번에 보내지 않고 잘라서 전송을 합니다. 여기서는 6개로 자르는데 각각의 덩어리를 세그먼트라 칭합니다. 여기서는 각 세그먼트당 1460바이트입니다. (1460바이트 * 5개 + 마지막 세그먼트 217바이트) 덩어리로 자른 후에 번호를 붙이게 되는데 0,1,2,3,4번 이렇게 순차적인 번호를 붙이는게 아니라 바이트로 번호를 매깁니다. 1, 1461, 2921, 4381, 5841, 7301번으로 나뉘어 있습니다. 이를 Sequence 번호라 합니다. 좀 더 부연설명을 하자면 xxx번 포트에서 연결 유지 중인 전체 데이터 중 몇 번째 바이트에 해당하는지 인덱싱하는 것입니다. 여기서는 이미 핸드셰이크에서 1바이트(No.2 패킷)가 추가되었으므로 0번 세그먼트가 아닌 1번 세그먼트(1바이트)부터 시작하는 것 입니다. 서버와 클라이언트의 Sequence 번호는 각각 인덱싱됩니다.


여기서는 서버가 2개의 덩어리를 보낸 후 제대로 데이터가 전달됐는지 확인응답을 받는 구조입니다. [No.8] 응답 패킷을 보면 Acknowledgment 번호(다음에 상대방으로부터 받아야 할 Sequence 번호)가 2921번이라고 나오는데 이는 서버로부터 2921바이트(1460바이트*2 + 1바이트(No.2 패킷의 SYN비트))까지 정상적으로 다운로드 하였다는 것을 의미합니다. 0번째바이트부터 계산되므로 2920번째 바이트까지라고 보면됩니다. (서버야 난 2920번째 바이트까지 무사히 받았고 그 다음엔 2921번째가 필요하니 패킷 내놔라)

그런데 잠깐.. 와이어샤크 덤프에서는 세그먼트는 5개만 보이는데 마지막 7301번은 어디에 있을까요? 그것은 200 응답의 [No. 13]의 패킷에 포함되어 있습니다.

No.13 패킷에는 분리된 세그먼트에 대한 정보도 포함되어 있습니다.

이 정보를 바탕으로 클라이언트에서 세그먼트들을 다시 합칩니다. 합친 내용을 아래 그림과 같이 브라우저가 출력합니다. 

[No. 13 패킷의 일부분]

추가적으로 [No. 4] & [No. 14]에 PSH (PUSH 비트)가 붙었는데 이는 애플리케이션 계층으로 토스하라는 의미입니다. (즉, Apache 혹은 크롬으로 토스)


#3. 통신 종료


FIN : 통신 종료를 요청

RST : 통신을 강제로 해제

[No. 15] 서버는 클라이언트에게 통신을 끝내자고 요청합니다. (FIN & ACK)

[No. 16] 클라이언트는 알겠다고 응답합니다. (ACK)

<< 16과 17사이에 수초 간의 텀이 있습니다. >> 

[No. 17] 클라이언트는 서버에게 통신을 끝내자고 요청합니다. (FIN & ACK)

[No. 18] 클라이언트는 통신을 강제로 해제합니다. (RST & ACK) (의도되지 않은 게 올라왔네요. 무시합니다.)

[No. 19] 서버는 클라이언트에게 알겠다고 응답합니다. No.17에 대한 응답입니다. (ACK)



1. windows 에서 리눅스를 설치할 파티션 만듦

2. 설치 시 현재 windows boot manager 설치되었다고 나옴. 첫번째 windows boot manager 어쩌고 옵션 선택

-> 빈공간에 우분투가 설치됨

3. 설치 완료 후 http://sourceforge.net/projects/boot-repair-cd/files/ 에서 iso를 받은 후 해당 iso로 usb 부팅

-> 이 방법을 사용하지 않으면 try ubuntu로 부팅 후 랜카드 잡고 boot-repair 설치하고 복잡함 (심지어 인터넷에 있는 방법으로는 없는 패키지라고 나옴)

4. 재부팅하면 boot repair 창이 뜨는데 recommended repair 선택 후 완료되면 ctrl_alt_t 를 눌러 reboot로 재부팅

5. windows 10 으로 부팅 후 cmd를 관리자 모드로 실행하여 

bcdedit /set "{bootmgr}" path \EFI\ubuntu\grubx64.efi

6. 재부팅하면 grub으로 멀티부팅 가능함

+ Recent posts