개요
친척에게 몇년 전에 선물받은 삼성 T5를 잘 사용하고 있었습니다.
그런데 매번 장치를 연결할 때마다 삼성 전용 프로그램으로 비밀번호 해제를 해제해야 한다는 것에 매우 큰 불만을 가지고 있었습니다!!
왜냐하면 그냥 비밀번호를 없애서 편하게 사용할 수도 있지만 제가 만약 이 SSD를 잃어버린다면 저의 데이터가 안전하지 않을 테니까요..
그리고 무엇보다 중요한 것은 개발자의 숙명입니다.. 아무리 사소한 것이라고 해도 목숨을 걸고 자동화에 집착하는 것.. 비밀번호 입력 자동화 프로그램을 만들고 디버깅하고 삽질하는 총 시간이, 평생을 앉아서 비밀번호 입력할 수 있는 시간보다 훨씬 크다고 해도 자동화에 목숨을 거는 것.. 그것이 개발자의 길 아니겠습니까?
개발 과정
처음에는 GUI 기반으로 만들어 보려고 spy++ 툴로 비번 입력 박스를 찾아서 그곳에 키보드 메시지를 보내는 방법을 썼습니다.
하지만 계속 써보니까 단점이 명확하더라고요.. 비번 입력 타이밍이 안맞는다거나 하는 상황이 생겼습니다.
그래서 직접 전용 잠금해제 프로그램(아래 영상에 보이는)을 리버싱해서 SSD에 어떤 커맨드를 보내는지 캡쳐해서 그것을 그대로 보내는 코드를 작성했습니다..
아래는 소스코드입니다.
https://github.com/minmoong/samsung-t5-auto-unlock
GitHub - minmoong/samsung-t5-auto-unlock: Samsung T5 SSD auto unlocker using SCSI protocol
Samsung T5 SSD auto unlocker using SCSI protocol. Contribute to minmoong/samsung-t5-auto-unlock development by creating an account on GitHub.
github.com
언락 과정은 다음과 같습니다.
첫 번째 DeviceIoControl 요청을 통해 비밀번호 암호화 단계에서 사용될 비밀번호 뒤에 붙이는 문자열들을 받아오고 그 둘을 붙인 다음 그것을 sha256 해싱합니다. 그리고 다시 받아온 문자열을 비밀번호와 붙이고 한번 더 sha256 해싱합니다.
두 번째 요청을 통해 해싱한 문자열을 암호화된 비밀번호로써 SSD로 보냅니다.
세 번째 요청을 통해 SSD 재연결을 요청합니다. 이 요청이 완료되면 자동으로 잠금 해제가 되면서 파일 탐색기가 뜹니다.
(아래 영상 참고)
이것을 응용하여 아래와 같이 윈도우의 작업 스케쥴러에 등록해서 사용할 수 있도록 코드를 짜줍니다.
USB 연결이 감지되면 곧바로 SSD에 연결 해제 절차를 진행할 수 있도록!!!!
#include <windows.h>
#include <dbt.h>
#include <initguid.h>
#include <usbiodef.h>
#include <ntddscsi.h>
#include <setupapi.h>
#include "sha256.h"
#pragma comment(lib, "setupapi.lib")
#define PASSWORD "asdf"
typedef struct {
SCSI_PASS_THROUGH_DIRECT sptd;
ULONG Filler;
UCHAR ucSenseBuf[64];
} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
void HideConsole() {
ShowWindow(GetConsoleWindow(), SW_HIDE);
}
int tryUnlock() {
const DWORD DATA_BUFFER_SIZE = 0x200;
HANDLE hDevice = INVALID_HANDLE_VALUE;
UCHAR* dataBuffer = NULL;
UCHAR* passwordBuffer = NULL;
UCHAR* dummyBuffer = NULL;
UCHAR cdb[16] = {
0x85, 0x08, 0x2e, 0x00,
0xd5, 0x00, 0x01, 0x00,
0xd7, 0x00, 0x4f, 0x00,
0xc2, 0x00, 0xb0, 0x00,
};
UCHAR cdb2[16] = {
0x85, 0x0a, 0x26, 0x00,
0xd6, 0x00, 0x01, 0x00,
0xc6, 0x00, 0x4f, 0x00,
0xc2, 0x00, 0xb0, 0x00,
};
UCHAR cdb3[16] = {
0x85, 0x0a, 0x26, 0x00,
0xd6, 0x00, 0x01, 0x00,
0xca, 0x00, 0x4f, 0x00,
0xc2, 0x00, 0xb0, 0x00,
};
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sptdwb = { 0 };
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sptdwb2 = { 0 };
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sptdwb3 = { 0 };
DWORD bytesReturned = 0;
DWORD bytesReturned2 = 0;
DWORD bytesReturned3 = 0;
BOOL success = false;
UCHAR* hashedPasswordBuffer = NULL;
printf("Opening SSD...\n");
// TODO: 더 정확한 방식(VID(Vender ID)와 PID(Product ID)를 이용한 방식)으로 바꾸기
for (int i = 0; i < 10; i++) {
hDevice = CreateFile(
L"\\\\.\\PhysicalDrive1",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hDevice != INVALID_HANDLE_VALUE)
break;
Sleep(500);
}
if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device after retries.\n");
goto cleanup;
}
// ================= PACKET 1 =================
// 두번째 패킷에 쓸 비밀번호를 암호화하기 위한 정보들을 SSD로부터 얻어오는 요청
dataBuffer = (UCHAR*)malloc(DATA_BUFFER_SIZE);
if (!dataBuffer) {
printf("Failed to allocate data buffer. Error: %d\n", GetLastError());
goto cleanup;
}
memset(dataBuffer, 0, DATA_BUFFER_SIZE);
sptdwb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
sptdwb.sptd.ScsiStatus = 0;
sptdwb.sptd.PathId = 0;
sptdwb.sptd.TargetId = 0;
sptdwb.sptd.Lun = 0;
sptdwb.sptd.CdbLength = 0x10;
sptdwb.sptd.SenseInfoLength = 0x20;
sptdwb.sptd.DataIn = SCSI_IOCTL_DATA_IN;
sptdwb.sptd.DataTransferLength = DATA_BUFFER_SIZE;
sptdwb.sptd.TimeOutValue = 0x1e;
sptdwb.sptd.DataBuffer = dataBuffer;
sptdwb.sptd.SenseInfoOffset = 0x30;
memcpy(sptdwb.sptd.Cdb, cdb, sizeof(cdb));
success = DeviceIoControl(
hDevice,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptdwb,
sizeof(sptdwb),
&sptdwb,
sizeof(sptdwb),
&bytesReturned,
NULL
);
if (!success) {
printf("First DeviceIoControl failed. Error: %d\n", GetLastError());
goto cleanup;
}
hashedPasswordBuffer = (UCHAR*)malloc(0x40);
if (!dataBuffer) {
printf("Failed to allocate hashed password buffer. Error: %d\n", GetLastError());
goto cleanup;
}
memset(hashedPasswordBuffer, 0, 0x40);
memcpy(hashedPasswordBuffer, PASSWORD, strnlen(PASSWORD, 0x20));
memcpy(hashedPasswordBuffer + 0x20, dataBuffer + 0x30, 0x10);
sha256_easy_hash(hashedPasswordBuffer, 0x30, hashedPasswordBuffer);
memcpy(hashedPasswordBuffer + 0x20, dataBuffer, 0x10);
memcpy(hashedPasswordBuffer + 0x30, dataBuffer + 0x10, 0x10);
sha256_easy_hash(hashedPasswordBuffer, 0x40, hashedPasswordBuffer);
// ================= PACKET 2 =================
// 암호화된 비밀번호를 SSD로 전송하는 요청
passwordBuffer = (UCHAR*)malloc(DATA_BUFFER_SIZE);
if (!passwordBuffer) {
printf("Failed to allocate password buffer. Error: %d\n", GetLastError());
goto cleanup;
}
memset(passwordBuffer, 0, DATA_BUFFER_SIZE);
memcpy(passwordBuffer + 0x20, hashedPasswordBuffer, 0x20);
sptdwb2.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
sptdwb2.sptd.ScsiStatus = 0;
sptdwb2.sptd.PathId = 0;
sptdwb2.sptd.TargetId = 0;
sptdwb2.sptd.Lun = 0;
sptdwb2.sptd.CdbLength = 0x10;
sptdwb2.sptd.SenseInfoLength = 0x20;
sptdwb2.sptd.DataIn = SCSI_IOCTL_DATA_OUT;
sptdwb2.sptd.DataTransferLength = DATA_BUFFER_SIZE;
sptdwb2.sptd.TimeOutValue = 0x1e;
sptdwb2.sptd.DataBuffer = passwordBuffer;
sptdwb2.sptd.SenseInfoOffset = 0x30;
memcpy(sptdwb2.sptd.Cdb, cdb2, sizeof(cdb2));
success = DeviceIoControl(
hDevice,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptdwb2,
sizeof(sptdwb2),
&sptdwb2,
sizeof(sptdwb2),
&bytesReturned2,
NULL
);
if (!success) {
printf("Second DeviceIoControl failed. Error: %d\n", GetLastError());
goto cleanup;
}
if ((sptdwb2.ucSenseBuf[21] & 0x21) != 0 && sptdwb2.ucSenseBuf[11]) {
printf("Wrong password.\n");
goto cleanup;
}
// ================= PACKET 3 =================
dummyBuffer = (UCHAR*)malloc(DATA_BUFFER_SIZE);
if (!dummyBuffer) {
printf("Failed to allocate dummy buffer. Error: %d\n", GetLastError());
goto cleanup;
}
memset(dummyBuffer, 0, DATA_BUFFER_SIZE);
sptdwb3.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
sptdwb3.sptd.ScsiStatus = 0;
sptdwb3.sptd.PathId = 0;
sptdwb3.sptd.TargetId = 0;
sptdwb3.sptd.Lun = 0;
sptdwb3.sptd.CdbLength = 0x10;
sptdwb3.sptd.SenseInfoLength = 0x20;
sptdwb3.sptd.DataIn = SCSI_IOCTL_DATA_OUT;
sptdwb3.sptd.DataTransferLength = DATA_BUFFER_SIZE;
sptdwb3.sptd.TimeOutValue = 0x1e;
sptdwb3.sptd.DataBuffer = dummyBuffer;
sptdwb3.sptd.SenseInfoOffset = 0x30;
memcpy(sptdwb3.sptd.Cdb, cdb3, sizeof(cdb3));
success = DeviceIoControl(
hDevice,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptdwb3,
sizeof(sptdwb3),
&sptdwb3,
sizeof(sptdwb3),
&bytesReturned3,
NULL
);
if (!success) {
printf("Third DeviceIoControl failed. Error: %d\n", GetLastError());
goto cleanup;
}
// TODO: 더 정확한 unlock sign 찾아내기
if (sptdwb3.sptd.ScsiStatus == 0x0) {
printf("Unlock succeed.");
}
else {
printf("Unlock failed.");
}
cleanup:
CloseHandle(hDevice);
free(dataBuffer);
free(passwordBuffer);
free(dummyBuffer);
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DEVICECHANGE:
{
PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
switch (wParam)
{
case DBT_DEVICEARRIVAL:
if (pHdr && pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
auto pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
if (wcsstr(pDevInf->dbcc_name, L"VID_04E8") != NULL
&& wcsstr(pDevInf->dbcc_name, L"PID_61F6") != NULL)
{
Sleep(2000);
printf("Samsung T5 detected. Trying to unlock...\n");
tryUnlock();
}
}
break;
case DBT_DEVICEREMOVECOMPLETE:
break;
default:
break;
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int main()
{
HideConsole();
tryUnlock();
WNDCLASS wc = {};
wc.lpfnWndProc = WndProc;
wc.lpszClassName = L"USBWatcherWindow";
RegisterClass(&wc);
HWND hWnd = CreateWindowEx(
0,
wc.lpszClassName,
L"USBWatcher",
0,
0, 0, 0, 0,
HWND_MESSAGE,
nullptr,
nullptr,
nullptr);
if (!hWnd)
{
return 1;
}
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter = {};
NotificationFilter.dbcc_size = sizeof(NotificationFilter);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE;
HDEVNOTIFY hDevNotify = RegisterDeviceNotification(
hWnd,
&NotificationFilter,
DEVICE_NOTIFY_WINDOW_HANDLE);
if (!hDevNotify)
{
return 1;
}
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnregisterDeviceNotification(hDevNotify);
return 0;
}
아 그리고 가장 중요한 얘기를 안할 뻔했습니다. 리눅스 전용 T5 해제 프로그램은 없습니다. 그래서 리눅스 환경에서 T5를 사용하려면 비밀번호를 삭제한 상태에서 비밀번호 없이 사용해야만 합니다.. 머 WINE 같은 에뮬레이터로 SSD 해제 프로그램을 돌려서 해제할 수 있을지는 모르겠으나 이것 또한 제가 해결해볼 수 있는 과제 아니겠습니까?
비밀번호 변경/삭제 요청도 분석하고 코드 리팩토링을 좀 하고 요청들을 라이브러리화해서 리눅스에서 T5의 모든 기능을 커맨드 형태로 이용 가능하게 만드는 것도 정말 재밌을 것 같습니다!!!!!!!
수능 3일 남음
수능이 3일 남았지만 저는 수능을 안봅니다.. 최저 없는 수시 6장을 썼기 때문이지요
그 덕분에 열심히 컴퓨터 공부를 할 수 있었습니다.
이제 학교 갈 일이 얼마 남지 않았는데(교외체험학습 20일을 빼면 20일정도만 나가면 됩니다..)
학교생활을 별 일 없이 잘 마무리지을 수 있으면 좋겠습니다.
'정보보안 > 리버싱' 카테고리의 다른 글
| 밀크초코 FPS 게임에서 채팅 버그로 다른 사람 사칭이 가능한 트릭 - 기술적 분석 (1) | 2025.12.28 |
|---|---|
| 추억의 게임 렙업만이살길2 리버싱 일기 - 2편 (완) (4) | 2025.06.03 |
| 추억의 게임 렙업만이살길2 리버싱 일기 - 1편 (1) | 2025.06.01 |
| DLL Injection 기법 - 프로세스에 침투하기 (0) | 2024.02.08 |
| 윈도우 이벤트 메시지 후킹 기법 (0) | 2024.02.06 |
