Domains like hdmovie2.pm have a predictable lifecycle:
Because of this cycle, hdmovie2.pm is likely already compromised or monitored by security agencies. Visiting it today is much riskier than it was six months ago.
Visit hdmovie2.pm without an ad-blocker. You will immediately be bombarded with: hdmovie2.pm
These ads generate Cost Per Mille (CPM) revenue for the site owners. For every 1,000 people who see those ads, the operator earns roughly $3 to $5. With millions of visits per month, this is a multi-million dollar illegal enterprise. They have zero incentive to vet the ads for safety, and scammers pay top dollar to place malware there.
A very common trick on hdmovie2.pm is a pop-up that says: "In order to play this video, you need to update your video player." It offers a download link for "HD_Codec_Update.exe." This file is 100% malware—usually a Trojan that steals saved passwords from your browser. Domains like hdmovie2
From a legal standpoint, hdmovie2.pm operates without a license from copyright holders. Under international laws such as the Digital Millennium Copyright Act (DMCA) in the US or the Copyright Act in the UK and India, uploading or streaming copyrighted material without permission is infringement.
Because the site does not pay for licensing fees, it generates revenue exclusively through advertisements. These ads are often aggressive, intrusive, and unvetted. Because of this cycle, hdmovie2
Score: 3/10
Below is the original source (as found on GitHub gist #b7c7f9, dated 2017‑09‑12) with line‑by‑line comments explaining the intent and any quirks.
# ------------------------------------------------------------
# hdmovie2.pm – Helper for extracting direct video URLs
# ------------------------------------------------------------
# Author : Unknown (forum‑user “DarkCoder”)
# Version : 1.3
# Updated : 2017‑09‑12
# ------------------------------------------------------------
package hdmovie2;
use strict;
use warnings;
# Core CPAN modules we rely on
use LWP::UserAgent; # HTTP client
use HTTP::Cookies; # Cookie jar (site uses Cloudflare/JS challenge)
use URI::Escape; # For urlencoding/decoding
use HTML::TreeBuilder; # Simple DOM parser
use JSON qw( decode_json );
use Digest::SHA qw( sha256 );
use MIME::Base64 qw( decode_base64 );
# -----------------------------------------------------------------
# GLOBAL USER‑AGENT – reused for all requests (keeps cookies alive)
# -----------------------------------------------------------------
my $ua = LWP::UserAgent->new(
agent => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) ' .
'Chrome/70.0.3538.77 Safari/537.36',
timeout => 30,
keep_alive => 1,
cookie_jar => HTTP::Cookies->new(),
ssl_opts => verify_hostname => 0 , # site uses self‑signed certs
);
# -----------------------------------------------------------------
# Constructor – simple hashref wrapper, allows per‑instance overrides
# -----------------------------------------------------------------
sub new
my ($class, %opts) = @_;
my $self =
ua => $ua, # default UA
debug => $optsdebug // 0, # optional debug flag
proxy => $optsproxy // undef,
;
# Proxy handling (if supplied)
if (defined $self->proxy)
$self->ua->proxy(['http', 'https'], $self->proxy);
bless $self, $class;
return $self;
# -----------------------------------------------------------------
# Optional: change proxy after construction
# -----------------------------------------------------------------
sub set_proxy
my ($self, $proxy) = @_;
$self->ua->proxy(['http', 'https'], $proxy);
$self->proxy = $proxy;
return 1;
# -----------------------------------------------------------------
# PUBLIC API – fetch a direct video URL from an HDMovie2 page
# -----------------------------------------------------------------
sub get_video_url
my ($self, $page_url) = @_;
# -----------------------------------------------------------------
# 1. Sanity‑check the URL – must belong to hdmovie2 domain
# -----------------------------------------------------------------
unless ($page_url =~ mnet)
$self->_log("Invalid URL: $page_url");
return;
# -----------------------------------------------------------------
# 2. Grab the HTML of the page (cookies are saved in $ua)
# -----------------------------------------------------------------
my $html = $self->_fetch_page($page_url);
return unless $html; # _fetch_page already logged errors
# -----------------------------------------------------------------
# 3. Extract the obfuscated token that the JS creates.
# The HTML contains something like:
# var token = "a1b2c3d4e5";
# or a call to a function that returns the token.
# -----------------------------------------------------------------
my $token = $self->_extract_token($html);
unless (defined $token)
$self->_log("Failed to locate token in page");
return;
# -----------------------------------------------------------------
# 4. Decrypt the token. The site uses a custom XOR + base64
# routine (see _decrypt_token). The result is a short string
# that the AJAX endpoint expects.
# -----------------------------------------------------------------
my $decoded = $self->_decrypt_token($token);
unless (defined $decoded)
$self->_log("Token decryption failed");
return;
# -----------------------------------------------------------------
# 5. Perform the AJAX request that returns JSON with the video URL.
# POST data: token=<decoded>&action=get_video
# -----------------------------------------------------------------
my $json_resp = $self->_ajax_fetch($page_url, $decoded);
return unless $json_resp;
# -----------------------------------------------------------------
# 6. Parse JSON and pull out the final video URL.
# -----------------------------------------------------------------
my $direct_url = $self->_final_url($json_resp);
unless ($direct_url)
$self->_log("Could not extract direct video URL from JSON");
return;
$self->_log("Success! Direct video URL: $direct_url") if $self->debug;
return $direct_url;
# -----------------------------------------------------------------
# Helper: fetch the page HTML (GET)
# -----------------------------------------------------------------
sub _fetch_page
my ($self, $url) = @_;
$self->_log("Fetching page: $url") if $self->debug;
my $resp = $self->ua->get($url);
unless ($resp->is_success)
$self->_log("HTTP GET failed: " . $resp->status_line);
return;
return $resp->decoded_content; # auto‑handles charset
# -----------------------------------------------------------------
# Helper: locate the token string inside the HTML.
# Uses a simple regex, but falls back to HTML::TreeBuilder if the
# token lives inside a <script> element.
# -----------------------------------------------------------------
sub _extract_token
my ($self, $html) = @_;
# 1️⃣ Regex shortcut – most pages embed the token as a literal.
if ($html =~ /var\s+token\s*=\s*"([^"]+)"/i)
$self->_log("Token found via regex") if $self->debug;
return $1;
# 2️⃣ Fallback – parse script tags and look for the token pattern.
my $tree = HTML::TreeBuilder->new_from_content($html);
for my $script ($tree->look_down(_tag => 'script'))
my $txt = $script->as_text;
if ($txt =~ /var\s+token\s*=\s*"([^"]+)"/i)
$tree->delete;
$self->_log("Token found via DOM parsing") if $self->debug;
return $1;
$tree->delete;
return; # token not found
# -----------------------------------------------------------------
# Helper: custom decryption routine.
# ---------------------------------------------------------------
# The JavaScript on the site does:
# token = atob(token);
# for (i=0; i<token.length; i++) token[i] ^= key[i % key.length];
# token = btoa(token);
# The Perl implementation mirrors that.
# -----------------------------------------------------------------
sub _decrypt_token
my ($self, $enc) = @_;
# Step 1 – base64 decode
my $decoded = eval decode_base64($enc) ;
if ($@)
$self->_log("Base64 decode error: $@");
return;
# Step 2 – XOR with static key (hard‑coded by the original author)
my $key = "hdmovie_secret"; # 14‑byte key
my $xorred = '';
for my $i (0 .. length($decoded)-1)
my $c = substr($decoded, $i, 1);
my $k = substr($key, $i % length($key), 1);
$xorred .= chr(ord($c) ^ ord($k));
# Step 3 – base64 encode again – this is the token the AJAX endpoint expects
my $final = encode_base64($xorred, ''); # no line breaks
$self->_log("Decrypted token: $final") if $self->debug;
return $final;
# -----------------------------------------------------------------
# Helper: perform the AJAX POST that returns JSON
# -----------------------------------------------------------------
sub _ajax_fetch
my ($self, $page_url, $token) = @_;
# Derive the AJAX endpoint from the page URL
my $ajax_url = $page_url;
$ajax_url =~ s(https?://[^/]+).*$1/ajax_endpoint.php;
$self->_log("POSTing to AJAX endpoint: $ajax_url") if $self->debug;
my $resp = $self->ua->post(
$ajax_url,
Content_Type => 'application/x-www-form-urlencoded',
Content => [
token => $token,
action => 'get_video',
],
);
unless ($resp->is_success)
$self->_log("AJAX POST failed: " . $resp->status_line);
return;
my $content = $resp->decoded_content;
$self->_log("AJAX response: $content") if $self->debug;
# Expect a JSON string like: "status":"ok","video":"https://cdn.hdmovie2.net/....m3u8"
my $data = eval decode_json($content) ;
if ($@)
$self->_log("JSON decode error: $@");
return;
return $data;
# -----------------------------------------------------------------
# Helper: pull the final direct video URL from the JSON payload.
# The JSON may contain either a single MP4 URL (key: video) or an
# HLS playlist (key: hls). Return whichever is present.
# -----------------------------------------------------------------
sub _final_url {
my ($self, $json_ref) = @_;
# Prefer HLS (most common for HDMovie2)
if (exists $json_ref->hls && $json_ref->hls)
return $json_ref->hls;
# Fallback to direct MP4
if (exists $json_ref->video && $json_ref->video) {
return $json_ref->video;