WARNING: This tutorial uses at&t syntax. The operands are backwards from intel. Remember all those C crashmes? You could have stolen all the flags. In some like watchman you could have done a 'strings' to steal the passcode because it was not encrypted. For example: student@csci4971:~/class/C/lab2/files$ strings watchman | grep Congrat -B 1 44911308 Congratulations! The flag is [%s] student@csci4971:~/class/C/lab2/files$ ./watchman 44911308 Congratulations! The flag is [3072cbf8c7037f284c9a0f88b615ae25] ==== Whooh Thats cool. But the flag is nowhere to be found. How would you go about extracting that process? A tutorial follows =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- student@csci4971:~/class/C/lab2/files$ file endiantest endiantest: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.15, stripped -> Statically linked binary does not use shared object. a lot of extra libc code gets linked in. -> Stripped symbols removed. hard to analyze gdb -q endiantest (gdb) i file Symbols from "/home/student/class/C/lab2/files/endiantest". Local exec file: `/home/student/class/C/lab2/files/endiantest', file type elf32-i386. Entry point: 0x8048170 ... (gdb) x/20i 0x8048170 0x8048170: xor %ebp,%ebp 0x8048172: pop %esi 0x8048173: mov %esp,%ecx 0x8048175: and $0xfffffff0,%esp 0x8048178: push %eax 0x8048179: push %esp 0x804817a: push %edx 0x804817b: push $0x8048d50 <-------- ctors 0x8048180: push $0x8048d90 <-------- dtors 0x8048185: push %ecx 0x8048186: push %esi 0x8048187: push $0x8048676 <-------- main is here 0x804818c: call 0x8048720 0x8048191: hlt 0x8048192: nop 0x8048193: nop 0x8048194: nop 0x8048195: nop 0x8048196: nop 0x8048197: n Okay so main @: (gdb) x/1000i 0x8048676 0x8048676: push %ebp 0x8048677: mov %esp,%ebp 0x8048679: and $0xfffffff0,%esp 0x804867c: sub $0x10,%esp 0x804867f: movl $0x80a7b54,(%esp) 0x8048686: call 0x8049ea0 0x804868b: movl $0x80a8304,(%esp) 0x8048692: call 0x8049ea0 0x8048697: movl $0x80a8338,(%esp) 0x804869e: call 0x8049ea0 0x80486a3: movl $0xa,(%esp) 0x80486aa: call 0x804a0d0 0x80486af: movl $0x80a8368,(%esp) 0x80486b6: call 0x8049ea0 0x80486bb: movl $0x80a83a0,(%esp) 0x80486c2: call 0x8049ea0 0x80486c7: movl $0x80a83d3,(%esp) 0x80486ce: call 0x8049ea0 0x80486d3: call 0x80482fe 0x80486d8: movl $0x80a83e0,(%esp) 0x80486df: call 0x8049ea0 0x80486e4: call 0x8048507 0x80486e9: movl $0x80a8408,(%esp) ---Type to continue, or q to quit--- 0x80486f0: call 0x8049ea0 0x80486f5: movl $0x80a8444,(%esp) 0x80486fc: call 0x8049ea0 0x8048701: call 0x8048563 0x8048706: call 0x80485b3 0x804870b: mov $0x0,%eax 0x8048710: leave 0x8048711: ret .... Lots of disassembling later we find that stage 3 of the program located @ 0x80485b3 and has this code at the end of it 0x804865f: mov $0x80a82ef,%eax 0x8048664: movl $0x80c90c0,0x4(%esp) 0x804866c: mov %eax,(%esp) 0x804866f: call 0x80498e0 0x8048674: leave 0x8048675: ret (gdb) x/s 0x080a82ef 0x80a82ef: "Your flag is: [%s]\n" (gdb) x/s 0x080c90c0 0x80c90c0: "" The flag isnt there yet. Lets try just calling into that code... (gdb) r ... (gdb) call (int *)(0x804865f)() Your flag is: [33fc9653b03a727395e39fc9627328fb] Program received signal SIGSEGV, Segmentation fault. 0x08048675 in ?? () The program being debugged was signaled while in a function called from GDB. GDB remains in the frame where the signal was received. To change this behavior use "set unwindonsignal on". Evaluation of the expression containing the function (at 0x0x804865f) will be abandoned. When the function is done executing, GDB will silently stop. (gdb) The program crashed because we called it from the middle of a function and the stack is a little off, but that's okay. We got our flag, but where does the flag get produced? (gdb) x/s 0x080c90c0 0x80c90c0: "33fc9653b03a727395e39fc9627328fb" student@csci4971:~/class/C/lab2/files$ objdump -D endiantest | grep 80c90c0 80482c9: 81 c2 c0 90 0c 08 add $0x80c90c0,%edx 8048664: c7 44 24 04 c0 90 0c movl $0x80c90c0,0x4(%esp) Lets check out the first hit: 0x80482c9: add $0x80c90c0,%edx 0x80482cf: mov %ecx,0x8(%esp) 0x80482d3: mov %eax,0x4(%esp) 0x80482d7: mov %edx,(%esp) 0x80482da: call 0x8049910 0x80482df: addl $0x1,-0x24(%ebp) 0x80482e3: mov -0x24(%ebp),%eax 0x80482e6: cmp $0xf,%eax 0x80482e9: jbe 0x80482b1 0x80482eb: mov -0xc(%ebp),%eax 0x80482ee: xor %gs:0x14,%eax 0x80482f5: je 0x80482fc 0x80482f7: call 0x80542f0 0x80482fc: leave Working backwards we can recognize the start of the function because of the standard prologue push %ebp mov %esp, ebp ... (gdb) x/200i 0x80482c9-121 0x8048250: push %ebp 0x8048251: mov %esp,%ebp 0x8048253: sub $0x38,%esp 0x8048256: mov %gs:0x14,%eax 0x804825c: mov %eax,-0xc(%ebp) 0x804825f: xor %eax,%eax 0x8048261: movl $0x73b6dc13,-0x1d(%ebp) 0x8048268: movl $0x53521a90,-0x19(%ebp) 0x804826f: movl $0xe9bfc3b5,-0x15(%ebp) 0x8048276: movl $0xdb085342,-0x11(%ebp) 0x804827d: movb $0x0,-0xd(%ebp) 0x8048281: movl $0x0,-0x24(%ebp) 0x8048288: jmp 0x80482a0 0x804828a: mov -0x24(%ebp),%eax 0x804828d: mov -0x24(%ebp),%edx 0x8048290: movzbl -0x1d(%ebp,%edx,1),%edx 0x8048295: xor $0x20,%edx 0x8048298: mov %dl,-0x1d(%ebp,%eax,1) 0x804829c: addl $0x1,-0x24(%ebp) 0x80482a0: mov -0x24(%ebp),%eax 0x80482a3: cmp $0x10,%eax 0x80482a6: jbe 0x804828a 0x80482a8: movl $0x0,-0x24(%ebp) 0x80482af: jmp 0x80482e3 0x80482b1: mov -0x24(%ebp),%eax 0x80482b4: movzbl -0x1d(%ebp,%eax,1),%eax 0x80482b9: movsbl %al,%eax 0x80482bc: movzbl %al,%ecx 0x80482bf: mov $0x80a7b08,%eax 0x80482c4: mov -0x24(%ebp),%edx 0x80482c7: add %edx,%edx 0x80482c9: add $0x80c90c0,%edx 0x80482cf: mov %ecx,0x8(%esp) 0x80482d3: mov %eax,0x4(%esp) 0x80482d7: mov %edx,(%esp) 0x80482da: call 0x8049910 0x80482df: addl $0x1,-0x24(%ebp) 0x80482e3: mov -0x24(%ebp),%eax 0x80482e6: cmp $0xf,%eax 0x80482e9: jbe 0x80482b1 0x80482eb: mov -0xc(%ebp),%eax 0x80482ee: xor %gs:0x14,%eax 0x80482f5: je 0x80482fc 0x80482f7: call 0x80542f0 0x80482fc: leave 0x80482fd: ret The above function represents a constructor that is used to populate the flag. From there we can pull out the key points: A hardcoded value is stored on the stack 0x8048261: movl $0x73b6dc13,-0x1d(%ebp) 0x8048268: movl $0x53521a90,-0x19(%ebp) 0x804826f: movl $0xe9bfc3b5,-0x15(%ebp) 0x8048276: movl $0xdb085342,-0x11(%ebp) A small loop xor decodes those values. 0x804828a: mov -0x24(%ebp),%eax 0x804828d: mov -0x24(%ebp),%edx 0x8048290: movzbl -0x1d(%ebp,%edx,1),%edx 0x8048295: xor $0x20,%edx 0x8048298: mov %dl,-0x1d(%ebp,%eax,1) 0x804829c: addl $0x1,-0x24(%ebp) 0x80482a0: mov -0x24(%ebp),%eax 0x80482a3: cmp $0x10,%eax 0x80482a6: jbe 0x804828a So putting that together we get: >>> data = [0x13,0xdc,0xb6,0x73,0x90,0x1a,0x52,0x53,0xb5,0xc3,0xbf,0xe9,0x42,0x53,0x08,0xdb] >>> print "".join([hex(x ^ 32)[2:] for x in data]) 33fc9653b03a727395e39fc9627328fb >>>