Created
January 15, 2025 12:58
-
-
Save cr0nx/05e41c079af0311e44360961700ed265 to your computer and use it in GitHub Desktop.
nslookup.zig - Simple Linux nslookup BOF ready to use with bof-launcher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const std = @import("std"); | |
| const beacon = @import("bof_api").beacon; | |
| const posix = @import("bof_api").posix; | |
| const net = std.net; | |
| const mem = std.mem; | |
| const DNS_PORT = 53; | |
| const MAX_DNS_PACKET_SIZE = 512; | |
| const DNS_QUERY_CLASS_IN = 1; | |
| const DNS_QUERY_TYPE_A = 1; | |
| const DNS_HEADER_SIZE = 12; | |
| const DnsHeader = extern struct { | |
| id: u16, | |
| flags: u16, | |
| qdcount: u16, | |
| ancount: u16, | |
| nscount: u16, | |
| arcount: u16, | |
| }; | |
| fn buildDnsQuery(allocator: mem.Allocator, domain: []const u8) ![]u8 { | |
| var packet = std.ArrayList(u8).init(allocator); | |
| defer packet.deinit(); | |
| // DNS Header | |
| const header = DnsHeader{ | |
| .id = 0x1234, | |
| .flags = 0x0100, // Standard query | |
| .qdcount = 0x0100, // One question | |
| .ancount = 0x0000, | |
| .nscount = 0x0000, | |
| .arcount = 0x0000, | |
| }; | |
| // Write header | |
| try packet.appendSlice(mem.asBytes(&header)); | |
| // Convert domain to DNS format (length-prefixed labels) | |
| var labels = mem.split(u8, domain, "."); | |
| while (labels.next()) |label| { | |
| try packet.append(@as(u8, @intCast(label.len))); | |
| try packet.appendSlice(label); | |
| } | |
| try packet.append(0); | |
| // Query type (A record) | |
| try packet.append(0); | |
| try packet.append(DNS_QUERY_TYPE_A); | |
| // Query class (IN) | |
| try packet.append(0); | |
| try packet.append(DNS_QUERY_CLASS_IN); | |
| return packet.toOwnedSlice(); | |
| } | |
| fn parseDnsResponse(response: []const u8) void { | |
| if (response.len < DNS_HEADER_SIZE) { | |
| _ = beacon.printf(0, "Response too short\n"); | |
| return; | |
| } | |
| const header = @as(*const DnsHeader, @ptrCast(@alignCast(response.ptr))).*; | |
| const answer_count = @byteSwap(header.ancount); | |
| if (answer_count == 0) { | |
| _ = beacon.printf(0, "No answers found\n"); | |
| return; | |
| } | |
| var pos: usize = DNS_HEADER_SIZE; | |
| // Skip question section | |
| while (pos < response.len and response[pos] != 0) { | |
| pos += @as(usize, @intCast(response[pos])) + 1; | |
| } | |
| pos += 5; // Skip null terminator and QTYPE/QCLASS | |
| // Parse answers | |
| var i: u16 = 0; | |
| while (i < answer_count and pos + 12 <= response.len) : (i += 1) { | |
| // Skip name field (compressed or uncompressed) | |
| if ((response[pos] & 0xC0) == 0xC0) { | |
| pos += 2; | |
| } else { | |
| while (pos < response.len and response[pos] != 0) { | |
| pos += @as(usize, @intCast(response[pos])) + 1; | |
| } | |
| pos += 1; | |
| } | |
| // Read type, class, TTL, and data length | |
| const rtype = (@as(u16, response[pos]) << 8) | response[pos + 1]; | |
| pos += 8; // Skip to rdlength | |
| const rdlength = (@as(u16, response[pos]) << 8) | response[pos + 1]; | |
| pos += 2; | |
| // Handle A records | |
| if (rtype == DNS_QUERY_TYPE_A and rdlength == 4 and pos + 4 <= response.len) { | |
| _ = beacon.printf(0, "IP Address: %d.%d.%d.%d\n", | |
| response[pos], | |
| response[pos + 1], | |
| response[pos + 2], | |
| response[pos + 3] | |
| ); | |
| } | |
| pos += rdlength; | |
| } | |
| } | |
| pub export fn go(args: ?[*]u8, args_len: i32) callconv(.C) u8 { | |
| if (args_len == 0) { | |
| return 1; // err 1: no argument provided | |
| } | |
| var opt_len: i32 = 0; | |
| const allocator = std.heap.page_allocator; | |
| var parser = beacon.datap{}; | |
| // Parse domain argument | |
| beacon.dataParse(&parser, args, args_len); | |
| const domain = beacon.dataExtract(&parser, &opt_len); | |
| const sDomain = domain.?[0..@as(usize, @intCast(opt_len - 1))]; | |
| // Create UDP socket | |
| const sockfd = std.posix.socket( | |
| std.posix.AF.INET, | |
| std.posix.SOCK.DGRAM, | |
| 0, | |
| ) catch return 1; | |
| defer std.posix.close(sockfd); | |
| // Set up DNS server address (using 8.8.8.8 as example) | |
| var server_addr = net.Address.initIp4([4]u8{ 8, 8, 8, 8 }, DNS_PORT); | |
| // Build DNS query | |
| const query = buildDnsQuery(allocator, sDomain) catch return 2; | |
| defer allocator.free(query); | |
| _ = beacon.printf(0, "Looking up %s...\n", sDomain.ptr); | |
| // Send query | |
| _ = std.posix.sendto( | |
| sockfd, | |
| query, | |
| 0, | |
| &server_addr.any, | |
| server_addr.getOsSockLen(), | |
| ) catch return 3; | |
| // Receive response | |
| var response_buf: [MAX_DNS_PACKET_SIZE]u8 = undefined; | |
| var src_addr: net.Address = undefined; | |
| var addrlen: std.posix.socklen_t = @sizeOf(net.Address); | |
| const received = std.posix.recvfrom( | |
| sockfd, | |
| &response_buf, | |
| 0, | |
| &src_addr.any, | |
| &addrlen, | |
| ) catch return 4; | |
| if (received > 0) { | |
| parseDnsResponse(response_buf[0..received]); | |
| } else { | |
| _ = beacon.printf(0, "No response received\n"); | |
| return 5; | |
| } | |
| return 0; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Find more interesting stuff here: https://edu.defensive-security.com/