DBMS_LOB.SUBSTR irá, para um BLOB, retornar um RAW. A maioria dos ambientes renderizará isso em hexadecimal. Você pode usar a função DUMP para visualizá-lo em alguns outros formatos.
select dump(dbms_lob.substr(product_image,10,1),10),
dump(dbms_lob.substr(product_image,10,1),16),
dump(dbms_lob.substr(product_image,10,1),17)
from APEX_DEMO.DEMO_PRODUCT_INFO
where product_id = 9;
Isso retorna os primeiros 10 bytes do BLOB em decimal (por exemplo, 0-255), hexadecimal e caractere. Este último pode lançar algum lixo não imprimível na tela e, se os conjuntos de caracteres do cliente e do banco de dados não corresponderem, sofrer alguma 'tradução'.
Você pode usar UTL_RAW.CAST_TO_VARCHAR2, que pode fornecer o que você deseja.
select utl_raw.cast_to_varchar2(dbms_lob.substr(product_image,10,1)) chr
from APEX_DEMO.DEMO_PRODUCT_INFO
where product_id = 9