如何用純 PHP 8 逆向工程 ZKTeco 二進制協議,打造可直接向考勤設備寫入記錄的工具
How I Reverse-Engineered ZKTeco’s Binary Protocol in Pure PHP 8 to Build a Direct Hardware Attendance Writer
作者以純 PHP 8 逆向工程 ZKTeco 二進制協議,建立 taha-rafi/php-zkteco v2.1.0,讓程式可直接向考勤設備寫入記錄。套件支援 TCP 優先並自動回退至 UDP、Laravel 8–12;42 項單元及協議測試已在 PHP 8.0 至 8.4 通過。
If you’ve ever had to integrate ZKTeco biometric time-and-attendance clocks (fingerprint scanners, facial recognition terminals, RFID readers) into a modern web stack, you already know the grim reality of the ecosystem:
- Most existing Packagist packages are 6 to 10 years old abandonware.
- They throw fatal deprecation notices on PHP 8.1, 8.2, 8.3, and 8.4.
- They are strictly UDP-only—meaning when deployed across remote offices over WAN routers or NAT firewalls, UDP packets drop silently without error messages.
- None of them support writing attendance back to the machine. If an employee forgot to clock in or your web app needs to push an authorized remote punch, you were told "it's impossible without ZKTeco's proprietary Windows C++ DLLs".
I decided to fix this once and for all. I reverse-engineered the binary protocol and built taha-rafi/php-zkteco (v2.1.0).
1. The Protocol Problem: UDP vs. TCP in Enterprise Networks
The original ZK protocol was designed in the late 1990s around raw UDP datagrams (CMD_CONNECT: 1000). While UDP is fast on a local switch, modern distributed enterprises connect clocks across branch offices, cloud VPNs, and port-forwarded WAN routers.
In php-zkteco, we implemented a Dual-Protocol Engine with automatic fallback:
use TahaRafi\ZKTeco\ZKTeco;
use TahaRafi\ZKTeco\Constants;
// Automatically tries TCP stream first, then transparently falls back to UDP
$zk = new ZKTeco('192.168.1.201', 4370, commKey: 0, protocol: Constants::PROTOCOL_AUTO);
if ($zk->connectWithRetry(maxRetries: 3, retryDelayMs: 400)) {
echo "Connected via: " . $zk->getActiveProtocol(); // TCP or UDP
}
The TCP implementation encapsulates packets with a 32-bit length header (0x5050827D) and handles keep-alive pinging, ensuring zero packet drops across WAN routers.
2. Reverse-Engineering Direct Hardware Attendance Writing (CMD_SET_DATA 3001)
Reading logs from a biometric clock is easy (CMD_ATTLOG_RRQ: 501). But what happens when an HR manager manually approves a punch in your Laravel app and you need the physical biometric terminal to recognize it?
Through packet analysis of ZKTeco's internal protocol, we uncovered the payload schema required to write directly to the machine's internal Flash table (ATT_LOG):
TableName=ATT_LOG\r\nUser_PIN={UID}\tVerify_Type={VT}\tVerify_Time={TIMESTAMP}\tStatus={STATE}\tWork_Code_ID=0\0
By packaging this buffer with CMD_SET_DATA (3001) and immediately dispatching CMD_REFRESHDATA (1013) with CMD_VOICE (1014), the machine not only permanently logs the check-in/out record inside its memory—it also plays the hardware voice: "Thank you"!
// Write a punch directly to physical hardware memory:
$zk->markAttendance(
userId: '101',
state: Constants::ATT_STATE_CHECK_IN,
timestamp: '2026-10-17 09:00:00',
verifyType: Constants::VERIFY_FINGERPRINT,
options: [
'write_to_device' => true,
'device_feedback' => true // Triggers machine voice & LCD display
]
);
3. Laravel 8–12 Native Support (Zero Configuration)
We included an automatic ServiceProvider and Facade. In any Laravel project:
composer require taha-rafi/php-zkteco
In your Artisan command or scheduled console kernel:
use TahaRafi\ZKTeco\Facades\ZKTeco;
use App\Models\Attendance;
class SyncBiometricClock extends Command
{
public function handle(): void
{
$logs = ZKTeco::getAttendanceStructured();
foreach ($logs as $log) {
Attendance::firstOrCreate([
'user_id' => $log['user_id'],
'timestamp' => $log['timestamp'],
], [
'state' => $log['state_label'],
'verify' => $log['verify_label'],
]);
}
}
}
4. Continuous Integration & Benchmark
-
100% Pure PHP 8.0 - 8.4: Zero vendor C++ SDKs, zero
ext-socketsrequired (uses standard stream wrappers). - Automated CI Suite: 42 unit and protocol tests passing on PHP 8.0, 8.1, 8.2, 8.3, and 8.4.
- Tested Models: TX628, iClock series, K40, UFace, and standard ZMM standalone terminals.
來源:DEV Community · dev.to