As we already know, our goal here is to call the winner function, and as I explained before, we have an arbitrary write primitive in our hands. One easy way of abusing this is overwriting the address of some other function that will be called with the address of winner(), for example, that being said, our best choice would be puts(), since it will be called short after our input is copied. For that we'll need to find the Global Offset Table (GOT) entry for puts().
(gdb) disas 0x80485b0
Dump of assembler code for function puts@plt:
0x080485b0 <+0>: jmp DWORD PTR ds:0x804c140
0x080485b6 <+6>: push 0x28
0x080485bb <+11>: jmp 0x8048550
End of assembler dump.
(gdb) x 0x804c140
0x804c140 <puts@got.plt>: 0x080485b6
The address we want to overwrite can be found at 0x804c140. We also need the address of the winner function since it is the value we want to write.
(gdb) x winner
0x804889a <winner>: 0x83e58955
And winner() can be found at 0x804889a. Perfect! Now let's take a good look at the heap layout to more easily calculate our padding, to do this I'll set a breakpoint after the last malloc and also another one after each strcpy().
(gdb) b*0x0804883a
Breakpoint 1 at 0x804883a
(gdb) b*0x0804885d
Breakpoint 2 at 0x804885d
(gdb) b*0x08048878
Breakpoint 3 at 0x8048878
Taking a look at the heap after our first breakpoint:
At this point the first strcpy() did not happen yet. As we predicted, the second word of the first chunk is a pointer to WHERE to write the user input, and we should expect a similar structure on i2 (the second chunk). Now we know where our argv[1] goes (0xf7e69018) and we can easily predict where our juicy pointer will be, we can calculate our offset, i2->name will be the word after the second priority (2), meaning it's address will be 0xf7e6902c. This reveals that we want to write 20 bytes of junk + the address where we want to write (puts@got) and argv[2] will be the value we want to write (*winner).
The expected heap layout right before the last strcpy() is the following:
with that layout, whatever value we provide in our second argument will be written to puts@got, so we simply need to provide the address of winner as the second argument.
Putting it all together we come down to the following exploit code: