I did the orw challange on pwnable.tw yesterday. It is very streight forward. You just have to send some x86 shellcode to stdin and the orw binary will execute it.
But I spend a few hours with getting this to work with gdb as the instructions in gdb were quite weird.
10x08048571 push 0xc8 ; 200 ; size_t nbyte
20x08048576 push obj.shellcode ; 0x804a060 ; void *buf
30x0804857b push 0 ; int fildes
40x0804857d call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
50x08048582 add esp, 0x10
60x08048585 mov eax, obj.shellcode ; 0x804a060
70x0804858a call eax
So it just writes the shellcode to 0x804a060
and jumps to the address using call
.
I’m using pwntools to write my exploits as a lot of other people are doing.
The important part is the gdb script:
1#!python2
2from pwn import *
3
4context.update(arch='i386', endian='little', os='linux')
5p = process("./challange/orw")
6elf = ELF("./challange/orw")
7gdb.attach(p, '''
8 # 1. breakpoint
9 break *0x0804858a
10 # 2. breakpoint
11 break *0x804a060
12 c
13 x /5i 0x804a060
14 x /5x 0x804a060
15''')
16
17shellcode = 'AAA' # Example 4 byte non-workable shellcode to see the effect of software breakpoints
18
19p.recvuntil("Give my your shellcode:")
20p.send(asm(shellcode))
21
22p.interactive()
Alright so we are setting 2 breakpoints when the programm starts. These are probably software
breakpoints as internal documents of GDB state. These write an INT
instruction to the specified instruction:
“Since it literally overwrites the program being tested, the program area must be writable, so this technique won’t work on programs in ROM. It can also distort the behavior of programs that examine themselves, although such a situation would be highly unusual.”
So yes it can distort the behavior and in this example it did! So lets see what we can find at the
instruction 0x804a060
when reaching the second first or second breakpoint:
0x804a060: 0x41414100
As you can see it seems like only 3 bytes got copied. The first byte is 0x00
.
This is because gdb wrote 0x90
(INT) to the address when the breakpoint was set.
After reaching the 1. breakpoing it wrote 0x90
again to make sure that the programm will stop at
the 2. breakpoint.
After reaching the 2. breakpoint it restored the byte to 0x00
because when the breakpoints were set it actually was.
So this is why gdb fucks our shellcode up! It restored the value where the INT
was to the wrong
value!
You may notice that the following script will not corrupt the instructions:
1gdb.attach(p, '''
2 break *0x804a060
3 c
4 x /5i 0x804a060
5 x /5x 0x804a060
6''')
This works because the code gets rewritten between setting the breakpoints and reaching it. So the breakpoint here will not work but also cause no problem.
Solution I
The solution is to set the breakpoint at 0x804a060
after the shellcode was copied!
1gdb.attach(p, '''
2 break *0x0804858a
3 c
4 break *0x804a060
5 x /5i 0x804a060
6 x /5x 0x804a060
7''')
Solution II
The other solution is to use hardware breakpoints which do not modify the code the CPU will execute! Note that there are only a limited amount of them!
Reason of confusion
The reson why I was so confused is that gdb never showed my the INT
instructions. So I did not
think that gdb would restore values to outdated values!
Even if you look at the assembler code in gdb it will not show it you
(Debugger flow control: Hardware breakpoints vs software breakpoints):
“Now, you might be tempted to say that this isn’t really how software breakpoints work, if you have ever tried to disassemble or dump the raw opcode bytes of anything that you have set a breakpoint on, because if you do that, you’ll not see an int 3 anywhere where you set a breakpoint. This is actually because the debugger tells a lie to you about the contents of memory where software breakpoints are involved; any access to that memory (through the debugger) behaves as if the original opcode byte that the debugger saved away was still there.”
Conclusion
Never set software breakpoints at addresses you are writing executable code to!