JSFuck []()!+
SFuck 是一种基于 JavaScript 原子部分的深奥且具有教育意义的编程风格。它仅使用六个不同的字符来编写和执行代码。
By @aemkei
它不依赖于浏览器,因此您甚至可以在 Node.js 上运行它。
加密: jsfuck.com
解密:de4js
Example
以下代码将执行alert(1)
:
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[
]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]
])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+
(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+
!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![
]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]
+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[
+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!!
[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![
]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[
]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![
]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(!
[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])
[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(
!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[
])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()
Basics
false => ![]
true => !![]
undefined => [][[]]
NaN => +[![]]
0 => +[]
1 => +!+[]
2 => !+[]+!+[]
10 => +[[+!+[]]+[+[]]]
Array => []
Number => +[]
String => []+[]
Boolean => ![]
Function => []["filter"]
run => []["filter"]["constructor"]( CODE )()
eval => []["filter"]["constructor"]("return eval")()( CODE )
window => []["filter"]["constructor"]("return this")()
参阅完整列表 here.
How it Works
[]
– 中括号
让我们从左括号和右括号开始,看看这里可以做什么。它们对于这个项目非常有用,并被视为核心元素,因为它们提供了一种方法:
- 处理数组
- 访问属性和方法。
[]
– 数组文字
创建新数组:
[] // 一个空数组
[[]] // 包含一个元素(另一个数组)的数组
[X][i]
– 数组/对象访问
[][[]] // undefined, 与[][""]一样
稍后我们将能够这样做:
"abc"[0] // 获取单个字母
[]["length"] // 获取属性
[]["fill"] // 获取方法
[X][0]
- 数组环绕技巧
通过将表达式包装在数组中,然后获取索引零处的元素,我们可以对一个表达式应用多个运算符。这意味着方括号“[]”可以代替圆括号“()”来隔离表达式:
[X][0] // X
++[ ++[ ++[X][0] ][0] ][0] // X + 3
+
– 加号
这个符号很有用,因为它允许我们:
- 创建数字
- 两个value相加
- 连接字符串
- 创建字符串
前版本的 JSFuck 经常使用它,但我们不确定它们是否是基础的。
转换为Number
+[] // 0 - the number 0
数字自增
使用上面提到的数组包装技巧:
++[ 0 ][ 0 ] // 1
++[ [] ][ +[] ] // 1
获取undefined
字符串
通过空数组中的索引获取元素将返回“undefined”:
[][ 0 ] // undefined
[][ +[] ] // 获取空数组第一个元素 (undefined)
[][ [] ] // look for property ""
获取NaN
将 undefined
转换为 Number 将导致非数字:
+[][[]] // +undefined = NaN
数值相加
1 + 1 // 2
++[[]][+[]] + ++[[]][+[]] // 2
更短的方式使用 ++:
++[ 1][ 0] // 2
++[++[[]][ 0]][ 0] // 2
++[++[[]][+[]]][+[]] // 2
使用这种技术,我们能够访问所有数字:
0
, 1
, 2
, 3
, 4
, 5
, 6
, 7
, 8
, 9
+[]
– 转换成String
组合加号和括号会将其他值转换为字符串:
[] +[] // "" - empty string
+[] +[] // "0"
[][[]] +[] // "undefined"
++[][[]] +[] // "NaN"
++[[]][+[]] +[] // "1"
"word"[i]
– 获取单个字符
由于我们有字符串,我们也可以获得单个字符:
"undefined" [ 0] // "u"
[ "undefined" ][ 0][ 0] // "u"
[ undefined +[] ][+[]][+[]] // "u"
[ [][[]] +[] ][+[]][+[]] // "u"
"undefined" [ 1 ] // "n"
[[][[]]+[]][+[]][ ++[[]][+[]] ] // "n"
由于我们有“NaN”和“undefined”,因此我们得到以下字符:
N
,a
,d
,e
,f
,i
,n
,u
.
+
– 组合字符
现在我们可以将字符连接到新单词。
// can be written using []+ only:
"undefined"[4] // "f"
"undefined"[5] // "i"
"undefined"[6] // "n"
"undefined"[3] // "d"
// combine using +
"f"+"i"+"n"+"d" // "find"
"e"
– 指数表示法中的数字
由于我们有来自undefined
的字符e
,我们可以使用指数表示法来构造非常大的数字并获得对Infinity
的引用:
+("1e309") // Infinity
+("1e309") +[] // "Infinity"
+("11e100") // 1.1e+101
+("11e100") +[] // "1.1e+101" (gives us `.` and `+`)
+("0.0000001") // 1e-7
+("0.0000001") +[] // "1e-7" (gives us `-`)
结果字符:
I
,f
,i
,n
,t
,y
,.
,+
,-
.
[]["method"]
– 访问方法
新组合的字符可以形成方法名称。可以使用方括号表示法来访问它们:
[]["f"+"i"+"n"+"d"] // 其中“f”是“false”的第一个字符,依此类推
[]["find"] // 和.find语法一样:
[] .find
Note: 对于"undefined", “NaN” and "Infinity"的字符, 我们能够在现有对象中找到的唯一方法是 Array.prototype.find
.
method+[]
– 获取方法定义
我们可以将一个方法转换为String并获取其定义:
[]["find"] +[]
返回下面String:
"function find() { [native code] }"
Note: native functions字符串表示形式不是 ECMAScript 标准的一部分,并且在浏览器之间有所不同。 例如,Firefox 将输出稍微不同的字符串,并使用附加换行符\n
.
结果字符:
a
,c
,d
,e
,f
,i
,n
,o
,t
,u
,v
{
,}
,(
,)
,[
,]
由此产生的方法:
.concat
.find
.
!
– 逻辑非运算符
这是原始 JSFuck 集中的第四个字符,用于创建布尔值。
Note: 该符号也可以替换为其他符号,例如<
or =
. 请参阅下面的“替代方案”部分。
!X
– 转换为 Boolean
逻辑非运算符可用于创建布尔值false
and true
:
![] // false
!![] // true
!X+[]
– 获取"true" and “false”
Booleans 可以转换成 string:
![] +[] // "false"
!![] +[] // "true"
这将使我们能够访问更多字符:
a
, e
, f
, l
, r
, s
, t
, u
.
与上面的集合一起,我们将有{}()[]+. INacdefilnorstuvy
可以访问这些方法:
call
concat
constructor
entries
every
fill
filter
find
fontcolor
includes
italics
reduce
reverse
slice
sort
Important: 我们可能会使用其他符号,例如 =
创建布尔值,因为它们更强大 (请参阅下面的“替代方案”部分。).
X["constructor"]
– 原始包装名称
通过 .constructor
我们可以引用创建实例的函数。 对于原始值,它返回相应的内置包装器:
0 ["constructor"] // Number
"" ["constructor"] // String
[] ["constructor"] // Array
false ["constructor"] // Boolean
[].find ["constructor"] // Function
使用 +[]
将它们转换为字符串并检索它们的函数名称以获得更多字符:
0["constructor"]+[] // "function Number() { ... }"
New chars available :
m
, b
, S
, g
, B
, A
, F
.
…以及更多方法和属性
arguments
big
bind
bold
name
small
some
sub
substr
substring
toString
trim
()
– 小括号
调用方法
由于我们可以访问方法,因此我们可以调用它们来获得更多功能。为此,我们需要引入另外两个符号 (
and )
.
没有参数的例子:
""["fontcolor"]() // "<font color="undefined"></font>"
[]["entries"]() +[] // "[object Array Iterator]"
结果字符:
j
, <
, >
, =
, "
, /
使用多个参数调用方法
调用具有多个参数的方法并非易事 - 为此,您可以使用以下命令
technique (discovered by trincot) - 例如:
调用字符串方法 "truefalse".replace("true","1")
可以写成 ["true", "1"].reduce("".replace.bind("truefalse"))
最后:
["true"]["concat"]("1")["reduce"](""["replace"]["bind"]("truefalse"))
调用数组方法 [1,2,3].slice(1,2)
可以写成 [1,2].reduce([].slice.bind([1,2,3]))
最后:
[1]["concat"](2)["reduce"]([]["slice"]["bind"]([1,2,3]))
在“流程”中调用具有多个参数的字符串方法
为了能够在上一个方法的结果的右侧调用方法(具有多个参数),您可以使用此方法
technique (discovered by trincot) - 例如: "truefalse".replace("true","1").replace("false","0")
can be written as
"truefalse"
.split().concat([["true", "1"]]).reduce("".replace.apply.bind("".replace))
.split().concat([["false", "0"]]).reduce("".replace.apply.bind("".replace))
最后:
"truefalse"
["split"]()["concat"]([["true"]["concat"]("1")])["reduce"](""["replace"]["apply"]["bind"](""["replace"]))
["split"]()["concat"]([["false"]["concat"]("0")])["reduce"](""["replace"]["apply"]["bind"](""["replace"]))
在“流程”中调用具有多个参数的数组方法
要以右侧(流)方式调用数组方法”,我们使用与字符串类似的技术,但有额外的技巧(details here) presented in following example: [3,4,5].slice(1,2).concat(6)
可以写成 [[3,4,5]].concat([[1,2]]).reduce([].slice.apply.bind([].slice)).concat(6)
(类似于字符串) 但现在我们需要找到右手边的方式来包装数组 [3,4,5]
和获取 [[3,4,5]]
和按照以下方法 [3,4,5].map([].constructor).concat([[[]]])[0].slice(-1)
所以我们得到
[3,4,5]
// call: slice(1,2)
.map([].constructor).concat([[[]]])[0].slice(-1)
.concat([[1,2]]).reduce([].slice.apply.bind([].slice))
// call next method (in flow)
.concat(6)
最后(删除点和逗号后)
[3]["concat"](4)["concat"](5)
["map"]([]["constructor"])["concat"]([[[]]])[0]["slice"](-1)
["concat"]([[1]["concat"](2)])["reduce"]([]["slice"]["apply"]["bind"]([]["slice"]))
["concat"](6)
number.toString(x)
– 获取任意小写字母
Number的 toString
方法有一个可选参数,指定要使用的基数 (between 2 and 36). 使用基数 36 我们可以获取 lowercase
10["toString"](36) // "a"
11["toString"](36) // "b"
...
34["toString"](36) // "y"
35["toString"](36) // "z"
暴露出的字符: abcdefghijklmnopqrstuvwxyz
Function("code")()
– 执行代码
Function 构造函数是 JSFuck 中的万能钥匙: 它接受一个字符串作为参数,并返回一个新的匿名函数,并以此字符串作为函数体。所以它基本上可以让你将任何代码作为字符串进行评估。这就像 eval
, 无需引用全局范围 (a.k.a. window
). 我们能得到 Function constructor e.g. with []["find"]["constructor"]
.
这是第一个主要步骤,也是 JS-to-JSFuck 编译器的重要组成部分.
…
Function("return this")()
– window
当执行 function anonymous() { return this }
, 我们在这里获得调用上下文,它是对全局范围的引用: window
!
获得对window
的引用是 JSFuck 向前迈出的又一大步。 使用括号字符,我们只能挖掘可用的对象:数字、数组、一些函数…以及对全局范围的引用, 我们现在可以访问任何全局变量以及这些全局变量的内部属性。
创建正则表达式对象
您可以创建正则表达式,例如 /pattern/g
如下
[]["fill"]["constructor"]("return RegExp")()("pattern","g")
删除逗号后 (通过使用 multi-arguments technique without bind
ing) 看起来如下:
["pattern"]["concat"]("g")["reduce"]([]["fill"]["constructor"]("return RegExp")())
替代方案
组合字符
代替 +
我们可以使用 .concat
组合字符:
"f"["concat"]("i")["concat"]("l")["concat"]("l") // fill
问题:我们需要组合“c”、“o”、“n”、“c”、“a”和“t”来得到“concat”。
布尔值
!
可能会被替换为具有多种用途的更“强大”的字符。
=
– Boolean + 赋值
X == X // true
X == Y // false
X = Y // assign a new value
>
– Boolean + 创建 Numbers
X > Y // true
X > X // false
X >> Y // number
一个更复杂的例子是仅使用 []>+
来获取字符“f”:
[[ []>[] ] + [] ] [[]>>[]] [[]>>[]]
[[ false ] + [] ] [ 0] [ 0]
[ "false" ] [ 0] [ 0]
"false" [ 0]
Numbers
代替 +
我们可以使用布尔值和位移运算符来创建数字:
true >> false // 1
true << true // 2
true << true << true // 4
问题: 一些数字 (like 5
) 更难得到。. 但使用字符串时是可能的,例如 "11" >> true
.
执行函数
除了使用 ()
之外的执行函数的方式:
- 使用反引号:
`
- 处理事件:
on...
- 构造函数:
new ...
- 类型转换:
toString|valueOf
- 符号数据类型:
[Symbol...]
使用反引号
代替左括号和右括号, 我们可以使用反引号 ` 执行方法. 在 ES6 中,它们可用于插入字符串并为标记模板文字提供表达式。
([]["entries"]``).constructor // Object
这将为我们提供“Object”中的字符并访问其方法。
不幸的是,我们只能传递一个字符串 (从我们的基本字母表中,例如 []!+
) 作为参数.无法调用具有多个参数或预编译字符串的方法。 为此,我们必须使用“${}”进行表达式插值,这会引入新字符。
详细讨论了反引号的可能性 in the Gitter chat room.
映射类型转换
另一种执行不带括号的函数的方法是映射.toString
或.valueOf
方法并隐式调用它们。
A = []
A["toString"] = A["pop"]
A+"" // will execute A.pop
Note: 没有办法传递参数,它需要 =
出现在我们的基本字母表中. 它仅适用于返回基本类型的方法。
到目前为止,唯一的用例是在 Firefox 中连接 .toSource
以获得特殊字符,例如反斜杠 \
.
触发事件处理程序
函数或方法也可以通过将它们分配给事件处理程序来执行。有几种方法可以做到这一点,例如:
// 启动时覆盖 onload 事件
onload = f
// 写入图像标签
document.body.innerHTML = '<img οnerrοr=f src=X />'
// 抛出并处理错误
onerror=f; throw 'x'
// 触发事件
onhashchange = f; location.hash = 1;
Note: 我们需要 =
来分配处理程序。
Problem: 我们无法访问window
或 DOM
元素来附加事件处理程序。
Constructor
我们还可以使用new
运算符将函数作为伪对象类型调用:
new f
Problem: 我们的基本符号集还不能使用new
运算符。
Symbol
Symbol是唯一且不可变的数据类型,可用作对象属性的标识符。这可用于隐式调用函数。
f[Symbol.toPrimitive] = f; f++;
f[Symbol.iterator] = f; [...f];
Note: 我们需要“=”来分配函数。
Problem: 我们无法使用简化的字符集访问“Symbol”。