A blog Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description. http://yourdomain.com/ Tue, 03 Jan 2023 05:31:55 +0000 Tue, 03 Jan 2023 05:31:55 +0000 Jekyll v4.3.1 2022 Advent of Code <p>I'm having a go at the <a href="https://adventofcode.com/2022/">2022 Advent of Code</a>. Here's how I solved the problems - the ones I could be bothered solving anyway.</p> <h1 id="a-hrefhttpsadventofcodecom2022day101a"><a href="https://adventofcode.com/2022/day/1">01</a></h1> <p>Use this awk script to convert the data to a CSV, one row per elf:</p> <pre><code>/[0-9]/ { printf &quot;%d,&quot;, $0 } /^$/ { print &quot;&quot; } </code></pre> <p>Load the result into a spreadsheet application, sum the rows, and sort the totals to get the answers.</p> <h1 id="a-hrefhttpsadventofcodecom2022day202a"><a href="https://adventofcode.com/2022/day/2">02</a></h1> <p>The score depends only on the second character (X, Y or Z), and whether you win, lose or draw. I went for Python this time.</p> <pre><code>import sys import functools player_score = { 'X': 1, 'Y': 2, 'Z': 3 } win_scores = { 'X': { 'A': 3, 'B': 0, 'C': 6 }, 'Y': { 'A': 6, 'B': 3, 'C': 0 }, 'Z': { 'A': 0, 'B': 6, 'C': 3 }, } print (functools.reduce( lambda score, line: score + player_score[line[2]] + win_scores[line[2]][line[0]], (l for l in sys.stdin if len(l) &gt;= 3), 0 )) </code></pre> <p>Part two is a little simpler, you can precalculate each result:</p> <pre><code>import sys import functools scores = { 'A': { 'X': 0 + 3, 'Y': 3 + 1, 'Z': 6 + 2 }, 'B': { 'X': 0 + 1, 'Y': 3 + 2, 'Z': 6 + 3 }, 'C': { 'X': 0 + 2, 'Y': 3 + 3, 'Z': 6 + 1 }, } print (functools.reduce( lambda score, line: score + scores[line[0]][line[2]], (l for l in sys.stdin if len(l) &gt;= 3), 0 )) </code></pre> <h1 id="a-hrefhttpsadventofcodecom2022day303a"><a href="https://adventofcode.com/2022/day/3">03</a></h1> <p>Get the first half of the string, look for the first character in the second half in the first half</p> <pre><code>import sys import functools # the score of each item; a = 1 scores = list(' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') def score(line): half = len(line) // 2 first_half = line[0:half] try: common = next(l for l in line[half:] if l in first_half) return scores.index(common) except StopIteration: # no item in common return 0 print(functools.reduce( lambda s, line: s + score(line.strip()), sys.stdin, 0 )) </code></pre> <p>Part 2:</p> <pre><code>import sys import functools import itertools # the score of each item; a = 1 scores = list(' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') def score(lines): rest = lines[1:] common = next(c for c in lines[0] if all(c in l for l in rest)) return scores.index(common) print(functools.reduce( lambda s, lines: s + score([l.strip() for l in lines]), # take groups of 3 until the result is empty iter(lambda: list(itertools.islice(sys.stdin, 3)), []), 0 )) </code></pre> <h1 id="a-hrefhttpsadventofcodecom2022day404a"><a href="https://adventofcode.com/2022/day/4">04</a></h1> <pre><code>import sys import re line_re = re.compile(r'(\d+)-(\d+),(\d+)-(\d+)') def within(line): n = [int(n) for n in line_re.match(line).group(1, 2, 3, 4)] return n[0] &gt;= n[2] and n[1] &lt;= n[3] or \ n[2] &gt;= n[0] and n[3] &lt;= n[1] print(sum( within(line) and 1 or 0 for line in sys.stdin )) </code></pre> <p>Part 2 is the same, with the return value:</p> <pre><code> return n[0] &lt;= n[3] and n[1] &gt;= n[2] or \ n[1] &lt;= n[2] and n[0] &gt;= n[3] </code></pre> <h1 id="a-hrefhttpsadventofcodecom2022day505a"><a href="https://adventofcode.com/2022/day/5">05</a></h1> <p>The stacks are represented by lists. The initial state is loaded into the start of each list, then manipulated at the end of the lists.</p> <pre><code>import sys import re move_re = re.compile('move (\d*) from (\d*) to (\d*)') stack_count = 9 stacks = [[] for n in range(stack_count)] for line in sys.stdin: if len(line) &lt; 36: break for n in range(stack_count): char = line[n * 4 + 1] if char != ' ': stacks[n].insert(0, char) for line in sys.stdin: match = move_re.match(line) if not match: continue number, src, to = (int(n) for n in move_re.match(line).group(1, 2, 3)) transferred = stacks[src-1][-number:] transferred.reverse() del stacks[src-1][-number:] stacks[to-1].extend(transferred) print(&quot;&quot;.join(s[-1] for s in stacks)) </code></pre> <p>For part two, remove the <code>reverse</code> line (which I did first, because I didn't read the instructions properly!)</p> <h1 id="a-hrefhttpsadventofcodecom2022day606a"><a href="https://adventofcode.com/2022/day/6">06</a></h1> <p>Almost a one liner:</p> <pre><code>import sys data = sys.stdin.read() count = 4 print(next(n for n in range(count, len(data)) if len(set(data[n-count:n])) == count)) </code></pre> <p>Change count to 14 for the second step.</p> <h1 id="a-hrefhttpsadventofcodecom2022day707a"><a href="https://adventofcode.com/2022/day/7">07</a></h1> <pre><code>import sys root = {} dir = root for line in sys.stdin: if line.startswith(&quot;$ cd &quot;): name = line[5:-1] # chop off the newline if name == '/': dir = root else: dir = dir[name] elif not line.startswith(&quot;$&quot;): size, name = line.split(maxsplit=1) name = name[:-1] # chop off the newline if size == &quot;dir&quot;: dir.setdefault(name, {&quot;..&quot;: dir}) else: dir[name] = int(size) def sizes(dir, all=[]): total = 0 for name, val in dir.items(): if name == '..': continue if type(val) == dict: total += sizes(val, all)[-1] else: total += val all.append(total) return all print(sum(s for s in sizes(root) if s &lt;= 100000)) </code></pre> <p>For part 2, change the final <code>print</code> to</p> <pre><code>allsizes = sizes(root) used = allsizes[-1] required = 30000000 - (70000000 - used) print(min(s for s in sizes(root) if s &gt;= required)) </code></pre> <h1 id="a-hrefhttpsadventofcodecom2022day808a"><a href="https://adventofcode.com/2022/day/8">08</a></h1> <p>Create iterators extending from each side of the grid. Store the coordinates of seen trees in a set.</p> <pre><code>import sys grid = [] for line in sys.stdin: grid.append([int(n) for n in line if n.isdigit()]) visible = set() def look(iter): highest = -1 for coord, value in iter: if value &gt; highest: visible.add(coord) highest = value for y, line in enumerate(grid): look(((x, y), line[x]) for x in range(len(line) - 1)) look(((x, y), line[x]) for x in range(len(line) - 1, 0, -1)) for x in range(len(grid[0]) - 1): look(((x, y), grid[y][x]) for y in range(len(grid) - 1)) look(((x, y), grid[y][x]) for y in range(len(grid) - 1, 0, -1)) print(len(visible)) </code></pre> <h1 id="a-hrefhttpsadventofcodecom2022day909a"><a href="https://adventofcode.com/2022/day/9">09</a></h1> <pre><code>import sys visited = set((0,0)) head = (0, 0) tail = (0, 0) dirs = { 'U': lambda x: (x[0], x[1] - 1), 'D': lambda x: (x[0], x[1] + 1), 'L': lambda x: (x[0] - 1, x[1]), 'R': lambda x: (x[0] + 1, x[1]), } signum = lambda x: x &lt; 0 and -1 or x &gt; 0 and 1 or 0 for line in sys.stdin: direction, count = line.split() dir_fn = dirs[direction] for n in range(int(count)): head = dir_fn(head) dx = head[0] - tail[0] dy = head[1] - tail[1] if abs(dx) &gt; 1 or abs(dy) &gt; 1: tail = (tail[0]+signum(dx), tail[1]+signum(dy)) visited.add(tail) print(len(visited)) </code></pre> <p>Apparently this is wrong. I took a guess and subtracted one, and that was right. It looks like I got the constructor call for <code>visited</code> wrong.</p> <p>The second part involves extending it to a rope of 10 elements:</p> <pre><code>import sys visited = set() visited.add((0,0)) rope = [(0, 0)] * 10 dirs = { 'U': lambda x: (x[0], x[1] - 1), 'D': lambda x: (x[0], x[1] + 1), 'L': lambda x: (x[0] - 1, x[1]), 'R': lambda x: (x[0] + 1, x[1]), } signum = lambda x: x &lt; 0 and -1 or x &gt; 0 and 1 or 0 for line in sys.stdin: direction, count = line.split() dir_fn = dirs[direction] for n in range(int(count)): rope[0] = dir_fn(rope[0]) for n in range(0,9): a = rope[n] b = rope[n+1] dx = a[0] - b[0] dy = a[1] - b[1] if abs(dx) &gt; 1 or abs(dy) &gt; 1: rope[n+1] = (b[0]+signum(dx), b[1]+signum(dy)) visited.add(rope[-1]) print(len(visited)) </code></pre> <h1 id="a-hrefhttpsadventofcodecom2022day1010a"><a href="https://adventofcode.com/2022/day/10">10</a></h1> <pre><code>import sys import unittest def run(lines): x = 1 for line in lines: if line.startswith(&quot;noop&quot;): yield x elif line.startswith(&quot;addx &quot;): x = x + int(line[5:]) yield x yield x class TestExecution(unittest.TestCase): def test_example(self): self.assertEqual([1, 4, 4, -1, -1], list(run(['noop', 'addx 3', 'addx -5']))) unittest.main(exit=False) # Subtract two from the index for the delay, one more because # it's 1 indexed print(sum(x * (c+3) for c, x in enumerate(run(sys.stdin)) if c in range(17, 221, 40))) </code></pre> <p>For the second part, change the last line to:</p> <pre><code>out = itertools.chain([1, 1], run(sys.stdin)) for y in range(6): for x in range(40): val = next(out) print((x &gt;= val-1 and x &lt;= val+1) and '#' or '.', end='') print() </code></pre> <h1 id="a-hrefhttpsadventofcodecom2022day1111a"><a href="https://adventofcode.com/2022/day/11">11</a></h1> <p>This one was tough - there are plenty of chances to misread the problem and type the wrong data.</p> <pre><code>import sys class Monkey: def __init__(self, num, items, op, divisor, testtrue, testfalse): self.num = num self.items = items self.op = op self.divisor = divisor self.testtrue = testtrue self.testfalse = testfalse self.inspections = 0 def inspect(self, monkeys): items = self.items self.items = [] self.inspections = self.inspections + len(items) for i in items: w = self.op(i) // 3 if (w % self.divisor) == 0: monkeys[self.testtrue].items.append(w) else: monkeys[self.testfalse].items.append(w) monkeys = [ Monkey(0, [83, 97, 95, 67], lambda x: x * 19, 17, 2, 7), Monkey(1, [71, 70, 79, 88, 56, 70], lambda x: x + 2, 19, 7, 0), Monkey(2, [98, 51, 51, 63, 80, 85, 84, 95], lambda x: x + 7, 7, 4, 3), Monkey(3, [77, 90, 82, 80, 79], lambda x: x + 1, 11, 6, 4), Monkey(4, [68], lambda x: x * 5, 13, 6, 5), Monkey(5, [60, 94], lambda x: x + 5, 3, 1, 0), Monkey(6, [81, 51, 85], lambda x: x * x, 5, 5, 1), Monkey(7, [98, 81, 63, 65, 84, 71, 84], lambda x: x + 3, 2, 2, 3), ] for x in range(20): for monkey in monkeys: monkey.inspect(monkeys) inspections = [m.inspections for m in monkeys] inspections.sort() print(inspections[-1] * inspections[-2]) </code></pre> <p>Using this approach for part two doesnt work - the numbers get too big.</p> <h1 id="a-hrefhttpsadventofcodecom2022day1212a"><a href="https://adventofcode.com/2022/day/12">12</a></h1> <p>Looks interesting, it's essentially a maze solving algorithm.</p> <p>The algorithm would be something like:</p> <ul> <li>Let &quot;Set&quot; be a class containing a coordinate and a direction.</li> <li>Let there be a list of Setps.</li> <li>The directions are, in sequence, up, right, down and left.</li> <li>You can't go further if you hit an edge, hit a space too high, or a space in the list of steps.</li> <li>While you can continue to move up: <ul> <li>Add the next step up to the list of steps</li> </ul> </li> <li>Go the next direction, then continue the above step</li> <li>If you can't move, remove one Step from the list of Steps, then continue testing the directions.</li> <li>Continue until the end is reached.</li> </ul> <p>After working on this for a while, it was taking a really long time to run, so I added the places visited but rejected to another list, and didn't visit them again. There was another problem: it can't account for a shorter way between two visited points. Instead, work from the end to the start, and for each point, store the minimum steps required to reach it from the end. Where a new minimum is set, re-evaluate all paths from that point, but there's no point visiting any spaces with a lower score. Thinking about these rules, instead of keeping a list of steps visited, when setting a new minimum, add that space to a list of spaces to be evaluated, and continue until this list is empty.</p> <pre><code>import sys movements = [ lambda pos: (pos[0], pos[1] - 1), # up lambda pos: (pos[0] + 1, pos[1]), # right lambda pos: (pos[0], pos[1] + 1), # down lambda pos: (pos[0] - 1, pos[1]), # left ] class Terrain: def __init__(self, data): self.data = data self.width = len(self.data[0]) self.height = len(self.data) self.size = self.width * self.height def __getitem__(self, pos): return self.data[pos[1]][pos[0]] def __setitem__(self, pos, val): self.data[pos[1]][pos[0]] = val terrain = Terrain([ [(1000 if c == 'S' else 26 if c == 'E' else ord(c) - ord('a')) for c in line[:-1]] for line in sys.stdin ]) scores = Terrain([ [None] * terrain.width for i in range(terrain.height) ]) # I can't be bothered extracting these from the input start = (0, 20) end = (137, 20) scores[end] = 0 to_evaluate = [end] while len(to_evaluate): curr = to_evaluate.pop() score = scores[curr] for movement in movements: next = movement(curr) if next[0] &lt; 0 or next[0] &gt;= terrain.width \ or next[1] &lt; 0 or next[1] &gt;= terrain.height: continue nextScore = scores[next] if terrain[curr] &gt; terrain[next] + 1: # The step is too high continue if nextScore == None or nextScore &gt; score + 1: scores[next] = score + 1 to_evaluate.append(next) print(scores[start]) </code></pre> <p>For part two, as this loop runs, keep track of the lowest score where the elevation is the lowest.</p> <h1 id="a-hrefhttpsadventofcodecom2022day1616a"><a href="https://adventofcode.com/2022/day/16">16</a></h1> <p>This feels similar to day 12. Let class Valve have a flow rate, and a list of tunnels. Walk the graph as in the map in day 12 until the 30 minutes have passed, then backtrack and continue.</p> <h1 id="a-hrefhttpsadventofcodecom2022day1717a"><a href="https://adventofcode.com/2022/day/17">17</a></h1> <p>This one took way longer than it should have, because the unit test for <code>hits</code> was inadequate!</p> <pre><code>import sys import itertools import unittest class Rock: def __init__(self, data): self.data = data self.width = max(len(d) for d in data) self.height = len(data) rocks = itertools.cycle([ # data is upside down Rock([[True, True, True, True]]), Rock([[False, True, False], [True, True, True], [False, True, False]]), Rock([[True, True, True], [False, False, True], [False, False, True]]), Rock([[True], [True], [True], [True]]), Rock([[True, True], [True, True]]) ]) class Chamber: def __init__(self, rows = [], width = 7): # The chamber, where the bottom is index 0, the next is 1 etc self.rows = rows self.width = width # ypos is the height where rock[0] appears def hits(self, rock, xpos, ypos): if xpos &lt; 0 or xpos &gt; self.width - rock.width: return True for y in range(min(len(self.rows) - ypos, rock.height)): if next((True for a, b in zip(self.rows[y + ypos][xpos:], rock.data[y]) if a &amp; b), False): return True return False def place(self, rock, xpos, ypos): self.rows += [[False] * self.width for n in range(self.height, ypos + rock.height)] for y, data in enumerate(rock.data): for x, val in enumerate(data): row = self.rows[y + ypos] row[xpos + x] = row[xpos + x] or val @property def height(self): return len(self.rows) def dump(self): for n in range(len(self.rows) - 1, -1, -1): print(''.join('#' if x else '.' for x in self.rows[n])) print() class ChamberTestCase(unittest.TestCase): def test_hits(self): chamber = Chamber([[False, True, True, False]]) rock = Rock([[True, False], [True, False]]) self.assertTrue(chamber.hits(rock, 2, 0)) self.assertFalse(chamber.hits(rock, 0, 0)) # the rock is entirely outside the chamber self.assertFalse(chamber.hits(rock, 1, 1)) class ChamberPlaceTestCase(unittest.TestCase): def setUp(self): self.chamber = Chamber([ [True, False, True, False], [True, False, False, False] ], width = 4) def test_place_over_existing(self): self.chamber.place(Rock([[True, False], [False, True]]), 2, 0) self.assertEqual(self.chamber.rows, [ [True, False, True, False], [True, False, False, True] ]) def test_place_over_new_row(self): self.chamber.place(Rock([[True, False], [False, True]]), 0, 1) self.assertEqual(self.chamber.rows, [ [True, False, True, False], [True, False, False, False], [False, True, False, False] ]) unittest.main(exit=False) jets = itertools.cycle([ 1 if c == '&gt;' else -1 for c in sys.stdin.read() if c == '&lt;' or c == '&gt;' ]) chamber = Chamber() for n in range(2022): rock = next(rocks) x = 2 y = chamber.height + 3 while True: newx = x + next(jets) x = x if chamber.hits(rock, newx, y) else newx if y == 0 or chamber.hits(rock, x, y - 1): break y = y - 1 chamber.place(rock, x, y) print(len(chamber.rows)) </code></pre> Sat, 31 Dec 2022 00:00:00 +0000 http://yourdomain.com/posts/2022/12/31/2022-advent-of-code/ http://yourdomain.com/posts/2022/12/31/2022-advent-of-code/ Cyber FastTrack 2020 Forensics RH01 challenge <p>This is the RH01 challenge from Cyber FastTrack Spring 2020.</p> <blockquote> <p>We received this file, our analysts believe it is too random to be solved. Can you do anything with this?</p> </blockquote> <p>the target file:</p> <pre><code>2b991035290d99b2fbf37fb54320ad6ce84b136d rh01.zip </code></pre> <p>The program asks for three numbers, then produces a message that the numbers were wrong.</p> <p>I haven't done much reverse engineering before, but knew about Ghidra, and blundered by way into opening the binary in that, searching for the last message, then looking at the routine which produces it. When you look at a routine in Ghidra, it shows a psuedo-C implementation of it, so I could see what was going on:</p> <pre><code> FUN_000108a1(&quot;I\'m thinking of three random numbers, guess which three:&quot;); local_44 = 0; while (local_44 &lt; 3) { printf(&quot;\nnumber %d:&quot;,local_44 + 1); __isoc99_scanf(&amp;DAT_00010c21,local_20 + local_44 * 4); local_44 = local_44 + 1; } uVar2 = memcmp(&amp;local_2c,local_20,0xc); iVar3 = memcmp(&amp;local_2c,local_20,0xc); if (iVar3 != 0) { printf(&quot;\nI was actually thinking of %d, %d and %d.\n&quot;,local_2c,local_28,local_24); /* WARNING: Subroutine does not return */ exit(0); } FUN_0001070d(uVar2,uVar2); uVar2 = 0; if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) { uVar2 = FUN_00010b40(); } return uVar2; </code></pre> <p>What I think is going on:</p> <ul> <li>Three numbers are typed in, they're stored as 4 bytes each after <code>local_20</code>. (It looks like the number is some local variable offset, perhaps relative to the stack pointer when the function is running.)</li> <li>12 bytes are compared - those from the numbers typed in, to another 12 bytes where the first 4 is a number produced by <code>rand()</code>, but I don't know what's in the other 8 bytes.</li> <li>The result of memcmp - which should be 0 if the numbers are correct - are passed to another function.</li> </ul> <p>A good start is to simply replace the branch with one which doesn't exit. When I put the cursor on the comparison, I see a <code>JNZ</code> (jump if not zero) instruction. I used a hex editor to replace it (<code>75 24</code>) with a JZ (jump if zero) instruction (<code>74 24</code>).</p> <p>This seems to have worked, but now displays some garbage after typing the numbers in. I suspect the flag is unscrambled using some of the numbers, by that last function. But if the number comparison succeeds, the <code>memcmp</code>s will return 0, which are passed to that function. How can I change this?</p> <p>The function call looks like:</p> <pre><code> 00010a73 ff 75 d4 PUSH dword ptr [EBP + local_34] 00010a76 ff 75 d8 PUSH dword ptr [EBP + local_30] 00010a79 e8 8f fc CALL FUN_0001070d </code></pre> <p>Two local numbers are pushed on to the stack, then the function is called. Perhaps I can put zeroes on the stack instead.</p> <p>First I need the instructions for this. Can <code>nasm</code> easily do this, or does it require sections and other stuff I don't know much about? Let's see what happens:</p> <pre><code>$ echo 'push 0' &gt; zero.asm $ nasm -o zero zero.asm $ hd zero 00000000 6a 00 |j.| </code></pre> <p>If I look at <a href="http://sparksandflames.com/files/x86InstructionChart.html">a x86 opcode table</a>, 6a is indeed <code>PUSH</code>.</p> <p>There original pushes were 6 bytes, now there are 4; something needs to fill the last two bytes. NOPs will do, which is the value 90.</p> <p>So I'll replace:</p> <pre><code>ff 75 d4 ff 75 d8 </code></pre> <p>with</p> <pre><code>6a 00 6a 00 90 90 </code></pre> <p>and see what happens:</p> <pre><code>$ ./RH01 I'm thinking of three random numbers, guess which three: number 1:1 number 2:1 number 3:1 Wait, how did you do that? I thought I was totally random... Flag: Sow_The_Seeds_Of_Doubt </code></pre> <p>Not bad for a first effort!</p> Sat, 28 Mar 2020 00:00:00 +0000 http://yourdomain.com/posts/2020/03/28/cyber-fasttrack-spring-2020-rh01/ http://yourdomain.com/posts/2020/03/28/cyber-fasttrack-spring-2020-rh01/ Cyber FastTrack 2020 Forensics HF05 challenge <p>This is challenge HF05 from the Cyber FastTrack Spring 2020 challenges.</p> <p>The challenge:</p> <blockquote> <p>The attacker created a shared folder on the victims machine. Find this folder and give us the absolute path of the directory, including drive letter.</p> <p>files.allyourbases.co/fi02.zip</p> </blockquote> <p>These are the files:</p> <pre><code>$ sha1sum fi02.zip memory-image.vmem 78c544a8e5cbb9764fd009760a7e4e3ae035db6a fi02.zip 7ea854fc529c7517dedbdf0c287a3c4e2a7f3903 memory-image.vmem </code></pre> <p>Volatility is a memory forensics tool which apparently comes with Kali - I've never used any tools like this before, so that will do for startes.</p> <p>It turns out it <em>doesn't</em> come with Kali. To get volatility working in Kali, I found it easiest to download it from the web site, extract it, and create an alias to it:</p> <pre><code>$ cd /tmp $ wget 'http://downloads.volatilityfoundation.org/releases/2.6/volatility_2.6_lin64_standalone.zip' $ unzip volatility_2.6_lin64_standalone.zip $ cd volatility_2.6_lin64_standalone/ $ alias vol=/tmp/volatility_2.6_lin64_standalone/volatility_2.6_lin64_standalone </code></pre> <p>Start by identifying the image:</p> <pre><code>$ vol imageinfo -f memory-image.vmem Volatility Foundation Volatility Framework 2.6 INFO : volatility.debug : Determining profile based on KDBG search... Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_23418 </code></pre> <p>I'll guess <code>Win7SP1x64</code> for starters. See whether this works:</p> <pre><code>$ vol --profile=Win7SP1x64 pslist -f memory-image.vmem Volatility Foundation Volatility Framework 2.6 Offset(V) Name PID PPID Thds Hnds Sess Wow64 Start Exit ------------------ -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------ 0xfffffa8000ca1890 System 4 0 89 480 ------ 0 2019-09-05 14:39:08 UTC+0000 0xfffffa8001a5b440 smss.exe 268 4 2 29 ------ 0 2019-09-05 14:39:08 UTC+0000 0xfffffa8002cadb30 csrss.exe 368 344 8 402 0 0 2019-09-05 14:39:23 UTC+0000 0xfffffa8002d34b30 wininit.exe 420 344 3 74 0 0 2019-09-05 14:39:23 UTC+0000 </code></pre> <p>Typing the --profile and -f is going to get annoying. These can go in environment files instead:</p> <pre><code>$ export VOLATILITY_PROFILE=Win7SP1x64 $ export VOLATILITY_LOCATION=file:///tmp/memory-image.vmem $ vol pslist </code></pre> <p>The last command shows that the environment variables are working.</p> <p>How can I find the share? Maybe the attacker used a console?</p> <pre><code>$ vol consoles </code></pre> <p>There's some things like IP addresses there, and commands which I guess disable the firewall:</p> <pre><code>c:\Users\Redacted\Desktop\IT Support Software&gt;NetSh Advfirewall set allprofiles state off Ok. </code></pre> <p>and the actual version of Windows (<a href="https://www.gaijin.at/en/infos/windows-version-numbers">a quick search</a> tells us it's Windows 7 service pack 1, so my guess might have been correct):</p> <pre><code>Microsoft Windows [Version 6.1.7601] Copyright (c) 2009 Microsoft Corporation. All rights reserved. </code></pre> <p>but other than that, nothing stands out.</p> <p>I guess Windows would keep its file shares in the registry. I used a web search to find out where these are, and tried to find that key:</p> <pre><code>$ vol printkey -K 'System\CurrentcontrolSet\Services\Lanmanserver\Shares' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile The requested key could not be found in the hive(s) searched </code></pre> <p>That's no good... does that command work at all? I'll try something from the manual:</p> <pre><code>$ vol printkey -K 'Microsoft\Security Center\Svc' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile ---------------------------- Registry: \SystemRoot\System32\Config\SOFTWARE Key name: Svc (S) Last updated: 2019-09-05 14:41:28 UTC+0000 Subkeys: (V) Vol Values: REG_QWORD VistaSp1 : (S) 128920218544262440 REG_DWORD AntiVirusOverride : (S) 0 </code></pre> <p>That seems to work. I notice that there's some <code>CurrentControlSet</code> stuff in the share query. I have a feeling that Windows somehow maps this somewhere else in the registry, <a href="https://renenyffenegger.ch/notes/Windows/registry/tree/HKEY_LOCAL_MACHINE/System/CurrentControlSet/index">which is correct</a>. Searching for <code>SYSTEM\ControlSet001</code> found nothing, but on a hunch I tried just <code>ControlSet001</code>:</p> <pre><code>$ vol printkey -K 'ControlSet001' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile ---------------------------- Registry: \REGISTRY\MACHINE\SYSTEM Key name: ControlSet001 (S) Last updated: 2019-09-05 21:58:26 UTC+0000 Subkeys: (S) Control (S) Enum (S) Hardware Profiles (S) Policies (S) services Values: </code></pre> <p>That's looking more interesting! Let's see whether that helps:</p> <pre><code>$ vol printkey -K 'Services\LanmanServer\Shares' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile The requested key could not be found in the hive(s) searched </code></pre> <p>maybe just the end will do?</p> <pre><code>$ vol printkey -K 'Shares' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile The requested key could not be found in the hive(s) searched </code></pre> <p>I also noticed what <code>CurrentControlSet</code> does:</p> <pre><code>$ vol printkey -K 'CurrentControlSet' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile ---------------------------- Registry: \REGISTRY\MACHINE\SYSTEM Key name: CurrentControlSet (V) Last updated: 2019-09-05 14:39:01 UTC+0000 Subkeys: Values: REG_LINK SymbolicLinkValue : (V) \Registry\Machine\System\ControlSet001 </code></pre> <p>I should have a closer look at the output: ControlSet001 lists a subkey of &quot;services&quot;.</p> <pre><code>$ vol printkey -K 'ControlSet001\services' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile ---------------------------- Registry: \REGISTRY\MACHINE\SYSTEM Key name: services (S) Last updated: 2019-09-05 14:17:34 UTC+0000 Subkeys: (S) .NET CLR Data (S) .NET CLR Networking ... (S) KtmRm (S) LanmanServer (S) LanmanWorkstation (S) ldap ... </code></pre> <p>I'll keep digging:</p> <pre><code>$ vol printkey -K 'ControlSet001\services\LanmanServer' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile ---------------------------- Registry: \REGISTRY\MACHINE\SYSTEM Key name: LanmanServer (S) Last updated: 2009-07-14 04:53:33 UTC+0000 Subkeys: (S) Aliases (S) AutotunedParameters (S) DefaultSecurity (S) Linkage (S) Parameters (S) ShareProviders (S) Shares Values: REG_SZ DisplayName : (S) @%systemroot%\system32\srvsvc.dll,-100 REG_EXPAND_SZ ImagePath : (S) %SystemRoot%\system32\svchost.exe -k netsvcs REG_SZ Description : (S) @%systemroot%\system32\srvsvc.dll,-101 REG_SZ ObjectName : (S) LocalSystem REG_DWORD ErrorControl : (S) 1 REG_DWORD Start : (S) 2 REG_DWORD Type : (S) 32 REG_MULTI_SZ DependOnService : (S) ['SamSS', 'Srv', '', ''] REG_DWORD ServiceSidType : (S) 1 REG_MULTI_SZ RequiredPrivileges : (S) ['SeChangeNotifyPrivilege', 'SeImpersonatePrivilege', 'SeAuditPrivilege', 'SeLoadDriverPrivilege', '', ''] REG_BINARY FailureActions : (S) 0x00000000 80 51 01 00 00 00 00 00 00 00 00 00 03 00 00 00 .Q.............. 0x00000010 14 00 00 00 01 00 00 00 60 ea 00 00 01 00 00 00 ........`....... 0x00000020 c0 d4 01 00 00 00 00 00 00 00 00 00 ............ </code></pre><pre><code>$ vol printkey -K 'ControlSet001\services\LanmanServer\Shares' Volatility Foundation Volatility Framework 2.6 Legend: (S) = Stable (V) = Volatile ---------------------------- Registry: \REGISTRY\MACHINE\SYSTEM Key name: Shares (S) Last updated: 2019-09-05 15:02:37 UTC+0000 Subkeys: (S) Security Values: REG_MULTI_SZ exfil : (S) ['CSCFlags=0', 'MaxUses=4294967295', 'Path=c:\\recyc1e_bin', 'Permissions=63', 'ShareName=exfil', 'Type=0', '', ''] </code></pre> <p><code>c:\recycle_bin</code> is correct! I'm happy with about half an hour for a beginner, and I didn't hit any dead ends.</p> Fri, 27 Mar 2020 00:00:00 +0000 http://yourdomain.com/posts/2020/03/27/cyber-fasttrack-spring-2020-fh05/ http://yourdomain.com/posts/2020/03/27/cyber-fasttrack-spring-2020-fh05/ Cyber FastTrack 2020 Forensics FE01 challenge <p>This is a challenge from Cyber FastTrack Spring 2020, using the same image as FH05.</p> <blockquote> <p>Take a look at the memory image provided and see if you can see what was written on Notepad while it was open on the user's screen.</p> </blockquote> <p>A good place to start might be to look at the memory for notepad?</p> <pre><code>$ vol pslist Volatility Foundation Volatility Framework 2.6 Offset(V) Name PID PPID Thds Hnds Sess Wow64 Start Exit ------------------ -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------ ... 0xfffffa8002642610 notepad.exe 2740 612 1 57 1 0 2019-09-05 15:33:20 UTC+0000 </code></pre> <p>I'll start by dumping the memory:</p> <pre><code>$ mkdir dump vol procdump -D dump/ -p 2740 Volatility Foundation Volatility Framework 2.6 Process(V) ImageBase Name Result ------------------ ------------------ -------------------- ------ 0xfffffa8002642610 0x00000000ff410000 notepad.exe OK: executable.2740.exe $ xxd dump/executable.2740.exe |less $ ls -al dump/executable.2740.exe -rw-r--r-- 1 kali kali 193536 Mar 26 16:48 dump/executable.2740.exe </code></pre> <p>Not too big. Normally I'd use <code>strings</code> on something like this, but Windows has a habit of using UTF-16 to store text, so I thought this command won't help - but the <code>-el</code> option does just that! It didn't show anything interesting though.</p> <p>There's a screenshot command! That would be too easy if it worked...</p> <pre><code>$ mkdir shots $ vol screenshot -D shots Volatility Foundation Volatility Framework 2.6 Wrote shots/session_0.Service-0x0-3e4$.Default.png Wrote shots/session_0.Service-0x0-3e5$.Default.png </code></pre> <p>No luck, but there is one image which shows where notepad and cmd.exe is on the display.</p> <p>There's a <code>wintree</code> command, which shows the GUI components:</p> <pre><code>$ vol wintree ... Untitled - Notepad (visible) notepad.exe:2740 Notepad ..#50188 notepad.exe:2740 6.0.7601.17514!msctls_statusbar32 ..#501ca (visible) notepad.exe:2740 6.0.7601.17514!Edit .Default IME notepad.exe:2740 IME .MSCTFIME UI notepad.exe:2740 MSCTFIME UI </code></pre> <p>Maybe &quot;edit&quot; controls are what's used to enter text?</p> <p>I spent a while trying to get the contents of the controls, then I wondered whether there was some memory not being dumped earlier, but no luck after an hour or so.</p> <p>I looked through the list of commands (in the README, not the wiki) and noticed the <code>editbox</code> command:</p> <pre><code> vol editbox Volatility Foundation Volatility Framework 2.6 ****************************** Wnd Context : 1\WinSta0\Default Process ID : 2740 ImageFileName : notepad.exe IsWow64 : No atom_class : 6.0.7601.17514!Edit value-of WndExtra : 0x350490 nChars : 33 selStart : 33 selEnd : 33 isPwdControl : False undoPos : 31 undoLen : 3 address-of undoBuf: 0x354740 undoBuf : qay ------------------------- flag:noting_notes_in_a_noting_way </code></pre> <p>That's a bit annoying. I would like to know how to get this using more generic commands, but I don't know anything about Windows user interfaces and there would be plenty to learn there first. This does make sense that an older application like notepad would use the control itself for storing its data, so it wouldn't appear in the memory space.</p> Fri, 27 Mar 2020 00:00:00 +0000 http://yourdomain.com/posts/2020/03/27/cyber-fasttrack-spring-2020-fe01/ http://yourdomain.com/posts/2020/03/27/cyber-fasttrack-spring-2020-fe01/ Serial LIRC devices on recent Ubuntu releases <p>I tried to get a &quot;homebrew&quot; infrared receiver attached to the DCD line of a serial port working on Ubuntu Bionic. It seems that things have changed since Ubuntu 12.04, when I last had it working.</p> <p>The changes ended up being:</p> <ul> <li>The kernel modules are in the mainline kernel, but aren't supplied with Ubuntu</li> <li>The LIRC driver's name has changed from &quot;serial&quot; to &quot;default&quot;</li> </ul> <p>My first problem is that the receiver wasn't working in the first place. I attached a DSO Nano to the data line, and noticed that the signal didn't have a high enough voltage to trigger the serial port. The <a href="https://www.mouser.com/ds/2/348/rpm6900-313874.pdf">data sheet</a> shows the receiver's output being a pull-up resistor with a transistor pulling the output low; maybe this serial port draws a particularly large amount of current. I wired a 2.2k resistor between the data line and VCC (which should be within the limits of the receiver), and everything works.</p> <p>I tested it with this program, which displays a time when the DCD line changes:</p> <pre><code>#include &lt;stdio.h&gt; #include &lt;unistd.h&gt; #include &lt;sys/types.h&gt; #include &lt;sys/stat.h&gt; #include &lt;fcntl.h&gt; #include &lt;sys/ioctl.h&gt; #include &lt;termios.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;errno.h&gt; #include &lt;time.h&gt; int die(const char* msg) { perror(msg); printf(&quot;%s\n&quot;, strerror(errno)); exit(1); } int main(void) { int fd = open(&quot;/dev/ttyS0&quot;, 0); if (fd == -1) die(&quot;open&quot;); while (1) { struct timespec tm; ioctl(fd, TIOCMIWAIT, TIOCM_CD | TIOCM_RNG | TIOCM_DSR | TIOCM_CTS); if (clock_gettime(CLOCK_MONOTONIC, &amp;tm) == -1) die(&quot;clock_gettime&quot;); printf(&quot;%ld\n&quot;, tm.tv_nsec); } close(fd); return 0; } </code></pre> <p>(So why does LIRC need the kernel driver, if this works in userspace? I suspect it's because the LIRC API requires a timeout, which the <code>ioctl</code> doesn't support.)</p> <p>LIRC's serial port support works by using a kernel module to read the hardware, and sends the output to <code>/dev/lirc0</code>. LIRC connects to this device to read the input. The LIRC userspace driver that does this is called &quot;default&quot; (it used to be called &quot;serial&quot;).</p> <p>It seems a few releases ago the drivers were upstreamed, and aren't supplied with Ubuntu. I got the driver installed with DKMS:</p> <ol> <li> <p>Create a working directory</p> </li> <li> <p>Put the <a href="https://elixir.bootlin.com/linux/v4.15.18/source/drivers/media/rc/serial_ir.c">driver</a> in it. (Choose the appropriate version for your kernel.)</p> </li> <li> <p>Add this Makefile:</p> <pre><code> obj-m += serial_ir.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean </code></pre></li> <li> <p>Add this <code>dkms.conf</code>:</p> <pre><code> PACKAGE_NAME=&quot;lirc_serial_ir&quot; PACKAGE_VERSION=&quot;4.15&quot; CLEAN=&quot;rm -f *.*o&quot; BUILT_MODULE_NAME[0]=&quot;serial_ir&quot; DEST_MODULE_LOCATION[0]=&quot;/updates&quot; AUTOINSTALL=&quot;no&quot; </code></pre></li> <li> <p>Run <code>sudo dkms add .</code></p> </li> <li> <p>Run <code>sudo dkms install lirc_serial_ir/4.15</code>.</p> </li> </ol> <p>Now run <code>sudo modprobe lirc_serial</code>. Run <code>dmesg</code>, and you should see:</p> <pre><code>[ 1627.908509] serial_ir serial_ir.0: port 03f8 already in use [ 1627.908515] serial_ir serial_ir.0: use 'setserial /dev/ttySX uart none' [ 1627.908516] serial_ir serial_ir.0: or compile the serial port driver as module and [ 1627.908517] serial_ir serial_ir.0: make sure this module is loaded first [ 1627.908532] serial_ir: probe of serial_ir.0 failed with error -16 </code></pre> <p>To fix this, install the &quot;setserial&quot; package, and <a href="http://www.lirc.org/html/configuration-guide.html#serial_port_reservation">disable the serial port as the instructions say</a>.</p> <p>Try running mode2, and press some buttons on the remote:</p> <pre><code>$ sudo mode2 --driver default Using driver default on device auto Trying device: /dev/lirc0 Using device: /dev/lirc0 Running as regular user space 504574 pulse 9015 space 4548 pulse 488 </code></pre> Wed, 28 Nov 2018 00:00:00 +0000 http://yourdomain.com/posts/2018/11/28/lirc-ubuntu-bionic/ http://yourdomain.com/posts/2018/11/28/lirc-ubuntu-bionic/ Sub 50 cent microcontrollers <p><a href="https://jaycarlson.net/microcontrollers/">$1 microcontrollers</a>, pfft. What useful ones are around for under 50 cents?</p> <p>The qualifications:</p> <ul> <li>There needs to be adequate documentation</li> <li>Programmamble with readly available (and cheap) hardware. This rules out a lot of ones from Megawin, Sinowealth and so on; while they have reasonable user manuals, there's no information on how to program them, short of buying a $20 programmer.</li> <li>Are readily available. I ruled out parts only available from Taobao, for instance.</li> </ul> <p>I was left with these:</p> <ul> <li>The STM8S103. They're not <em>the</em> cheapest, but are readily available in the West. There's a cheaper STM8S003, but its flash is rated to only 100 writes, so it sounds like the idea is to develop for the S103 first.</li> <li>The Nuvoton N76E003. It has loads of peripherals, and is pin compatible with the STM8S103.</li> <li>The STC microcontrollers. Not quite as much bang for buck as the Nuvoton, and unavailable in the West, but come in 8 pin packages.</li> </ul> <p>The Atmel ATtiny13 also qualifies, but I already know how to program those!</p> <p>All should be programmable using <a href="http://sdcc.sourceforge.net/">SDCC</a>, and either a ST-Link or USB-Serial dongle. I've purchased development boards for the <a href="https://www.aliexpress.com/item/STM8S103F3P6-System-Board-STM8S-STM8-Development-Board-Minimum-Core-Board/32885918852.html">STM8</a>, <a href="https://www.aliexpress.com/item/51-Development-Board-N76E003AT20-Development-Board-System-Board-Core-Board-N76E003/32898770085.html">Nuvoton</a> and a <a href="https://www.aliexpress.com/item/STC15W204S-SCM-Minimum-System-Board-Development-Board-51-SOP8-STC15F104E/32899351974.html">STC15W204</a>. I hope to try these out and write about them.</p> Wed, 24 Oct 2018 00:00:00 +0000 http://yourdomain.com/posts/2018/10/24/sub-50c-microcontrollers/ http://yourdomain.com/posts/2018/10/24/sub-50c-microcontrollers/ Schematic for the "Bustodephon" brushed motor electronic speed controller (ESC) <p>I tried drawing the schematic for the common &quot;Bustophedon&quot; electronic speed controllers (ESC) for brushed motors.</p> <div class='content-image'> <a href='/images/bustodephonschematic.png'> <img src="/generated/400-bustodephonschematic.png" alt="" srcset=" /generated/400-bustodephonschematic.png 400w, /images/bustodephonschematic.png 1028w" sizes="400px"> </a> </div> <p>The circuit is fairly straightforward to follow. The zener diode based regulator for the receiver power is a bit surprising - I would have thought another regulator would have been cheap enough instead of several components. I'm guessing the separate supply is to shield the microcontroller from the receiver, especially since it would be easy to supply power from another ESC.</p> <p>The microcontroller has no markings, but there <a href="https://www.lcsc.com/product-detail/_PMS153_C129129.html">are</a> <a href="https://www.lcsc.com/product-detail/_SN8P2501D-SOP-14_C80639.html">several</a> very cheap microcontrollers which have a suitable pinout.</p> <p>I'll have to analyze the microcontroller's output to see whether it does anything special.</p> Thu, 13 Sep 2018 00:00:00 +0000 http://yourdomain.com/posts/2018/09/13/brushed-bustodephon-esc-schematic/ http://yourdomain.com/posts/2018/09/13/brushed-bustodephon-esc-schematic/ The FlySky iBus protocol <p>This is the FlySky iBus protocol that I've gleaned from <a href="https://basejunction.wordpress.com/2015/08/23/en-flysky-i6-14-channels-part1/">a blog post</a> and a <a href="https://github.com/aanon4/FlySkyIBus">single library</a>.</p> <p>Data is transmitted as serial UART data, 115200bps, 8N1. A message is sent every 7 milliseconds. <a href="https://www.banggood.com/818CH-Mini-Receiver-With-PPM-iBus-SBUS-Output-for-Flysky-i6-i6x-AFHDS-2A-Transmitter-p-1183313.html">My receiver</a> sends this over its white wire, and stops sending a few tenths of a second after the transmitter is switched off (unlike the PPM signal on the yellow wire, which keeps sending its last value).</p> <p>The first byte is 0x20, the second is 0x40.</p> <p>Next are 14 pairs of bytes, which is the channel value in little endian byte order. The FS-i6 is a 6 channel receiver, so it fills in the first 6 values. The remainder are set to 0x05DC. My transmitter sends values between 0x3E8 and 0x7D0.</p> <p>Finally a 2 byte checksum is sent. It's in little endian byte order, it starts at 0xFFFF, from which every byte's value is subtracted except for the checksum.</p> <p>I've written <a href="https://github.com/33d/ibus-library">a library</a> to decode this data. An Arduino could measure the time of the message start to improve detection of the message start. One problem using an Arduino is that the board will interfere with programming - a resistor (maybe 10k) on the data line should help.</p> Sun, 22 Oct 2017 00:00:00 +0000 http://yourdomain.com/posts/2017/10/22/flysky-ibus-protocol/ http://yourdomain.com/posts/2017/10/22/flysky-ibus-protocol/ Build a Jekyll blog with Travis CI without granting write access <p>With the demise of Openshift Online and its free tier, I was looking for somewhere else to host my blog. Even though it's a <a href="https://jekyllrb.com/">Jekyll</a> blog, it does some image resizing, so I can't use Github's native builder. I should be able to use <a href="https://travis-ci.org/">Travis CI</a> though, then push them to another Github repository for <a href="https://help.github.com/categories/github-pages-basics/">Pages</a> hosting.</p> <p>The <a href="https://docs.travis-ci.com/user/deployment/pages/">Travis instructions for Github Pages</a> instructions suggest you use Github personal access tokens for authentication, but that seems to give write access to all of my Github repositories - which I don't want to do.</p> <p>You will need the <a href="https://github.com/travis-ci/travis.rb">command line client</a> installed and logged in.</p> <ol> <li> <p>Generate a key pair for Travis to use when pushing</p> <pre><code> ssh-keygen -t rsa -b 4096 -f travis-key </code></pre> <p>Don't commit the generated files!</p> </li> <li> <p>Create a repository on Github for the generated site to be pushed to. In that repository, go to Settings, Deploy keys, then Add deploy key. Copy in <code>travis-key.pub</code> which you generated in the previous step, check &quot;Enable write access&quot;, then add the key.</p> </li> <li> <p>The private key should be <a href="https://docs.travis-ci.com/user/encrypting-files/">encrypted</a>. From the blog repository:</p> <pre><code> travis encrypt-file -r 33d/blog travis-key </code></pre> <p>where <code>travis-key</code> is one of the files created earlier by <code>ssh-keygen</code>.</p> <p>This command will suggest you add a line to your <code>before_install</code> section - do that.</p> </li> <li> <p>Put at the end of <code>.travis.yml</code>'s <code>before_install</code>, to stop <code>ssh-add</code> complaining about the key's permissions:</p> <pre><code> - chmod 400 ../travis-key </code></pre></li> <li> <p>Add this to <code>.travis.yml</code>, to perform the Git push:</p> <pre><code>after_success: - eval &quot;$(ssh-agent -s)&quot; - ssh-add ../travis-key - git clone git@github.com:33d/blog-pages.git target - cp -pr _site/* target - git -C target checkout -b gh-pages - git -C target add . - git -C target commit -m &quot;$( date --utc --iso-8601=seconds )&quot; - git -C target push --force origin gh-pages </code></pre> <p>I use a different repository for this, because I don't want the build artifacts clogging the blog repository.</p> </li> </ol> <p>You can see <a href="https://github.com/33d/blog/blob/blog/.travis.yml">my completed <code>.travis.yml</code> file</a>.</p> Sun, 08 Oct 2017 00:00:00 +0000 http://yourdomain.com/posts/2017/10/08/jekyll-blog-travis-ci/ http://yourdomain.com/posts/2017/10/08/jekyll-blog-travis-ci/ Check that your flash memory isn't fake <p>Here's how I've checked that my flash memory (USB stick, SD card etc) isn't fake. The idea is to use a cipher to produce a psuedorandom stream, write its output to the flash memory, then genenrate the stream again comparing it to the card.</p> <p>First, find out how big the flash is:</p> <pre><code>$ cat /proc/paritions major minor #blocks name ... 8 16 30736384 sdb </code></pre> <p>Some quick maths tells me the block size is 1024 bytes. Next, use <code>openssl</code> to encrypt some zeroes, writing the output to the card:</p> <pre><code>$ dd if=/dev/zero bs=1024 count=30736384 | openssl enc -aes128 -k some-passworrd -nosalt | sudo tee /dev/sdb &gt; /dev/null 30736384+0 records in 30736384+0 records out 31474057216 bytes (31 GB) copied, 989.076 s, 31.8 MB/s tee: /dev/sdb: No space left on device </code></pre> <p>I don't know why it complains about no space, but the number of bytes written looks correct.</p> <p>(Change some-password to some other word.) <code>-nosalt</code> prevents openssl writing a header, which contains the password salt. Because our encryption key (the <code>some-password</code> above) doesn't need to be secure, we don't care about it, and will stop the same stream being output next time.</p> <p>Now check the result:</p> <pre><code>$ dd if=/dev/zero bs=1024 count=30736384 | openssl enc -aes128 -k some-password -nosalt | sudo cmp - /dev/sdb 30736384+0 records in 30736384+0 records out 31474057216 bytes (31 GB) copied, 367.183 s, 85.7 MB/s cmp: EOF on /dev/sdb </code></pre> <p>If it hits the end without complaining about differences, what's on the card is what was produced again by the cipher stream, so the card contains the advertised flash chip.</p> Sat, 11 Jul 2015 00:00:00 +0000 http://yourdomain.com/posts/2015/07/11/check-for-fake-flash/ http://yourdomain.com/posts/2015/07/11/check-for-fake-flash/