import pytest from tools.havoc_debug_protocol import decode_message, encode_message, evaluate_condition def test_encode_decode_message_round_trip(): # encode_message/decode_message are the literal wire contract between the # Windows-side debug agent and this Linux-side MCP server, built independently on # two different machines -- prove they actually agree with each other. op = "set_watchpoint" params = {"addr": "0x47eb48", "size": 4, "mode": "write"} assert decode_message(encode_message(op, params)) == {"op": op, "params": params} def test_register_condition_true(): registers = {"eax": 0x50} condition = {"register": "eax", "op": "!=", "value": "0x47"} assert evaluate_condition(condition, registers, read_memory=None) is True def test_register_condition_false(): registers = {"eax": 0x47} condition = {"register": "eax", "op": "!=", "value": "0x47"} assert evaluate_condition(condition, registers, read_memory=None) is False def test_register_condition_equals(): registers = {"eip": 0x436070} condition = {"register": "eip", "op": "==", "value": "0x436070"} assert evaluate_condition(condition, registers, read_memory=None) is True def test_memory_condition_with_offset(): def fake_read_memory(addr, size): assert addr == 0x47eb48 + 0x18 assert size == 4 return (0x47).to_bytes(4, "little") condition = {"memory": "0x47eb48+0x18", "size": 4, "op": "==", "value": "0x47"} assert evaluate_condition(condition, {}, fake_read_memory) is True def test_memory_condition_no_offset(): def fake_read_memory(addr, size): assert addr == 0x47da88 return (0).to_bytes(size, "little") condition = {"memory": "0x47da88", "size": 4, "op": "==", "value": "0x0"} assert evaluate_condition(condition, {}, fake_read_memory) is True def test_comparison_operators(): registers = {"ecx": 10} assert evaluate_condition({"register": "ecx", "op": "<", "value": "0xb"}, registers, None) assert evaluate_condition({"register": "ecx", "op": ">", "value": "0x9"}, registers, None) assert evaluate_condition({"register": "ecx", "op": "<=", "value": "0xa"}, registers, None) assert evaluate_condition({"register": "ecx", "op": ">=", "value": "0xa"}, registers, None) def test_unknown_register_raises(): with pytest.raises(KeyError): evaluate_condition({"register": "bogus", "op": "==", "value": "0x0"}, {}, None) from tools.havoc_debug_protocol import WatchpointSlots, WatchpointLimitError def test_allocate_returns_free_slots_in_order(): slots = WatchpointSlots() assert slots.allocate(0x47c48c, 4, "write") == 0 assert slots.allocate(0x47da88, 4, "readwrite") == 1 def test_allocate_raises_past_four(): slots = WatchpointSlots() slots.allocate(0x1000, 4, "write") slots.allocate(0x2000, 4, "write") slots.allocate(0x3000, 4, "write") slots.allocate(0x4000, 4, "write") with pytest.raises(WatchpointLimitError): slots.allocate(0x5000, 4, "write") def test_release_frees_slot_for_reuse(): slots = WatchpointSlots() slot0 = slots.allocate(0x1000, 4, "write") slots.release(slot0) assert slots.allocate(0x2000, 4, "write") == 0 def test_compute_dr7_enables_local_bit_per_active_slot(): slots = WatchpointSlots() slots.allocate(0x1000, 4, "write") # slot 0 -> L0 = bit 0 dr7 = slots.compute_dr7() assert dr7 & 0b1 == 0b1 # L0 enabled assert dr7 & 0b100 == 0 # L1 not enabled def test_compute_dr7_encodes_write_mode_and_4byte_len(): slots = WatchpointSlots() slots.allocate(0x1000, 4, "write") dr7 = slots.compute_dr7() rw0 = (dr7 >> 16) & 0b11 len0 = (dr7 >> 18) & 0b11 assert rw0 == 0b01 # write-only assert len0 == 0b11 # 4 bytes def test_compute_dr7_encodes_readwrite_and_1byte_len(): slots = WatchpointSlots() slots.allocate(0x2000, 1, "readwrite") dr7 = slots.compute_dr7() rw0 = (dr7 >> 16) & 0b11 len0 = (dr7 >> 18) & 0b11 assert rw0 == 0b11 # read/write assert len0 == 0b00 # 1 byte