The short answer is /var/lib/dpkg/status.
There might be easier ways, but strace can give you some
hints wrt where to look if you're patient:
$ strace dpkg -l virtualbox 2>&1 | grep open |
grep O_RDONLY | grep -v ENOENT
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libselinux.so.1",
O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libc.so.6",
O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libpcre.so.3",
O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libdl.so.2",
O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libpthread.so.0",
O_RDONLY|O_CLOEXEC) = 3
open("/proc/filesystems", O_RDONLY) = 3
open("/usr/lib/locale/locale-archive",
O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/dpkg/dpkg.cfg.d",
O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
open("/etc/dpkg/dpkg.cfg", O_RDONLY) = 3
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libc.so.6",
O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive",
O_RDONLY|O_CLOEXEC) = 3
open("/var/lib/dpkg/arch", O_RDONLY) = 3
open("/var/lib/dpkg/status", O_RDONLY) = 3
open("/usr/share/locale/locale.alias",
O_RDONLY|O_CLOEXEC) = 4
openat(AT_FDCWD, "/var/lib/dpkg/updates/",
O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
open("/var/lib/dpkg/triggers/File", O_RDONLY) = 3
open("/var/lib/dpkg/triggers/Unincorp", O_RDONLY) = 3
open("/proc/meminfo", O_RDONLY|O_CLOEXEC) = 4
open("/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache",
O_RDONLY) = 4
In the middle of all that noise you'll notice it's opening
/var/lib/dpkg/status. If you run "head /var/lib/dpkg/status"
you should get an idea of what's going on.