MoreRSS

site iconGaoJin | 高金修改

后端开发、爬虫、ETH开发、EOS开发。目前all in 区块链
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

GaoJin | 高金的 RSS 预览

friend_tech 第一笔交易只能买 1 个key?

2023-09-19 15:36:24

背景

群里有人说friend tech 第一笔交易只能自己购买1笔?

我当时就反驳了他,毕竟我也算看过 friend tech 合约代码的人,合约没有限制只能买 1 笔啊

废话少说,直接看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function buyShares(address sharesSubject, uint256 amount) public payable {
uint256 supply = sharesSupply[sharesSubject];
require(supply > 0 || sharesSubject == msg.sender, "Only the shares' subject can buy the first share");
uint256 price = getPrice(supply, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
require(msg.value >= price + protocolFee + subjectFee, "Insufficient payment");
sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] + amount;
sharesSupply[sharesSubject] = supply + amount;
emit Trade(msg.sender, sharesSubject, true, amount, price, protocolFee, subjectFee, supply + amount);
(bool success1,) = protocolFeeDestination.call{value: protocolFee}("");
(bool success2,) = sharesSubject.call{value: subjectFee}("");
require(success1 && success2, "Unable to send funds");
}

一看代码很简单,唯一限制的就这一句

require(supply > 0 || sharesSubject == msg.sender, "Only the shares' subject can buy the first share");

这句话的意思 新注册用户,只能自己买入第一笔. 也没有限制买多少key啊…

没那么简单

那别人为啥还言之凿凿说确实只能买 1 个key 呢?

严谨一点直接用forge模拟买买看就知道了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {TestHarness} from "../../TestHarness.sol";

interface iFriendtech {
function buyShares(address sharesSubject, uint256 amount) external payable;
}

contract fuck_friend_tech is TestHarness {
iFriendtech friendtech = iFriendtech(0xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d4);

function setUp() external {
cheat.createSelectFork("base", 4_119_770);
cheat.deal(address(this), 100 ether);
}

function test_attack() external {
friendtech.buyShares{value: 100 ether}(address(this), 2);
}

receive() external payable {}
}

你别说,还真失败了…报错为 [FAIL. Reason: Arithmetic over/underflow] test_attack() (gas: 3252)

显示溢出报错.肯定是哪里做运算出错了

再回头看buyShares的代码,唯一有疑点的地方就是getPrice

1
2
3
4
5
6
7
8
9
function getPrice(uint256 supply, uint256 amount) public view returns (uint256) {
uint256 sum1 = supply == 0 ? 0 : (supply - 1) * (supply) * (2 * (supply - 1) + 1) / 6;
uint256 sum2 = supply == 0 && amount == 1
? 0
: (supply - 1 + amount) * (supply + amount) * (2 * (supply - 1 + amount) + 1) / 6;

uint256 summation = sum2 - sum1;
return summation * 1 ether / 16_000;
}

出现问题在sum2… supply-1+amount这边就出错了…毕竟 solidity 语言有点傻,0-1=-1就直接溢出了…

好吧,原来不能第一次买多个的原因是 团队写 bug 了…改正也很简单,先+后-就可以了,这也算 solidity 开发常见的问题了,要先加后减,先乘后除

(supply - 1 + amount) * (supply + amount) * (2 * (supply - 1 + amount) + 1) / 6;
(supply + amount - 1) * (supply + amount) * (2 * (supply + amount - 1) + 1) / 6;

getPrice 分析

再回头看getPrice的代码.
估计第一眼看过去的都会比较迷糊,这到底是干啥了…当然数学大神一下就能看出来这是平方和公式

我们可以借助 ChatGPT

1
2
3
4
5
6
7
8
9
10
11
12
Q: (supply - 1) * (supply) * (2 * (supply - 1) + 1) / 6 这是什么公式?

A:
这个公式是用来计算一个整数变量(通常表示为"supply")的平方和平方和的一种表示方式。公式的具体步骤如下:

1. 先将"supply"的值减去1,即"(supply - 1)"。
2. 然后将该值与"supply"相乘,即"(supply - 1) * supply"。
3. 接下来,将2乘以"(supply - 1)",即"2 * (supply - 1)"。
4. 将上一步骤的结果加1,即"2 * (supply - 1) + 1"。
5. 最后,将前三步骤的结果相乘,并将其除以6,即"(supply - 1) * supply * (2 * (supply - 1) + 1) / 6"。

这个公式可以用来计算一系列连续整数的平方和,其中"supply"代表整数的上限。这种形式的平方和通常在数学和统计学中有多种应用,比如计算方差、概率分布等。

知道这是平方求和公式就简单了,这段代码就很好理解了
((supply-1+amount)的平方和 - (supply-1)的平方和) / 16000

friend_tech 套利到底有多卷

2023-09-18 23:24:45

背景

最近有准备搞一下friend tech的套利…

就写个 sql看看目前有多少人盈利.这个数据不会基于没有卖出去的key

数据在这friend tech top profit

按照盈利金额排序

按照亏损金额排序

头几个盈利的肯定是机器人账号.就看看他们的数据,这一看就发现了一些问题.

数据分析

所有的数据基于block 411977,真实用户0xda, 购买 tx

数据长这样

1
2
3
4
5
6
7
8
9
10
11
12

fromnoncetotransactionIndexgasPricegasFeestatusnew_status
00xa3afA500e3d88CAfA41AA7B95a8dEF7d2CBce92b941660xfF231524719dB94a0Ef08A88fC4e6939134EadE811254698872434951723127900
10x135557B95fc164cB152e853568b25880Ba06F94A94870x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c21254698872394616413986700
20x8ac85761268B9Ac3fDe06d381611b64B232CEa97662930x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c31254698872397503555135900
30x1BA4C73e7831b8099377c0583acfF4DBE488dFc6166260x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c341254698872428797029923900
40xa92a6331d0174c0eDC463B097E8564a492D451FA936090xfF231524719dB94a0Ef08A88fC4e6939134EadE851254698872434951723127900
50x9a34845CCBeA93925B15F5af6d9F4aFae4EC590a937550xfF231524719dB94a0Ef08A88fC4e6939134EadE861254698872434951723127900
60x5ED1222A1c0BdBA479B61201231f539ac131DD69641310x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c371254698872428797029923900
70xe8C5433d6E8Fc7a2b38eF0391FabA7470c9576e451420x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c381254698872428797029923900
80x38Cd1a8474A948a5a3e4d4b06c4069F2116DD184855740x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c91254698872406164978583500
90x3c2c170296446e241dE2eE39d7036254d55Df9E4936020xfF231524719dB94a0Ef08A88fC4e6939134EadE8101254698872426290299680300

交易数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import json
import pandas as pd

from hexbytes import HexBytes

with open('4119771_da681.json') as f:
txs = json.loads(f.read())

def filter_da68(txs):
flashruntxs = []
for tx in txs:
_input = tx["input"]
if "da68ee" in _input:
flashruntxs.append(
{
"from": tx["from"],
"nonce": tx["nonce"],
"to": tx["to"],
"transactionIndex": tx["transactionIndex"],
"gasPrice": tx["gasPrice"],
"gasFee": tx["gasFee"],
"status": tx["status"],
}
)
return flashruntxs

da68 = filter_da68(txs)

len(da68),len(txs)

(506, 517)

可以看到整个区块交易517 条,其中和da68相关的 506. 就非常离谱,97.87%的用户都是来抢跑的.

一共有多少机器人?

我们这边按照合约地址分一下类. 可以看到有几个头部机器人.
前3 占了大部分交易.

1
2
3
4
5
6
7
8
9
10
11
to_count = df['to'].value_counts()
to_count

0x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c3 184
0xfF231524719dB94a0Ef08A88fC4e6939134EadE8 161
0x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c 123
0xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d4 19
0xe27ddadbC8779D01fC539Bf5BA8529D728418625 9
0x27E8A5B043d3f58dde1c7ab96d2E3cF07558A1f1 8
0xAfE6795A9097BbCE00fe61462d22CCeeF706a3e8 2
Name: to, dtype: int64

看看这几个机器人花了多少gas费,没想到发送 184 条交易才花 7.69刀.

1
2
3
4
5
6
7
8
9
10
11
12
13
ethprice = 1640
for to in to_count.index:
tteth = sum(df[df.to==to].gasFee)/1e18
ttusd = tteth*ethprice
print(tteth,ttusd)

0.004690181471662096 7.691897613525838
0.004104812550636566 6.731892583043968
0.003097893590529322 5.080545488468088
0.000457215606189089 0.749833594150106
0.000226412037571068 0.3713157416165515
0.00017632952477182 0.2891804206257848
5.0244385725274e-05 0.08240079258944936

再看看这些机器人有多少小号
有发现几个菜鸟机器人…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
for to in to_count.index:
print('to:',to)
print(df[df.to==to]['from'].value_counts())
print()
print('#'*30)


to: 0x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c3
0x5ED1222A1c0BdBA479B61201231f539ac131DD69 27
0x1BA4C73e7831b8099377c0583acfF4DBE488dFc6 26
0xd9E4b9579a5CF5B336E258820CC95cA34411c9D6 26
0x27C2a598Db389b5276c0156f14B4dE75E490712F 26
0xe8C5433d6E8Fc7a2b38eF0391FabA7470c9576e4 24
0xDd2B7694259a83753918BF5b6dC6961fabd2FDbC 24
0xC81a00ee6839EE0dFbF465d3C08E5F0b0291b71A 20
0x3Ee2183b706cf381e7e4586b8063a779f1E7b7dA 11
Name: from, dtype: int64

##############################
to: 0xfF231524719dB94a0Ef08A88fC4e6939134EadE8
0xa3afA500e3d88CAfA41AA7B95a8dEF7d2CBce92b 9
0xa92a6331d0174c0eDC463B097E8564a492D451FA 9
0x2bE3973950D0F1951E12C21bC7bB8e2428101Fdd 9
0x63cEe818600f91C4473CD14cDfD9C10d918B587b 9
0x92c21dB4a7cC0302ccCb71d444aE674Ec3980c93 9
0xCb44B9062eB0Cd22c4C0701385e6482BF4a77759 9
0x57ae46a097751124f8c35A86f64B4c38d2C62675 9
0x8eE3847cAE34e6e0292dc3DF8DD3C0C69a266b97 9
0x807db242cD37b5E2B53697842197611f51693C06 9
0xDb6f56639AaeA09FfEA6080D31A32851524128d4 9
0x9D94BdAf42d7906b3D3c011053ed2cBF42A1cA3f 9
0x244Fb415A8765812B9bc5B52cA14fd83C9d7E0e5 9
0xF689ac0322786Ac2D7245Fcd665fe120ff1d0408 9
0x895D2d10FB9b92D0e1002c1a1bCdb90Ed42c3588 9
0xF53010525Df586e78b14114C9cd05481E79E6C8C 9
0x3c2c170296446e241dE2eE39d7036254d55Df9E4 9
0x9a34845CCBeA93925B15F5af6d9F4aFae4EC590a 9
0x36da68690F238366156890010c411394FC529cd9 8
Name: from, dtype: int64

##############################
to: 0x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c
0x8ac85761268B9Ac3fDe06d381611b64B232CEa97 13
0x135557B95fc164cB152e853568b25880Ba06F94A 12
0x38Cd1a8474A948a5a3e4d4b06c4069F2116DD184 12
0x3547DbcD816bc17D7dE0e7e67a8B8bEE74384ACF 12
0xa9ff99346829E7c578E6d38C2269997a2FCcEEE1 12
0xd65eD99CB8b3b65CE378e3E5CaE545B751f4DAc6 11
0x1F238B47ab9fD915e5852601E83c6c1358297870 11
0x75Ff68BDe78974ab54a642fD294369447bD9Feef 10
0xf1E7E7Ad07C046abb6e488F73c126B897892755c 10
0x76eF08c772Db73E05fa00398746FbA31eFC5804F 10
0x7CfE232Ed8b0acc111ec780b7Cb95C68f3CF7Aa6 10
Name: from, dtype: int64

##############################
to: 0xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d4
0x188eCF8f4290C4e5d641449B88CEf94574174972 15
0xDa68EEe4c5b10D8A574b9ec072335c583B1C67A2 1
0xbEbA981C5697b1CFa7c04fCB523E94bD42F79afB 1
0x40954b9B4bAfC41Ec6387325fD0069C68674a04d 1
0x296D3324Ee4b2316E87f965F4A04F073780f5310 1
Name: from, dtype: int64

##############################
to: 0xe27ddadbC8779D01fC539Bf5BA8529D728418625
0xeFa4b454A64049ff93cC37b54102982ACEdf4Eae 9
Name: from, dtype: int64

##############################
to: 0x27E8A5B043d3f58dde1c7ab96d2E3cF07558A1f1
0x8b113ad5c2b2E3ddb102305e32259c6b23D12aa3 4
0x89B5BB48f016b809EB2eE78416550487334C1331 4
Name: from, dtype: int64

##############################
to: 0xAfE6795A9097BbCE00fe61462d22CCeeF706a3e8
0xC1322133ACbAd823614bf79Ca9eC409fA3A7E39A 2
Name: from, dtype: int64

##############################

最后总览看看

26是用户的交易.可以看到所有的 gasPrice都和用户的一样…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 筛选出 transactionIndex 在 16-40 范围内的数据
filtered_df = df[(df['transactionIndex'] >= 16) & (df['transactionIndex'] <= 36)]

# 按照 transactionIndex 从小到大排序
sorted_df = filtered_df.sort_values(by='transactionIndex')

def highlight_row(row):
if row['transactionIndex'] == 26:
return ['background-color: yellow'] * len(row)
else:
return [''] * len(row)

highlighted_df = filtered_df.style.apply(highlight_row, axis=1)
highlighted_df


fromnoncetotransactionIndexgasPricegasFeestatus
150xa9ff99346829E7c578E6d38C2269997a2FCcEEE1730390x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c16125469887240616497858350
160x895D2d10FB9b92D0e1002c1a1bCdb90Ed42c3588949870xfF231524719dB94a0Ef08A88fC4e6939134EadE817125469887243495172312790
170xDd2B7694259a83753918BF5b6dC6961fabd2FDbC31800x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c318125469887242879702992390
180xF689ac0322786Ac2D7245Fcd665fe120ff1d0408943820xfF231524719dB94a0Ef08A88fC4e6939134EadE819125469887243495172312790
190xC81a00ee6839EE0dFbF465d3C08E5F0b0291b71A27080x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c320125469887242879702992390
200x244Fb415A8765812B9bc5B52cA14fd83C9d7E0e5562030xfF231524719dB94a0Ef08A88fC4e6939134EadE821125469887242340315853110
210x5ED1222A1c0BdBA479B61201231f539ac131DD69641320x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c322125469887242879702992390
220xeFa4b454A64049ff93cC37b54102982ACEdf4Eae66820xe27ddadbC8779D01fC539Bf5BA8529D72841862523125469887233748113267501
230x1BA4C73e7831b8099377c0583acfF4DBE488dFc6166270x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c324125469887242013560647630
240xd65eD99CB8b3b65CE378e3E5CaE545B751f4DAc6660630x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c25125469887240616497858350
250xDa68EEe4c5b10D8A574b9ec072335c583B1C67A200xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d426125469887288954547350181
260x9D94BdAf42d7906b3D3c011053ed2cBF42A1cA3f557120xfF231524719dB94a0Ef08A88fC4e6939134EadE827125469887336599568461861
270xe8C5433d6E8Fc7a2b38eF0391FabA7470c9576e451430x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c328125469887255865836296890
280xd9E4b9579a5CF5B336E258820CC95cA34411c9D676560x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c329125469887255865836296890
290x1F238B47ab9fD915e5852601E83c6c13582978701886260x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c30125469887252936386062880
300xDb6f56639AaeA09FfEA6080D31A32851524128d4557420xfF231524719dB94a0Ef08A88fC4e6939134EadE831125469887254748032978540
310x27C2a598Db389b5276c0156f14B4dE75E490712F81120x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c332125469887255865836296890
320x8ac85761268B9Ac3fDe06d381611b64B232CEa97662940x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c33125469887252936386062880
330x75Ff68BDe78974ab54a642fD294369447bD9Feef747960x4Ff30D7222B852c67A66Dc8E8d56A8cf92B8411c34125469887252070243718120
340xDd2B7694259a83753918BF5b6dC6961fabd2FDbC31810x4C99B25DdD0887D88b6C1364E3Df63a3bE76A5c335125469887255865836296890
350x807db242cD37b5E2B53697842197611f51693C06559790xfF231524719dB94a0Ef08A88fC4e6939134EadE836125469887254748032978540

代码以及相关数据下载

https://github.com/jin10086/friend-tech-bot-analyze

无公网ip,无服务器实现内网穿透

2023-09-16 22:11:33

背景

家里有台服务器,想着能远程 ssh上去改改 bug 啥的,发现无公网 ip

网上搜了一堆方案,发现都挺麻烦,有的确实不需要公网 ip,但是他却要有一台有公网 ip的服务器…

实现.

本次实现要感谢这篇文章 使用Cloudfalre Tunnels实现内网穿透,同时突破80/443限制

他这篇主要讲的是80,443端口,那么同理 22 应该也没有问题

操作

  • 你需要注册一个域名,买一个最便宜的就行,一年 10rmb 的那种.
  • 打开Cloudfalre控制台,依次点击Access -> Tunnels(这边记得选 0 元的,需要你绑卡才能下一步,直接关掉,重新打开控制台发现不绑卡也开通了.)
  • 然后就是创建隧道了,可以参考官方文档 Set up a tunnel through the dashboard

Subdomain选择你想要的前缀(随便写都可以)
domain选择你注册的域名
type记得选择 ssh
URL 选择localhost:22(如果你把自己 sshd端口改了,那这边写你改以后的.)

操作流程我复制一下

  • 下载 cloudflared 在客户端
  • 修改SSH configuration file:
    vim ~/.ssh/config
  • 新增下面的内容,把下面的ssh.example.com 替换成你的,比如我就应该替换成ssh.igaojin.me
    还需要注意的是 cloudflared的位置.记得where cloudflared看一下,如果不一致就替换成你的

    1
    2
    Host ssh.example.com
    ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h
  • ssh@ssh.example.com (最后可以直接 ssh 登录了.)

植物挖矿

2021-07-01 12:08:00

背景

  1. 最近在B站看了一个纪录片 超级仿生

    我们如何利用自然界最伟大的运动员 - 植物群和动物群的潜力来发挥我们的优势?我们怎样才能从自然的现象力量中获得灵感,并复制它并进化发展出超级解决方案,以满足特定的人类需求,解决我们的问题,限制或修复人类活动已经造成的损害?通过仿生学,精美的3D效果和CGI,本片讲述了一个更加理想和可持续发展的世界的故事。我们将逐步揭示我们的未来。在全球范围内,越来越多的研究人员和企业家,真正的“未来创造者”,正在利用自然界40亿年积累的智慧而获得的惊人成果,开始改变今天和明天世界。

  2. 最近国内禁止挖矿,导致BTC算力猛降.市场上蛮多二手矿机的,我最近在研究能不能抄点显卡挖挖看。

植物挖矿

看纪录片的时候 我突然想到,植物是不会也会数学呢.

能不能用来计算hash呢.

找到这篇文章植物「會數學」

當植物在夜間無法通過光合作用,將二氧化碳轉化為糖和澱粉時,它們必須自身規劃好所需儲備的澱粉的量,以幫助它們在黎明到來前,有足夠的能量。

這項實驗在位於諾維奇的約翰-因斯中心進行。實驗表明,植物能如此精確地調整它所需的澱粉消耗量。這種現象只能有一種解釋,該植物一定進行了完整的數學運算,而且進行的還是除法。

負責這項研究的史密斯教授(Prof Alison Smith)向BBC表示:「植物實際上是通過一種簡單的化學方式,在做數學題。 當我們這些科學家看到這個實驗結果時,我們不僅欣喜異常,而且應該說是給嚇著了。」

他說:「植物進行的運算,是初中水平的數學計算。」

科學家們還通過建立數學模塊的方式,對植物體內如何通過除法進行運算,展開了深入研究。

在整個晚上,植物葉片內的組織計算所需的澱粉儲備。時間信息通過植物體內的生物鐘完成,與人類的生物鐘相似。

「複雜」

研究人員猜測,這項計算通過植物體內的兩種分子來完成,它們分別是代表澱粉的「S」分子、和代表時間的「T」分子。

如果「S」分子能激發澱粉儲備的臨界點,而「T」分子能阻止澱粉消耗殆盡的話,植物體內的澱粉消耗量則能通過兩個分子之間的比率設定出來。換句話說,就是用「S」除以「T」。

該研究中心的霍華德教授(Prof Martin Howard)說:「這是證明生物能進行如此複雜的算數運算的力證。」

科學家們推想,這種運算能力也在動物界被廣泛使用,比如鳥類在遷徙、或孵化過程中,能有效控制自己體內的脂肪儲備。

所以理论上植物是会数学的.未来会不会出现养一大堆植物就是为了 计算hash呢

当然更可能的是出现植物类的计算机。

浏览器环境检测

2020-05-23 22:26:52

背景

之前爬虫 驱动个selenium基本上就可以了.

但是现在各种检测浏览器环境…特别是不熟悉js的同学就更烦了

本文是直接把 selenium pyppeteer 以及正常打开浏览器 的环境差异直接列出来

这样你就可以更愉快的爬虫了(可以直接把环境全部模拟上,或者大概看看有啥,下次看人家混淆js的时候心里有个数)

原理

就是遍历window对象,把属性全部保存成json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function recur(obj) {
var result = {},
_tmp;
for (var i in obj) {
// enabledPlugin is too nested, also skip functions
if (i === 'enabledPlugin' || typeof obj[i] === 'function') {
continue;
} else if (typeof obj[i] === 'object') {
// get props recursively
_tmp = recur(obj[i]);
// if object is not {}
if (Object.keys(_tmp).length) {
result[i] = _tmp;
}
} else {
// string, number or boolean
result[i] = obj[i];
}
}
return result;
}

function go() {

var j = new Object();
var propertys = Object.getOwnPropertyNames(window);

propertys.forEach(element => {
if (element === 'globalThis' ||
element === 'Illegal' ||
element === 'parent' ||
element === 'top' ||
element === 'frames' ||
element === 'self' ||
element === 'window' ||
element === 'document'
) {} else {
eval("j." + element + "=recur(window." + element + ")")
}
});

console.log(JSON.stringify(j))

}
go()

然后分别正常打开,selenium打开,pyppeteer打开

再查看方法的差异

操作

获取json

下载本项目

启动本地server python -m http.server 80

打开 http://localhost/

正常打开最好是无痕模式,因为浏览器扩展可能会导致多出很多浏览器扩展的属性

打开网页以后 -> 打开开发者工具 -> 切换到console

然后点击copy,粘贴json到对应的文件内

selenium pyppeteer 打开可以参考getEnvironment.py

比较

直接运行 python diff.py就可以了.

下面可以看到

差异有

root[‘navigator’][‘webdriver’], root[‘clientInformation’][‘webdriver’] 等等

这个算是目前检测用的比较多的,还有一些其他的可以自己运行看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
selenuim 与正常环境的差异
############################################################
{ 'dictionary_item_added': [root['cdc_adoQpoasnfa76pfcZLmcfl_Symbol'], root['cdc_adoQpoasnfa76pfcZLmcfl_Array'], root['cdc_adoQpoasnfa76pfcZLmcfl_Promise'], root['navigator']['webdriver'], root['clientInformation']['webdriver']],
'dictionary_item_removed': [root['chrome']['runtime'], root['navigator']['doNotTrack'], root['navigator']['languages']['2'], root['clientInformation']['doNotTrack'], root['clientInformation']['languages']['2']],
'type_changes': { "root['visualViewport']['height']": { 'new_type': <class 'int'>,
'new_value': 948,
'old_type': <class 'float'>,
'old_value': 751.2000122070312}},
'values_changed': { "root['clientInformation']['connection']['downlink']": { 'new_value': 1.3,
'old_value': 1.45},
"root['screen']['availHeight']": { 'new_value': 1027,
'old_value': 1050},
"root['screen']['availTop']": { 'new_value': 23,
'old_value': 0},
"root['visualViewport']['width']": { 'new_value': 840,
'old_value': 1344}}}
pyppeteer 与正常环境的差异
############################################################
{ 'dictionary_item_added': [root['WebKitAnimationEvent'], root['RTCRtpContributingSource'], root['SVGDiscardElement'], root['MediaCapabilitiesInfo'], root['WebKitTransitionEvent'], root['chrome']['webstore'], root['navigator']['webdriver'], root['clientInformation']['webdriver']],
'dictionary_item_removed': [root['BackgroundFetchManager'], root['RTCPeerConnectionIceErrorEvent'], root['WritableStreamDefaultWriter'], root['XRDOMOverlayState'], root['TrustedTypePolicy'], root['GeolocationCoordinates'], root['External'], root['ClipboardItem'], root['XRTransientInputHitTestSource'], root['XRRay'], root['PerformanceElementTiming'], root['XRViewerPose'], root['RTCError'], root['TextDecoderStream'], root['BackgroundFetchRegistration'], root['TrustedHTML'], root['onpointerrawupdate'], root['XRSession'], root['XRSessionEvent'], root['XRReferenceSpaceEvent'], root['XRReferenceSpace'], root['BarcodeDetector'], root['SubmitEvent'], root['AnimationEffect'], root['PeriodicSyncManager'], root['onformdata'], root['onselectstart'], root['TrustedScriptURL'], root['XRRenderState'], root['SpeechSynthesisErrorEvent'], root['RTCIceTransport'], root['TextEncoderStream'], root['MediaSession'], root['XRHitTestSource'], root['GeolocationPositionError'], root['XRPose'], root['XRView'], root['ElementInternals'], root['XRInputSourceArray'], root['XRInputSourceEvent'], root['XRFrame'], root['DecompressionStream'], root['TrustedScript'], root['Geolocation'], root['TrustedTypePolicyFactory'], root['XRBoundedReferenceSpace'], root['FeaturePolicy'], root['PerformanceEventTiming'], root['LargestContentfulPaint'], root['VideoPlaybackQuality'], root['onselectionchange'], root['XRSpace'], root['XRInputSourcesChangeEvent'], root['GeolocationPosition'], root['XRViewport'], root['XRHitTestResult'], root['queueMicrotask'], root['XRWebGLLayer'], root['DeviceMotionEventRotationRate'], root['ReadableStreamDefaultReader'], root['BackgroundFetchRecord'], root['LayoutShift'], root['DeviceMotionEventAcceleration'], root['MediaMetadata'], root['XRTransientInputHitTestResult'], root['PaymentMethodChangeEvent'], root['trustedTypes'], root['FragmentDirective'], root['UserActivation'], root['RTCErrorEvent'], root['CompressionStream'], root['XRRigidTransform'], root['Animation'], root['RTCSctpTransport'], root['XRInputSource'], root['FormDataEvent'], root['XRSystem'], root['EnterPictureInPictureEvent'], root['KeyframeEffect'], root['RTCDtlsTransport'], root['chrome']['runtime'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_OVERLAY'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_HUE'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_SATURATION'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_COLOR_DODGE'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_LUMINOSITY'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_EXCLUSION'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_DIFFERENCE'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_SOFT_LIGHT'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_HARD_LIGHT'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_COLOR_BURN'], root['SVGFEBlendElement']['SVG_FEBLEND_MODE_COLOR'], root['navigator']['userActivation'], root['navigator']['doNotTrack'], root['navigator']['mediaSession'], root['navigator']['languages']['2'], root['clientInformation']['userActivation'], root['clientInformation']['doNotTrack'], root['clientInformation']['mediaSession'], root['clientInformation']['languages']['2'], root['PerformanceObserver']['supportedEntryTypes']],
'type_changes': { "root['visualViewport']['height']": { 'new_type': <class 'int'>,
'new_value': 600,
'old_type': <class 'float'>,
'old_value': 751.2000122070312}},
'values_changed': { "root['clientInformation']['appVersion']": { 'new_value': '5.0 '
'(Macintosh; '
'Intel '
'Mac '
'OS '
'X '
'10_15_4) '
'AppleWebKit/537.36 '
'(KHTML, '
'like '
'Gecko) '
'Chrome/71.0.3542.0 '
'Safari/537.36',
'old_value': '5.0 '
'(Macintosh; '
'Intel '
'Mac '
'OS '
'X '
'10_15_4) '
'AppleWebKit/537.36 '
'(KHTML, '
'like '
'Gecko) '
'Chrome/83.0.4103.61 '
'Safari/537.36'},
"root['clientInformation']['connection']['rtt']": { 'new_value': 100,
'old_value': 150},
"root['clientInformation']['plugins']['0']['name']": { 'new_value': 'Chromium '
'PDF '
'Plugin',
'old_value': 'Chrome '
'PDF '
'Plugin'},
"root['clientInformation']['plugins']['1']['name']": { 'new_value': 'Chromium '
'PDF '
'Viewer',
'old_value': 'Chrome '
'PDF '
'Viewer'},
"root['clientInformation']['userAgent']": { 'new_value': 'Mozilla/5.0 '
'(Macintosh; '
'Intel '
'Mac '
'OS '
'X '
'10_15_4) '
'AppleWebKit/537.36 '
'(KHTML, '
'like '
'Gecko) '
'Chrome/71.0.3542.0 '
'Safari/537.36',
'old_value': 'Mozilla/5.0 '
'(Macintosh; '
'Intel '
'Mac '
'OS '
'X '
'10_15_4) '
'AppleWebKit/537.36 '
'(KHTML, '
'like '
'Gecko) '
'Chrome/83.0.4103.61 '
'Safari/537.36'},
"root['console']['memory']['jsHeapSizeLimit']": { 'new_value': 2330000000,
'old_value': 3760000000},
"root['history']['length']": { 'new_value': 2,
'old_value': 3},
"root['location']['href']": { 'new_value': 'http://localhost/',
'old_value': 'http://localhost/t.html'},
"root['location']['pathname']": { 'new_value': '/',
'old_value': '/t.html'},
"root['navigator']['appVersion']": { 'new_value': '5.0 '
'(Macintosh; '
'Intel '
'Mac '
'OS X '
'10_15_4) '
'AppleWebKit/537.36 '
'(KHTML, '
'like '
'Gecko) '
'Chrome/71.0.3542.0 '
'Safari/537.36',
'old_value': '5.0 '
'(Macintosh; '
'Intel '
'Mac '
'OS X '
'10_15_4) '
'AppleWebKit/537.36 '
'(KHTML, '
'like '
'Gecko) '
'Chrome/83.0.4103.61 '
'Safari/537.36'},
"root['navigator']['connection']['rtt']": { 'new_value': 100,
'old_value': 150},
"root['navigator']['plugins']['0']['name']": { 'new_value': 'Chromium '
'PDF '
'Plugin',
'old_value': 'Chrome '
'PDF '
'Plugin'},
"root['navigator']['plugins']['1']['name']": { 'new_value': 'Chromium '
'PDF '
'Viewer',
'old_value': 'Chrome '
'PDF '
'Viewer'},
"root['screen']['availHeight']": { 'new_value': 1027,
'old_value': 1050},
"root['screen']['availTop']": { 'new_value': 23,
'old_value': 0},
"root['screen']['colorDepth']": { 'new_value': 24,
'old_value': 30},
"root['screen']['orientation']['type']": { 'new_value': 'portrait-primary',
'old_value': 'landscape-primary'},
"root['screen']['pixelDepth']": { 'new_value': 24,
'old_value': 30},
"root['visualViewport']['width']": { 'new_value': 800,
'old_value': 1344}}}

项目地址

https://github.com/jin10086/browser-environment-test

加密、编码相关知识汇总

2020-05-18 10:14:12

ascii

js

1
2
3
4
var str = "A";
str.charCodeAt(); // 65

String.fromCharCode(65); // 'A'

python

1
2
3
4
s = 'A'
print(ord(s)) #65

print(chr(65)) # A

base64

js

1
2
3
window.btoa("python123") //cHl0aG9uMTIz

window.atob("cHl0aG9uMTIz") //python123

python

1
2
3
4
5
import base64

base64.b64encode(b'python123') # b'cHl0aG9uMTIz'

base64.b64decode(b'cHl0aG9uMTIz') # b'python123'

AES

js

https://github.com/brix/crypto-js 基于这个库

参考 https://cryptojs.gitbook.io/docs/#ciphers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var CryptoJS = require("crypto-js");
// 参考https://cryptojs.gitbook.io/docs/#ciphers

var password = 'dahsjhfjakhfjksdsssss1sx' //密码
var iv = 'fdshgjdsngjknaaa'


var encrypted = CryptoJS.AES.encrypt("helloworld", password);
var decrypted = CryptoJS.AES.decrypt(encrypted, password);
console.log('encrypted: ', encrypted.toString())
console.log('decrypted: ', decrypted.toString(CryptoJS.enc.Utf8))
// encrypted: U2FsdGVkX1 + mjJEOaXrsYp + PtlZvC0P4HIZZh6LYAmM =
// decrypted: helloworld

var encrypted1 = CryptoJS.AES.encrypt("helloworld", password, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
var decrypted1 = CryptoJS.AES.decrypt(encrypted1, password, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log('encrypted1: ', encrypted1.toString())
console.log('decrypted1: ', decrypted1.toString(CryptoJS.enc.Utf8))

// encrypted1: U2FsdGVkX1 + bg / GfdQ4dHlCKpGj15ZsC6ewZru327lY =
// decrypted1: helloworld

CryptoJS supports the following modes:

  • CBC (the default)
  • CFB
  • CTR
  • OFB
  • ECB

And CryptoJS supports the following padding schemes:

  • Pkcs7 (the default)
  • Iso97971
  • AnsiX923
  • Iso10126
  • ZeroPadding
  • NoPadding

python

https://pypi.org/project/pycrypto/ 基于这个库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import AES

password = 'dahsjhfjakhfjksdsssss1sx'
iv = 'fdshgjdsngjknaaa'

##这边填充方式没有自带的方法,需要自己写
# pad = lambda s: s + (16 - len(s)%16) * chr(0) # ZeroPadding
pad = lambda s: s + (16 - len(s)%16) * chr(16 - len(s)%16) # Pkcs7

obj = AES.new(password,AES.MODE_CBC,iv)
encrypted = obj.encrypt(pad("helloworld").encode("utf8"))
print(encrypted)
# b'k\xb8\xad\x1c\xde\xa3O1n-\xf9\x99\xbc\x16\xb4\xd7'

obj2 = AES.new(password, AES.MODE_CBC, iv)
print(obj2.decrypt(encrypted)) #结果也需要手动去掉填充...
# b'helloworld\x06\x06\x06\x06\x06\x06'

RSA

js

使用的js库 是 https://github.com/travist/jsencrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var publicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSLffg4ztiN4J7eSWhw3kIrrsM
5IMnkYMAxJgWDaIXwo7mJRpCdYkQtxLPnbE+zFklL1BLS923MFtAz5uLpIkf0LiV
YoaAdGMMVfEfusnW8GcWPMse/0jMZkb9RdSI7othUETJNYadUdBQ2tZoGryHJ6N2
DU7RVvt+6KiysCGbXQIDAQAB
-----END PUBLIC KEY-----`

var priveteKey = `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDSLffg4ztiN4J7eSWhw3kIrrsM5IMnkYMAxJgWDaIXwo7mJRpC
dYkQtxLPnbE+zFklL1BLS923MFtAz5uLpIkf0LiVYoaAdGMMVfEfusnW8GcWPMse
/0jMZkb9RdSI7othUETJNYadUdBQ2tZoGryHJ6N2DU7RVvt+6KiysCGbXQIDAQAB
AoGBAMvW2Ti6KLWwsFOfwueH9LJ/A9oVJT4S2cK85+M7haEViBGcmkMqSqxzYUMJ
5QTKmwp9QJpGEdnAhD/vGSPthy4ZfaLLSDqjHgrSAVLTUp8ol1/n5V0G40EjvRWc
l4FKx8yHDcsOhkk4LcBDJnUCkZW5t+td8eASewtTMmFHDV3hAkEA859JvI881KoM
u5hCYBdIkSTsmjZdz68LTxDF3eB9Wgdsk4FS+OYkujl6+pch1fXIgjycs8V5eb6H
9QS8SHE/+wJBANzbs/IX2TcIPU6T4AKMt9QI/+lA/baLD6aaznxs8uvlVQ1XF6Y2
G91imkM0YgobSvUgleswWvjfJPicoVWeOocCQDTUoiTl2wczKTe1OQ+lfPMc0sJO
b+GN0CIBPSd0ezp+yajjLp8TKXvI40ZcZ9YCSs0TX9gMRt+D1e/oavw6sukCQCEd
5I6hmtWQq7iznbxkI1BtBQqRzzlnArLFtb6ci5Rpr+YvMAGhTW5V0bHQEf9X9lZp
S3GXr5xvwVhyDBMNhdMCQQCgVI/9jnc5WW7M9vMxNjxIJ1/LD0G58A31pj0SM0rA
UlRz95QGWJ93+SA2fzM45NgjbTD7IOZ5O7GfBSEjSok5
-----END RSA PRIVATE KEY-----`

//下面的代码参考https://github.com/travist/jsencrypt/issues/147#issuecomment-534359519
//其实实际使用应该是在js环境下...这个代码只是参考
global.navigator = {
appName: 'nodejs'
}; // fake the navigator object
global.window = {}; // fake the window object
const JSEncrypt = require('jsencrypt').default;

const crypt = new JSEncrypt({
default_key_size: 512
});
// console.log(priveteKey, publicKey)

const rawStr = 'helloworld'

const n1cypt = new JSEncrypt();
n1cypt.setPublicKey(publicKey);
const encrptStr = n1cypt.encrypt(rawStr);

const n2cypt = new JSEncrypt();
n2cypt.setPrivateKey(priveteKey);
const decryptStr = n2cypt.decrypt(encrptStr);

console.log(rawStr, '\n', encrptStr, '\n', decryptStr);
//helloworld
// S4phrZr2XESkWCzEkj2MsjeBTAMdzxrGSOpoSHOiDKlJDWliRil5OFIk03OTh9f4U0yr8+8biLNhkpFwDR8CbvXRuhrf8/vRfAtpDsfWyFE7VXeEchp9r/YNgTHjPosnzZSxMGdx7+QTDZ4p2uIx0pgCXm5GzySPU3D/lyZLBgI=
// helloworld

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#参考 https://stackoverflow.com/questions/21327491/using-pycrypto-how-to-import-a-rsa-public-key-and-use-it-to-encrypt-a-string

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5

private_key = """-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDSLffg4ztiN4J7eSWhw3kIrrsM5IMnkYMAxJgWDaIXwo7mJRpC
dYkQtxLPnbE+zFklL1BLS923MFtAz5uLpIkf0LiVYoaAdGMMVfEfusnW8GcWPMse
/0jMZkb9RdSI7othUETJNYadUdBQ2tZoGryHJ6N2DU7RVvt+6KiysCGbXQIDAQAB
AoGBAMvW2Ti6KLWwsFOfwueH9LJ/A9oVJT4S2cK85+M7haEViBGcmkMqSqxzYUMJ
5QTKmwp9QJpGEdnAhD/vGSPthy4ZfaLLSDqjHgrSAVLTUp8ol1/n5V0G40EjvRWc
l4FKx8yHDcsOhkk4LcBDJnUCkZW5t+td8eASewtTMmFHDV3hAkEA859JvI881KoM
u5hCYBdIkSTsmjZdz68LTxDF3eB9Wgdsk4FS+OYkujl6+pch1fXIgjycs8V5eb6H
9QS8SHE/+wJBANzbs/IX2TcIPU6T4AKMt9QI/+lA/baLD6aaznxs8uvlVQ1XF6Y2
G91imkM0YgobSvUgleswWvjfJPicoVWeOocCQDTUoiTl2wczKTe1OQ+lfPMc0sJO
b+GN0CIBPSd0ezp+yajjLp8TKXvI40ZcZ9YCSs0TX9gMRt+D1e/oavw6sukCQCEd
5I6hmtWQq7iznbxkI1BtBQqRzzlnArLFtb6ci5Rpr+YvMAGhTW5V0bHQEf9X9lZp
S3GXr5xvwVhyDBMNhdMCQQCgVI/9jnc5WW7M9vMxNjxIJ1/LD0G58A31pj0SM0rA
UlRz95QGWJ93+SA2fzM45NgjbTD7IOZ5O7GfBSEjSok5
-----END RSA PRIVATE KEY-----"""

public_key = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSLffg4ztiN4J7eSWhw3kIrrsM
5IMnkYMAxJgWDaIXwo7mJRpCdYkQtxLPnbE+zFklL1BLS923MFtAz5uLpIkf0LiV
YoaAdGMMVfEfusnW8GcWPMse/0jMZkb9RdSI7othUETJNYadUdBQ2tZoGryHJ6N2
DU7RVvt+6KiysCGbXQIDAQAB
-----END PUBLIC KEY-----"""

msg = "helloworld"

keyPub = RSA.importKey(public_key)
cipher = Cipher_PKCS1_v1_5.new(keyPub)
cipher_text = cipher.encrypt(msg.encode()) # 公钥加密
print("cipher text->", cipher_text)


keyPriv = RSA.importKey(private_key)
cipher = Cipher_PKCS1_v1_5.new(keyPriv)
decrypt_text = cipher.decrypt(cipher_text, None).decode() # 私钥解密
print("decrypted msg->", decrypt_text)
assert msg == decrypt_text # check that