4

I found a post describing how to recover an Ethereum wallet keystore by guessing a single password, however, it uses node synchronous code, and I'm trying to convert it into asynchronous code so that I can use multithreading using worker_threads.

run.js (snippet)

for (let x = 0; x <= maxX*passesPerThread; x++) {
  passes[x +1] = c.iterate(passes[x], chars)
}
for (let x = 0; x < maxX; x++) {
  dcWorker[x] = new Worker('./worker.js', { workerData: { FILE: FILE, PASSWORD: passes[x*passesPerThread], workerNum: x, passesPerThread: passesPerThread, CHARS: chars}, })
  dcWorker[x].on('message', (data) => {
    if (data.status === 'done') {
      console.log(" w" + x + "d")
    }
  })
}

crack.js (snippet)

function crack(PASSWORD, FILE) {
  const data = fs.readFile(FILE, {encoding: "utf8"}, (err, data) => {
    if (err) throw err
    const content = data
    attempt(data, PASSWORD, FILE)
  })
}

function attempt(data, PASSWORD, FILE) {
  const keythereum = require("keythereum")
  const keyObject = JSON.parse(data)
  keythereum.recover(PASSWORD, keyObject, privateKey => {
    if (!privateKey.toString() === "message authentication code mismatch") {
      console.log("\npossible password: " + PASSWORD + "\naddress: " + `0x${keyObject.address}` + "\npriv key: " + `0x${privateKey}`)
      savePass(PASSWORD, FILE)
      //process.exit()
    }
  })
}

worker.js (snippet)

passes = new Array()
passes[0] = pass
maxX = workerData.passesPerThread

const cProm = 
  new Promise((resolve, reject) => {
    for (let x = 1; x < maxX; x++) {
      passes[x] = c.iterate(passes[x -1], chars)
    }
    resolve('done')
  })
  .then(value => {
    for (let x = 1; x < maxX; x++) {
      process.stdout.write(" w" + workerData.workerNum + "a" + passes[x] + "T" + Date.now() + ", ")
      c.crack(passes[x], FILE)
    }
    parentPort.postMessage({ status: value })
  })
  .catch(err => {
    parentPort.postMessage({ status: err })
  })

I don't understand why the stack is processing all output before actually attempting each crack attempt.

The following is output, then a long pause while it attempts to crack ONE password for each thread, instead of many passwords per thread like the output seems to indicate:

 w1aeasyaspsi, w1d
 w1aeasyaspsi,
 w1aeasyaspyeT1641634988273,  w1aeasyaspyaT1641634988274,  w1aeasyaspysT1641634988274,  w1aeasyaspyyT1641634988274,  w1aeasyaspypT1641634988274,  w1aeasyaspyiT1641634988274,  w1aeasyasppeT1641634988274,  w1aeasyasppaT1641634988274,  w1aeasyasppsT1641634988274,  w0aeasyaspaa, w3aeasyasiea, w0d
 w3d
 w0aeasyaspaa,
 w0aeasyaspasT1641634988279,  w0aeasyaspayT1641634988280,  w0aeasyaspapT1641634988280,  w0aeasyaspaiT1641634988280,  w0aeasyaspseT1641634988280,  w0aeasyaspsaT1641634988280,  w0aeasyaspssT1641634988280,  w0aeasyaspsyT1641634988280,  w0aeasyaspspT1641634988280,  w3aeasyasiea,
 w3aeasyasiesT1641634988279,  w3aeasyasieyT1641634988280,  w3aeasyasiepT1641634988280,  w3aeasyasieiT1641634988280,  w3aeasyasiaeT1641634988280,  w3aeasyasiaaT1641634988280,  w3aeasyasiasT1641634988280,  w3aeasyasiayT1641634988280,  w3aeasyasiapT1641634988280,  w2aeasyasppy, w2d
 w2aeasyasppy,
 w2aeasyaspppT1641634988296,  w2aeasyasppiT1641634988297,  w2aeasyaspieT1641634988297,  w2aeasyaspiaT1641634988297,  w2aeasyaspisT1641634988297,  w2aeasyaspiyT1641634988297,  w2aeasyaspipT1641634988297,  w2aeasyaspiiT1641634988298,  w2aeasyasieeT1641634988298,

after this is output almost instantaneously, it pauses while it attempts to crack 4 passwords, then the process drops out (without error).

I was expecting the output to pause after each comma , but it outputs everything before attempting anything.

The idea is to, for example, crack 10 passwords per thread, where there are 4 threads, then when the worker is terminated, a new worker in its place is started with the next 10 passwords, however, I'm just trying to run each worker_thread once to start until I can debug the rest of it.

So, my questions are:

  • Why isn't it attempting all crack attempts like the output seems to indicate?
  • How do I make the output align with what is actually being attempted when it's being attempted?
loud_flash
  • 67
  • 5
  • Could it be that having multiple threads writing to the console causes some blocking, buffering? I would not assume that the output is visible the moment print is called. Add time stamps – Emond Jan 07 '22 at 22:40
  • `await console.log(…)` isn’t a thing, note. `console.log` doesn’t return a promise. `await readFile(…, callback)` is also not a thing; you would use the version of `readFile` from fs.promises that returns a promise. But async instead of sync at the worker level is also pointless in general and will only make things slower since you’re CPU-bound. – Ry- Jan 08 '22 at 01:02
  • 1
    @Ry- i've removed the async and await statements, edited accordingly – loud_flash Jan 08 '22 at 06:37
  • @Emond i've added timestamps, and updated output in the edit, but given the instantaneousness of the output, and the significant pause at the end before it exits, it's not actually attempting during the output, but instead afterward. each attempt takes about a second. – loud_flash Jan 08 '22 at 06:51
  • As a quick note - your `crack` operation is async with callback API - you just fire off the file read (which schedules a callback) and proceed to the next item in the loop - *something* tells me this why every process.stdout write happens before the attempts. – Oleg Valter is with Ukraine Jan 08 '22 at 09:00
  • You should also edit the IPC part back in as it helps to see that the "done" message is also sent before the cracking is guaranteed to finish – Oleg Valter is with Ukraine Jan 08 '22 at 09:03
  • edited the promise back in, i'm a bit lost as to what's happening here - should i change the crack and attempt functions into promises? – loud_flash Jan 08 '22 at 09:48
  • @loud_flash [this](https://tsplay.dev/wg6EMW) should give you some clue about what is happening :) I used TS Playground for convenience, but you will not be able to it there, just make a local file and run with Node. You will notice something familiar about the ordering of the output. It's the [event loop](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#what-is-the-event-loop) ordering at play. – Oleg Valter is with Ukraine Jan 08 '22 at 18:48
  • You have a lot of possibilities here - you are not forced to use promises (although it is the simplest and idiomatic way), you can still use callbacks. But the easiest is to utilize `async/await` and what the `fs/promises` core module exposes (filesystem methods, promisified and ready for consumption). Ensure `attempt` finishes before `crack` is popped off the stack, and that the loop waits for `crack` to exit before proceeding to the next iteration – Oleg Valter is with Ukraine Jan 08 '22 at 18:51

1 Answers1

0

i gave up trying to understand promises (but i understand some of it) so i reverted to synchronous code while still implementing true multithreading via nodejs cluster and it now runs much, much faster than single threaded

run.js

//(synchronous functions not displayed)
//...
workers = new Array()
if (cluster.isMaster) {
  pass = ""
  if(PROGRESS === "CONTINUE") {
    if (checkFileExists(FILE + ".progress.txt")) {
      console.log("progress file found: continuing")
      try {
        const data = fs.readFileSync(FILE + ".progress.txt", 'utf8')
        pass = data
      } catch (err) {
        console.error(err)
      }
      if (!pass.length > 0) pass = chars.substring(0,1)
    } else {
      console.log("progress file not found: starting at beginning; this is ok if first time running")
      pass = chars.substring(0,1)
    }
  } else pass = chars.substring(0,1)
  
  let r = 0
  let someLeft = true
  let passes = new Array()
  passes[0] = pass
  for (let y = 1; y < cpuCount; y++) passes[y] = iterate(passes[y -1])
  let passTried = new Array()
  for (let y = 0; y < cpuCount; y++) passTried[y] = false
  for (let x = 0; x < cpuCount; x++) {
    workers[x] = cluster.fork()
    passTried[x] = true
    workers[x].send({ message: passes[x] })
    workers[x].on('message', function(msg) {
      if (msg.stat) {
        for (sworker in workers) {
          workers[sworker].process.kill()
        }
        process.exit()
      }
      if (msg.att) {
        process.stdout.write("\rattempted: " + msg.att)
        r++
        if (r % 100 == 0) {
          fs.writeFileSync(FILE + '.progress.txt', msg.att, function (err) {
            if (err) throw err
          })
          r = 0
        }
        for (let i = 0; i < cpuCount; i++) {
          if (!passTried[i]) {
        passTried[i] = true
            workers[x].send({ message: passes[i] })
            break
      }
    }
        someLeft = false
        for (let i = 0; i < cpuCount; i++) {
          if (!passTried[i]) {
        someLeft = true
            break
      }
    }
        if (!someLeft) {
      passes = getNewBlock(passes)
          for (let y = 0; y < cpuCount; y++) passTried[y] = false
          someLeft = true
    }
      }
    })
  }
} else {
  process.on('message', function(pass) {
    crack(pass.message)
    process.send({ att: pass.message })
  })
}

please note that this is a bit of an ugly hack-job and shouldn't be implemented in a server and definitely should not be implemented in production because it uses blocking code, but for my narrow purpose it does the job

thanks to everyone of whom tried to show me the way of using asynchronous code in the comments above

loud_flash
  • 67
  • 5