3 puntos por GN⁺ 2024-08-30 | 1 comentarios | Compartir por WhatsApp
  • Un desarrollador puso en ChatGPT un JavaScript minificado para entender cómo estaba implementado un componente de arte ASCII que vio en la web, y el modelo deshizo el flujo ofuscado hasta convertirlo en una estructura legible
  • El original era un componente de React que calculaba columnas y filas según el tamaño de la ventana, y actualizaba una cuadrícula de texto a 60fps con setInterval
  • ChatGPT explicó la selección del conjunto de caracteres, el cálculo de caracteres basado en coordenadas y tiempo, y el flujo de actualización de div.textContent, para luego reescribirlo como código TypeScript/React equivalente
  • Al principio pareció que la implementación era distinta porque faltaban caracteres de bloque como ░▒▓█, pero al revisar de nuevo el script original se confirmó que era un problema de codificación al copiar y pegar
  • Tras actualizarlo con el conjunto de caracteres correcto, la salida pasó a ser igual a la del componente original, mostrando que ChatGPT puede usarse de forma práctica para entender y reimplementar código minificado

Encontrando una implementación de arte ASCII en JavaScript minificado

  • Se revisó el código fuente para comprobar cómo estaba implementado el bloque de arte ASCII en la página de reactive.network hackathon
  • El código relacionado estaba minificado, así que era difícil de leer directamente para una persona; normalmente esto implicaría descifrarlo manualmente o buscar un archivo .map para restaurar el original
  • Esta vez, se pegó el código minificado completo en ChatGPT y se le pidió que explicara qué hacía

La estructura que reveló el código minificado

  • El código tenía una estructura que usaba JavaScript y React para generar contenido de texto dinámico
  • Los elementos principales eran los siguientes
    • Se usaban alias cortos para funciones matemáticas como Math.floor, Math.abs y Math.min
    • Había un conjunto de caracteres compuesto por "reactive.network REACTIVE.NETWORK" y caracteres especiales y de bloque
    • Se elegía qué conjunto de caracteres usar según el resultado de Date.now() % 3
    • A partir de la longitud del conjunto seleccionado, se calculaba el índice del carácter a mostrar en cada coordenada
  • La función principal, eT, recibía coordenadas de posición y un valor de tiempo, y devolvía un solo carácter para mostrar
    • El valor de tiempo se transformaba con t.time * 8e-5
    • Las coordenadas se ajustaban usando el valor menor entre columnas y filas, junto con la proporción de la pantalla
    • El índice del carácter se determinaba combinando el resultado de Math.cos(position.x * position.x - position.y * position.y) con el valor de tiempo

Cómo renderiza el componente de React

  • El componente usaba useRef para referenciar el div donde se colocaría el texto, y useState para guardar el height y width de la ventana
  • El primer useEffect establecía el tamaño inicial con window.innerHeight y window.innerWidth, y actualizaba esos valores cuando ocurría el evento resize
  • El segundo useEffect calculaba el tamaño de la cuadrícula ASCII a partir del tamaño de la pantalla
    • cols era Math.floor(width / 12) * 1.6
    • rows era Math.floor(height / 12)
    • aspect era cols / rows
  • setInterval se ejecutaba cada 1_000 / 60, regenerando la cadena para ajustarse a 60fps
  • En cada cuadro se recorrían todas las filas y columnas, se concatenaban los caracteres calculados y la cadena final se asignaba a element.textContent

Reescritura del código en TypeScript para hacerlo legible

  • Luego, el desarrollador pidió implementar un código equivalente en TypeScript, pero de una forma fácil de leer para humanos
  • El código generado por ChatGPT descompuso el papel del código minificado con nombres como los siguientes
    • selectedCharacterSet
    • characterSetLength
    • calculateCharacter
    • Tipo Size
    • Componente de React AsciiArtGenerator
  • Los nombres de variables crípticos se cambiaron por otros que revelaban la intención de la implementación
    • calculateCharacter calcula el carácter a mostrar a partir de las coordenadas, las columnas y filas, la proporción de pantalla y el valor de tiempo
    • textRef apunta al div real donde entra el texto
    • size y setSize representan el estado del tamaño de la ventana
  • El código reescrito era mucho más fácil de leer que el original minificado, y tenía un nivel suficiente para aprender cómo estaba implementado el componente

El malentendido por caracteres faltantes y el problema de codificación

  • En la comparación inicial, parecía que el LLM había pasado por alto algunos detalles de implementación
  • En particular, se creyó que en el resultado de ChatGPT faltaban caracteres como ░▒▓█ que sí aparecían en el componente original
  • Después, un usuario del foro de HN sugirió que podría tratarse de un problema de copiar y pegar, y una verificación adicional mostró que el código original contenía caracteres distintos de los que se habían pegado en ChatGPT
  • Al descargar directamente el script, se obtuvieron los caracteres correctos y se confirmó que la causa del problema era una diferencia de codificación
  • Después de corregir el conjunto de caracteres, la salida quedó idéntica a la del componente original

1 comentarios

 
GN⁺ 2024-08-30
Comentarios de Hacker News
  • Soy la persona que hizo HumanifyJS, y creé una herramienta basada en LLM específicamente para este caso de uso
    Usa el LLM a nivel de AST para garantizar que el código siga funcionando incluso después de desofuscarlo: https://github.com/jehna/humanify

    • Me pregunto si sería difícil agregar una función de renombrado desde cero
      Me refiero a una función que borre todos los nombres significativos del usuario en código normal y luego elija nombres nuevos basándose en el algoritmo y los nombres que queden, por ejemplo los nombres integrados
      A veces lo hago manualmente con un LLM al refactorizar, y sirve para encontrar términos más estándar como antiparallel_line en vez de parallel_line_opposite_direction, o descubrir nombres que se puedan generalizar más, como find_instance_in_list en vez de find_animal_instance_in_animals
    • Deberían hacerse más herramientas basadas en AST, es un trabajo excelente
      Sinceramente, todavía sigo esperando control de versiones a nivel de AST
    • Tengo curiosidad por saber qué preguntas le haces al LLM
      No sé si le das la función completa y le preguntas “¿qué habría que renombrar?” repitiendo hasta que todo cambie, o si le pides todo de una vez, luego parseas la salida y verificas que el AST siga coincidiendo
    • Me pregunto si funciona también con archivos enormes
      Me refiero a unas 50 mil líneas
      Edit: ahora mismo apenas estoy probándolo con un archivo JS de 1.2 mil líneas en modo openai, y después de 20 minutos solo va en 70%. Aunque en teoría funcione con archivos de 50 mil líneas, probablemente sea mejor no intentarlo
  • La minificación de JS es bastante mecánica y simple, así que revertirla también debería ser relativamente fácil
    Claro, en general sigue siendo lo bastante tedioso como para que una persona no quiera hacerlo a mano, pero la transformación en sí es bastante limitada, así que con notas para rastrear identificadores ofuscados debería poder leerse
    La desminificación o desofuscación más general todavía parece un problema abierto. Hace tiempo escribí algunos programas ofuscados a propósito, y por mi experiencia ChatGPT ni siquiera logró entenderlos a nivel superficial
    Por ejemplo, hubo un comentario que intentó explicar con GPT-4 el código de un gist de un intérprete de Brainfuck en C de 160 bytes [1], y esa “versión aclarada” no se parecía en nada al código original
    [1] https://gist.github.com/lifthrasiir/596667#gistcomment-47512...

    • Que una tarea sea simple no significa que su inversa también tenga que serlo
      Por ejemplo: multiplicación/factorización prima, derivación/integración, recordar el pasado/predecir el futuro
      Desofuscar código claramente es uno de esos problemas inversos difíciles, y puede volverse mucho más difícil por bugs, rutinas no usadas o irrelevantes, e implementaciones incorrectas que por casualidad producen el resultado esperado
      En ese sentido, si ChatGPT logra resultados decentes al desofuscar, es algo positivo; no hay motivo para esperar a priori que deba poder hacerlo. Está bien haber comprobado los límites de ChatGPT con un problema más difícil, pero creo que lo encontrado es solo un techo superior. No consideraría trivial el ejemplo del artículo original
    • Cambiar let userSignedInTimestamp = new Date() por let x = new Date() es trivial, pero para ir en sentido contrario probablemente haya que leer y entender el código alrededor para ver en qué contexto se usa x
      Además, el resto del código también está minificado, así que es más difícil, y aun si todo sale bien sigue siendo una transformación con pérdida, así que puede que el nombre de la variable contuviera propiedades que no estaban explícitas en el código
    • Convertir una foto a color en blanco y negro es bastante simple, pero volverla a sus colores originales no es nada fácil
      Eso pasa porque en el proceso se pierden datos
      La minificación funciona igual. Se pierde mucha información necesaria para entender el código, y recuperar esa información puede ser muy complicado
    • Cambiar un nombre de variable descriptivo como timeFactor por uno mucho más corto como i puede ser algo mecánico y simple, pero el proceso es destructivo y revertirlo no es relativamente fácil
      De hecho, es imposible sin entender con bastante sofisticación qué hace realmente el código. Eso es precisamente lo que hizo el LLM aquí, y aunque no me sorprende, sí está muy bien. Esa actitud de descalificarlo tan rápido no me gusta mucho
    • Hice un primer intento aleatorio con Claude 3.5 Sonnet: https://claude.site/artifacts/246c1b1a-3088-447a-a526-b1e716...
      No pude probarlo porque no estoy en una PC
  • ¿No debería el título usar ChatGPT o el nombre del modelo, GPT-4, en vez del nombre de la empresa, OpenAI?

    • Considerando que OpenAI convirtió GPT, que era un término técnico general, en una marca, hay cierta ironía justa en usar OpenAI como si fuera un nombre de producto
  • Los LLM destacan en la transformación de texto
    Esa es su fortaleza principal, pero parece que no se está aprovechando lo suficiente

    • No solo es su fortaleza principal, sino que eso es exactamente lo que los transformers fueron diseñados para hacer y, de forma polémica, podría decirse que es lo único que pueden hacer
      Otras capacidades como razonar o conservar conocimiento son solo efectos secundarios de esta habilidad sobrehumana de reproducir texto sin “entender” el significado pretendido
    • Son especialmente fuertes en transformaciones casi lineales, donde el orden no cambia mucho y no hace falta considerar en profundidad la relación entre elementos
      Por ejemplo, no pueden ordenar una lista, pero sí pueden traducir un idioma. Una lista casi bien ordenada está mal, pero una traducción casi correcta muchas veces sí la aceptamos
    • Con las herramientas adecuadas, debería ser posible mover una base de código a otro lenguaje y framework casi con solo apretar un botón
      Si un equipo está pensando si vale la pena convertir grandes partes de código Python a Elixir, ya no tendría que basarse solo en suposiciones
      Hace poco intenté traducir un script de Python a JavaScript y quedó impecable; con unas pocas barreras de seguridad, parece algo escalable
    • ¿No es ya este el principal caso de uso en negocios?
      Nosotros lo usamos sobre todo para extraer datos estructurados a partir de otros formatos
    • El problema es que tiene que tratarse de casos en los que no importe el riesgo de alucinación, o donde se pueda validar la salida aunque no exista ya un dato en un formato útil
      Además, también hay que carecer del conocimiento o la habilidad para resolverlo más rápido con awk, Python, Perl, etc.
  • Algunos desarrolladores que usaban la compresión como método de ofuscación sin duda se van a molestar
    Esto me recordó cuando versiones antiguas de ColdFusion traían una herramienta para “encriptar” código. El algoritmo era tan débil que no tardó mucho en aparecer una herramienta para revertirlo
    Aun así, a algunas personas les molestó porque confiaban en que esa herramienta era segura y la usaban para vender código sin entregar el fuente. Estamos hablando de finales de los 90 y principios de los 2000, antes de que el código abierto se volviera la opción dominante por amplio margen

    • Pronto parece que habrá este tipo de usos y una carrera armamentista
      Los sitios web ofrecerán competencias que dependan en parte de código del lado del cliente ofuscado, y los participantes más astutos ahora podrán pasárselo a ChatGPT para mejorar sus probabilidades de ganar
      Esto apenas está empezando
  • Este es un ejemplo de desempeño intelectual superior al humano
    No se puede negar. Esta tarea es intelectual y no tiene que ver con simple memorización. Tampoco hay tantísimos pares de código comprimido/código restaurado en la web como para que un LLM aprenda de eso
    El LLM entiende aquello que está descomprimiendo y, al menos en este tema concreto, en general supera al ser humano

    • Esto no es más que transformación de texto
      ¿De verdad se puede estar seguro de que no hay muchos pares de código comprimido y no comprimido en la web? Se pueden generar fácilmente a partir de JS existente para usarlos como conjunto de entrenamiento, y además ya hay cantidades enormes de JS sin comprimir en el entrenamiento
    • Soy optimista con la IA, pero no estoy convencido de que este sea un ejemplo del fenómeno que mencionas
      A los humanos nos cuesta entender código comprimido por nombres de variables opacos, bucles incómodos, espacios en blanco minimizados y cosas así. A las computadoras eso no les cuesta, y de hecho esa es la razón por la que comprimimos. La estructura de attention debería ser buena para este tipo de tarea
      También supongo que hay muchísimos pares de código comprimido/no comprimido. Ese es justamente el propósito de los archivos source map. Viendo cuánto ha invertido OpenAI en navegación web y desarrollo de software, no sorprendería que hayan incluido datos comprimidos/no comprimidos en el entrenamiento
    • No soy experto ni en IA ni en comprensión de la inteligencia, pero ¿no podría decirse también que una calculadora de bolsillo realmente entiende la aritmética y posee un desempeño intelectual superior al humano?
    • ¿Por qué no habría que contar el hecho de que los humanos crearon herramientas que ayudan a descomprimir dentro del puntaje humano?
      Que una computadora multiplique al instante números de 1000 dígitos es un caso en que los humanos primero construyeron esa herramienta y lograron hacer multiplicación con ella. ¿Qué otra cosa habría sido un éxito intelectual ahí? La computadora tampoco se construyó sola
      Si cortamos los límites de la inteligencia humana en el cráneo y no contamos las herramientas que esa inteligencia crea y usa como parte del proceso de resolver problemas, entonces también habría que aceptar que los humanos no son lo bastante inteligentes para ir al espacio, operar a alguien o cocinar la mayoría de sus comidas
    • GPT-4 ha consumido más código del que todos tus antepasados juntos vieron en toda su vida, y entiende los patrones intrínsecos entre el código y sus versiones comprimidas
      No parece descabellado que reconozca la forma abstracta del código con nombres de variables omitidos y mapee esos patrones a nombres de variables legibles por humanos basándose en patrones similares vistos en el inmenso internet
  • Interesante, y parece haber mejorado bastante
    Hace poco más de un año recibí un correo pidiéndome ayuda con el plugin personalizado de un sitio de WordPress hackeado, e intenté usar GPT para la desofuscación de malware. En ese momento, avanzar siguiendo el código manualmente paso a paso rendía mucho más
    Después de leer esta publicación, volví a intentarlo [0]. Me dio contenido comprensible, pero estaba ofuscado hasta el punto de prácticamente hacer eval de cadenas que no se podían leer a través del objeto Window, así que eso por sí solo no bastó
    Parte del informe que envié en ese momento decía algo así: pude desentrañar código JavaScript fuertemente ofuscado, detectar la carga de la página, desenmascarar una llamada que simulaba ser un cálculo teóricamente constante, y descifrar la lógica que redirigía la página a un sitio malicioso
    [0] https://chatgpt.com/share/f51fbd50-8df0-49e9-86ef-fc972bca6b...

  • ¿Alguien está trabajando en un LLM descompilador?
    Parece que podría volver todo el código como si fuera open source
    En este caso, los datos de entrenamiento serían fáciles de crear. Bastaría con compilar en masa código gratuito de GitHub con varios compiladores y entrenarlo para revertir la compilación. Es un caso donde los datos sintéticos de entrenamiento serían apropiados y bastante fáciles de generar
    El descompilador solo tendría que entrenarse para revertir la compilación, y para tareas como agregar comentarios se podrían usar LLMs de código más grandes que ya existen

    • El impacto potencial sería enorme
      No solo para abrir código, sino también para imaginar una situación donde se puedan descompilar y modificar fácilmente apps propietarias para corregir bugs o agregar funciones. Sobre todo, sería una gran liberación para programas abandonados hace mucho tiempo
      Por razones legales, es muy probable que en los modelos principales esto termine siendo una función bloqueada
    • Tener el código fuente es solo el primer paso, y también se necesita el derecho a leer ese código, modificarlo, ejecutarlo y redistribuir la versión modificada
      Solo quien escribió el código puede otorgar esos derechos
    • De acuerdo. Creo que eso de “la IA genera/entiende código fuente” es un gran señuelo para distraer
      Si la IA de verdad entendiera tan bien el código, simplemente crearía o corregiría el binario
      Si aparece un programador de IA real, creo que va a codificar todo en lenguaje de máquina sin preocuparse por código legible para humanos. Si le encargan mantener un sistema existente, entenderá de punta a punta todo el stack de software y hardware
      Es parecido a cómo los modelos de difusión para generar imágenes no se preocupan por aprender técnicas reales de dibujo
    • Hay un LLM que descompila de x86 a C: https://github.com/albertan017/LLM4Decompile
    • Desminificar no es descompilar
      Es más parecido a renombrar variables y funciones y poner saltos de línea
  • Se parece muchísimo a una forma en que uso LLMs con frecuencia [0]
    Cuando tengo que leer código a puro esfuerzo, los uso como primer paso para descifrarlo
    Hay algo enormemente liberador en meter código espagueti hecho un desastre, pedir que lo desenvuelvan para que sea más legible, y empezar desde ahí
    Como también descubrió el autor, los LLM a veces se comen detalles, pero esas partes las puedo detectar yo mismo
    Otro uso es cuando entiendo qué hace el código, pero no termino de captar por qué está hecho de cierta manera. Eso pasa especialmente cuando quien escribió ese código ya no está en la empresa. En ese caso, meto el método en un chat con un LLM, le pido que explique qué hace y luego le pregunto por qué algunas cosas podrían estar hechas de esa forma
    No siempre acierta, pero bastante seguido da explicaciones que realmente tienen sentido, resisten revisión y me aportan nuevas ideas. Una o dos veces incluso evitó que refactorizara de una manera que después me habría traído dolores de cabeza
    [0] Uso chatGPT y, últimamente, varios otros modelos, principalmente openwebUI como frontend para variantes de Claude, para ver las diferencias. También se pueden armar configuraciones interesantes donde distintos modelos revisan las respuestas de los demás

  • Si el código desminificado no coincide con el código minificado, entonces su utilidad cae muchísimo, como en la parte final donde dice que “la respuesta del LLM parece haber pasado por alto algunos detalles de implementación”
    Sobre todo porque en muchos casos no se puede simplemente ejecutar el código, como en el artículo, para comprobar la diferencia
    Edición: viendo el hilo de abajo, parece que fue un problema de codificación. Aun así, la precisión me sigue preocupando un poco

    • Para el renombrado real habría que usar otra herramienta, como HumanifyJS: https://github.com/jehna/humanify
    • El código desminificado se ve bastante cercano al original
      En algunos casos, ChatGPT incluso hizo su propia refactorización además de desminificar
      El código original no llama a handleResize inmediatamente, sino que tiene su contenido inline. Probablemente el minificador hizo el inlining de verdad. La única diferencia real aquí es que falta la condición if (typeof window < "u")
    • Eso significa que en la versión generada por ChatGPT faltan algunos caracteres usados en el ejemplo original
      En concreto, se refiere a caracteres de bloque, y parece que HN no deja pegar caracteres Unicode. En la versión original sí se ven, pero en la generada por ChatGPT no
      Aun así, también es totalmente posible que haya sido porque no incluí todo el contexto que hacía falta
      Descalificar toda la salida porque faltan unos pocos caracteres ya roza lo excesivamente quisquilloso. Fuera de eso, para mí la salida es idéntica
    • Además, también le pidió que lo reimplementara de JavaScript a TypeScript
      Si solo le hubiera pedido cambiar los nombres de variables y métodos, probablemente habría quedado más cerca del original