12

Preface

In JavaScript, can you execute console.log(1) without using English letters and numbers?

In other words, it is that no English letters (a-zA-Z) and numbers (0-9) can appear in the code, except for all kinds of symbols. After performing Sigma, performs console.log(1) , and then outputs the console 1 .

If you think about what libraries or services you can use to do it, don't rush to say the answer. Think about it for yourself first, and see if there is a way to write it yourself. If you can write it yourself from scratch, it means that you are familiar with the js language and various automatic type conversions.

Analyze a few key points

To successfully implement the console.log(1) required by the subject, several key points must be completed:

  1. Find out how to execute the code
  2. How to get numbers without letters and numbers
  3. How to get letters without letters and numbers

As long as these three points are resolved, the requirements of the problem can be achieved.

Solve the first point first: find out how to execute the code

Find out how to execute the code

Direct console.log is not possible, because even if you spell out a string console , there is no way you like PHP as take string to execute the function.

What about eval? You can put strings in evali, it can be. The problem is that we can't use eval either, because English letters can't be used.

Is there any other way? You can also use function constructor: new Function("console.log(1)") to execute, but the problem is that we can't use new , so it doesn't work at first glance, but you don't need new . Just use Function("console.log(1)") to create a function that can execute specific code.

So the next question becomes how to get the function constructor, as long as you can get it, there is a chance

In JS, you can use .Constructor to get the constructor of an object. For example, "".constructor will get: ƒ String() { [native code] } . If you have a function, you can get the function constructor, like this: (()=>{}).constructor . In this question, we can’t directly use .constructor , Should use: (()=>{})['constructor'] .

If ES6 is not supported, what should I do if I can’t use arrow functions? Is there a way to get a function?

[]['fill']['constructor'] , and it’s easy. There are various built-in functions. For example, 060b5a9c8ce0b7 is actually [].fill.constructor or ""['slice']['constructor'] . You can also get the function constructor, so this is not a problem, even if there is no arrow function.

The code we expect at the beginning is this: Function('console.log(1)')() , if you rewrite it with the previous method, you should replace the previous Function with (()=>{})['constructor'] , which becomes (()=>{})['constructor']('console.log(1)')()

Just think of a way to piece together this code and the problem will be solved. Now we have solved the first problem: find a way to execute the function.

How to get numbers

The next number is relatively simple.

The key here is the forced polymorphism . If you have read js type conversion articles, you may remember that {} + [] can get the number 0

Assuming you don't know this, let me explain: Using ! , you can get false , for example, 1[] or !{} can get false . Then two false sum can be obtained 0 : ![] + ![] , and so on, since ![] is false , that front plus a ! , !![] is true , so ![] + !![] equivalent to false + true , is 0 + 1 , the result is 1 .

Or with a shorter method for +[] may be obtained using automatic conversion 0 this result, it +!![] is 1 .

After you have 1, you can get all the numbers, as long as you continue to add << >> or the multiplication sign, for example, to make up 8 , which is 1 << 3 , or 2 << 2 , to make up 2 is (+!![])+(+!![]) , so (+!![])+(+!![]) << (+!![])+(+!![]) is 8 , only four 1 are needed, no need to add 8 times.

But for now, you can ignore the length, just consider whether you can get it together, as long as you can get 1 is enough.

How to get the string

The last thing is to find a way to make up the string, or to get every character in (()=>{})['constructor']('console.log(1)')()

How can I get the characters? The answer is the same as the number, that is, forced polymorphism .

As mentioned above, ![] can get false , then add an empty string at the end: ![] + '' , can't you get "false" ? In this way, you can get the five characters a, e, f, l, s. For example, (![] + '')[1] is a . For record convenience, let’s write a small piece of code:

const mapping = {
  a: "(![] + '')[1]",
  e: "(![] + '')[4]",
  f: "(![] + '')[0]",
  l: "(![] + '')[2]",
  s: "(![] + '')[3]",
}

Now that there is false , it is not difficult true !![] + '' can get true , now change the code to:

const mapping = {
  a: "(![] + '')[1]",
  e: "(![] + '')[4]",
  f: "(![] + '')[0]",
  l: "(![] + '')[2]",
  r: "(!![] + '')[1]",
  s: "(![] + '')[3]",
  t: "(!![] + '')[0]",
  u: "(!![] + '')[2]",
}

Then use the same method, use ''+{} to get "[object Object]" (or you can use the magical []+{} ), and now the code can be updated to this:

const mapping = {
  a: "(![] + '')[1]",
  b: "(''+{})[2]",
  c: "(''+{})[5]",
  e: "(![] + '')[4]",
  f: "(![] + '')[0]",
  j: "(''+{})[3]",
  l: "(![] + '')[2]",
  o: "(''+{})[1]",
  r: "(!![] + '')[1]",
  s: "(![] + '')[3]",
  t: "(!![] + '')[0]",
  u: "(!![] + '')[2]",
}

From an array of objects or take a nonexistent property will return undefined , then undefined together with string, you can get a string of undefined , like this: [][{}]+'' , you can get undefined .

After getting it, our conversion table becomes more complete:

const mapping = {
  a: "(![] + '')[1]",
  b: "(''+{})[2]",
  c: "(''+{})[5]",
  d: "([][{}]+'')[2]",
  e: "(![] + '')[4]",
  f: "(![] + '')[0]",
  i: "([][{}]+'')[5]",
  j: "(''+{})[3]",
  l: "(![] + '')[2]",
  n: "([][{}]+'')[1]",
  o: "(''+{})[1]",
  r: "(!![] + '')[1]",
  s: "(![] + '')[3]",
  t: "(!![] + '')[0]",
  u: "(!![] + '')[2]",
}

Take a look at the conversion table, and then take a look at our target string: (()=>{})['constructor']('console["log"](1)')() . If you compare it a little, you will find that constructor is no problem to get 060b5a9c8ce641, and there is no problem to get console , but only the log is missing. g , currently there is no such character in our conversion table.

So we need to get g from somewhere to piece together the string we want. Or you can use another method to get the characters.

I thought of two methods at first. The first one is to use hexadecimal conversion. When converting a number from toString to a string, you can take a parameter radix , which represents how many hexadecimals the number should be converted into. For example, (10).toString(16) will get a Because the decimal 10 is the hexadecimal a .

A total of 26 letters of the alphabet, numbers, there are 10, so long as (10).toString(36) can get a , with (16).toString(36) can get g , you can get all the letters of the alphabet using this method. But here comes the problem. toString itself also has g , but we don't have one now, so this method doesn't work.

Another method is to use base64. JS has two built-in functions: btoa atob . btoa is to encode a string into base64. For example, btoa('abc') will get YWJj , and then atob('YWJj') to get abc .

Just think of a way to make the base64 encoded result g , you can write the code to run, or you can try it yourself slowly, fortunately btoa(2) can get the string Mg== So btoa(2)[1] result is g up.

But the next question comes again, how to execute btoa ? The same can only be done through the above function constructor: (()=>{})['constructor']('return btoa(2)[1]')() , this time every character is collected.

You can combine the mapping above and write a simple code to help with the conversion. The goal is to convert a string into a form without characters:

const mapping = {
  a: "(![] + '')[1]",
  b: "(''+{})[2]",
  c: "(''+{})[5]",
  d: "([][{}]+'')[2]",
  e: "(![] + '')[4]",
  f: "(![] + '')[0]",
  i: "([][{}]+'')[5]",
  j: "(''+{})[3]",
  l: "(![] + '')[2]",
  n: "([][{}]+'')[1]",
  o: "(''+{})[1]",
  r: "(!![] + '')[1]",
  s: "(![] + '')[3]",
  t: "(!![] + '')[0]",
  u: "(!![] + '')[2]",
}

const one = '(+!![])'
const zero = '(+[])'

function transformString(input) {
  return input.split('').map(char => {
    // 先假设数字只会有个位数,比较好做转换
    if (/[0-9]/.test(char)) {
      if (char === '0') return zero
      return Array(+char).fill().map(_ => one).join('+')
    }
    if (/[a-zA-Z]/.test(char)) {
      return mapping[char]
    }
    return `"${char}"`
  })
  // 加上 () 保证执行顺序
  .map(char => `(${char})`)
  .join('+')
}

const input = 'constructor'
console.log(transformString(input))

The output is:

((''+{})[5])+((''+{})[1])+(([][{}]+'')[1])+((![] + '')[3])+((!![] + '')[0])+((!![] + '')[1])+((!![] + '')[2])+((''+{})[5])+((!![] + '')[0])+((''+{})[1])+((!![] + '')[1])

You can write another function that only converts the numbers and removes the numbers:

function transformNumber(input) {
  return input.split('').map(char => {
    // 先假设数字只会有个位数,比较好做转换
    if (/[0-9]/.test(char)) {
      if (char === '0') return zero
      let newChar = Array(+char).fill().map(_ => one).join('+')
      return`(${newChar})`
    }
    return char
  })
  .join('')
}

const input = 'constructor'
console.log(transformNumber(transformString(input)))

The result is:

((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((''+{})[((+!![]))])+(([][{}]+'')[((+!![]))])+((![] + '')[((+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((!![] + '')[((+!![]))])+((!![] + '')[((+!![])+(+!![]))])+((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((''+{})[((+!![]))])+((!![] + '')[((+!![]))])

constructor the result to the console for execution, and found that the value obtained is 060b5a9c8ce9e5. So synthesize the above code and go back to the paragraph just now: (()=>{})['constructor']('return btoa(2)[1]')() , to get the converted result is:

const con = transformNumber(transformString('constructor'))
const fn = transformNumber(transformString('return btoa(2)[1]'))
const result = `(()=>{})[${con}](${fn})()`
console.log(result)

The result is very long and I won’t post it, but I can get a g .

Before continuing, change the code and add a function that can directly convert the code:

function transform(code) {
  const con = transformNumber(transformString('constructor'))
  const fn = transformNumber(transformString(code))
  const result = `(()=>{})[${con}](${fn})()`
  return result;
}

console.log(transform('return btoa(2)[1]'))

Well, here is actually very close to the end, only one thing has not been resolved, that is btoa is WebAPI, browsers only, node.js does not have this function, so if you want to be more beautiful, you must Find other ways to generate the character g

Recall the beginning mentioned, with function.constructor can get the function constructor, and so on, with ''['constructor'] can get a string constructor, just add a string, you can get the contents of the string constructor!

''['constructor'] + '' like this: 060b5a9c8ceb5c, the result is: "function String() { [native code] }" , a bunch of strings are available all of a sudden, and the g we are thinking about is: (''['constructor'] + '')[14] .

Since our converter currently only supports one-digit numbers (because it is easy to make), we changed it to: (''['constructor'] + '')[7+7] , which can be written like this:

mapping['g'] = transform(`return (''['constructor'] + '')[7+7]`)

Integrate all results

After all the hardships, I finally came up with the most troublesome g , combined with the converter we just wrote, we can successfully generate console.log(1) with letters and numbers removed:

const mapping = {
  a: "(![] + '')[1]",
  b: "(''+{})[2]",
  c: "(''+{})[5]",
  d: "([][{}]+'')[2]",
  e: "(![] + '')[4]",
  f: "(![] + '')[0]",
  i: "([][{}]+'')[5]",
  j: "(''+{})[3]",
  l: "(![] + '')[2]",
  n: "([][{}]+'')[1]",
  o: "(''+{})[1]",
  r: "(!![] + '')[1]",
  s: "(![] + '')[3]",
  t: "(!![] + '')[0]",
  u: "(!![] + '')[2]",
}

const one = '(+!![])'
const zero = '(+[])'

function transformString(input) {
  return input.split('').map(char => {
    // 先假设数字只会有个位数,比较好做转换
    if (/[0-9]/.test(char)) {
      if (char === '0') return zero
      return Array(+char).fill().map(_ => one).join('+')
    }
    if (/[a-zA-Z]/.test(char)) {
      return mapping[char]
    }
    return `"${char}"`
  })
  // 加上 () 保证执行顺序
  .map(char => `(${char})`)
  .join('+')
}

function transformNumber(input) {
  return input.split('').map(char => {
    // 先假设数字只会有个位数,比较好做转换
    if (/[0-9]/.test(char)) {
      if (char === '0') return zero
      let newChar = Array(+char).fill().map(_ => one).join('+')
      return`(${newChar})`
    }
    return char
  })
  .join('')
}

function transform(code) {
  const con = transformNumber(transformString('constructor'))
  const fn = transformNumber(transformString(code))
  const result = `(()=>{})[${con}](${fn})()`
  return result;
}

mapping['g'] = transform(`return (''['constructor'] + '')[7+7]`)
console.log(transform('console.log(1)'))

The final code:

(()=>{})[((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((''+{})[((+!![]))])+(([][{}]+'')[((+!![]))])+((![] + '')[((+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((!![] + '')[((+!![]))])+((!![] + '')[((+!![])+(+!![]))])+((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((''+{})[((+!![]))])+((!![] + '')[((+!![]))])](((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((''+{})[((+!![]))])+(([][{}]+'')[((+!![]))])+((![] + '')[((+!![])+(+!![])+(+!![]))])+((''+{})[((+!![]))])+((![] + '')[((+!![])+(+!![]))])+((![] + '')[((+!![])+(+!![])+(+!![])+(+!![]))])+(".")+((![] + '')[((+!![])+(+!![]))])+((''+{})[((+!![]))])+((()=>{})[((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((''+{})[((+!![]))])+(([][{}]+'')[((+!![]))])+((![] + '')[((+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((!![] + '')[((+!![]))])+((!![] + '')[((+!![])+(+!![]))])+((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((''+{})[((+!![]))])+((!![] + '')[((+!![]))])](((!![] + '')[((+!![]))])+((![] + '')[((+!![])+(+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((!![] + '')[((+!![])+(+!![]))])+((!![] + '')[((+!![]))])+(([][{}]+'')[((+!![]))])+(" ")+("(")+("'")+("'")+("[")+("'")+((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((''+{})[((+!![]))])+(([][{}]+'')[((+!![]))])+((![] + '')[((+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((!![] + '')[((+!![]))])+((!![] + '')[((+!![])+(+!![]))])+((''+{})[((+!![])+(+!![])+(+!![])+(+!![])+(+!![]))])+((!![] + '')[(+[])])+((''+{})[((+!![]))])+((!![] + '')[((+!![]))])+("'")+("]")+(" ")+("+")+(" ")+("'")+("'")+(")")+("[")+((+!![])+(+!![])+(+!![])+(+!![])+(+!![])+(+!![])+(+!![]))+("+")+((+!![])+(+!![])+(+!![])+(+!![])+(+!![])+(+!![])+(+!![]))+("]"))())+("(")+((+!![]))+((+!![])+(+!![]))+((+!![])+(+!![])+(+!![]))+(")"))()

With 1,800 characters, successfully wrote only: [ , ], ( ), { , } , " , ' , + , ! , = , > the 12-character program, and the smooth implementation of console.log(1) .

And because we can get the words String smoothly, we can use the bit conversion method mentioned before to get any lowercase characters, like this:

mapping['S'] = transform(`return (''['constructor'] + '')[9]`)
mapping['g'] = transform(`return (''['constructor'] + '')[7+7]`)
console.log(transform('return (35).toString(36)')) // z

How do you get any uppercase characters, or even any characters? I also thought of several ways.

If you want to get any character, you can pass String.fromCharCode , or write it in another form: ""['constructor']['fromCharCode'] , you can get any character. But before that, we have to find a way to get the capitalized C , and we need to think about it again.

In addition to this path, there is another one, which is to rely on coding. For example, '\u0043' is actually a capital C, so I originally thought it could be done this way, but I tried it and it didn’t work, like console.log("\u0043") would It is correct to print C, but console.log(("\u00" + "43")) will directly spray an error to you. It seems that there is no way to put the code together like this (think carefully and find that it is quite reasonable).

In addition to this path, there is another method, which is to rely on encoding. For example, '\u0043' is actually the uppercase C , so I originally thought that this method could be used to make it together, but I tried it and it didn’t work. For example, console.log("\u0043") would print. C correct, but console.log(("\u00" + "43")) will directly report an error. It seems that the code cannot be put together like this. But it makes sense to think about it.

to sum up

The conversion function written at the end is actually not complete. There is no way to execute arbitrary code. The jsfuck 160b5a9c8cef5b has been written very clearly. The conversion process is described in detail in the README, and In the end, only 6 characters were used. I really admire it.

In its Code which can also be seen how the conversion, capital C part is used on a String named italics function, it can produce <i></i> , after then call escape , get %3Ci%3E%3C/i%3E , then get capital C .

Some people may say that I usually write BUG well, what is the use of messing up, but the focus of this is not on the final result, but on training a few things:

  1. For the familiarity of the js language, we have used a lot of type conversions and built-in methods to piece together things, some of which may be something you have never heard of.
  2. The ability to narrow down the problem when solving problems, from how to execute the string as a function, to piece together numbers and strings, step by step to narrow the problem, after the sub-problem is solved, the original problem is solved

173382ede7319973.gif


This article first published WeChat public account: Front-end Pioneer

Welcome to scan the QR code to follow the official account, and push you fresh front-end technical articles every day

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


Welcome to continue reading other highly praised articles in this column:



疯狂的技术宅
44.4k 声望39.2k 粉丝